Added PropertyName type

This commit is contained in:
Alexei Anoshenko 2024-11-13 12:56:39 +03:00
parent 8fcc52de63
commit e2775d52f2
74 changed files with 6218 additions and 7340 deletions

View File

@ -425,7 +425,7 @@ View имеет ряд свойств, таких как высота, шири
(View реализует данный интерфейс): (View реализует данный интерфейс):
type Properties interface { type Properties interface {
Get(tag string) any Get(tag PropertyName) any
Set(tag string, value any) bool Set(tag string, value any) bool
Remove(tag string) Remove(tag string)
Clear() Clear()

View File

@ -429,7 +429,7 @@ View has a number of properties like height, width, color, text parameters, etc.
The Properties interface is used to read and write the property value (View implements this interface): The Properties interface is used to read and write the property value (View implements this interface):
type Properties interface { type Properties interface {
Get(tag string) any Get(tag PropertyName) any
Set(tag string, value any) bool Set(tag string, value any) bool
Remove(tag string) Remove(tag string)
Clear() Clear()

View File

@ -20,7 +20,8 @@ func NewAbsoluteLayout(session Session, params Params) AbsoluteLayout {
} }
func newAbsoluteLayout(session Session) View { func newAbsoluteLayout(session Session) View {
return NewAbsoluteLayout(session, nil) //return NewAbsoluteLayout(session, nil)
return new(absoluteLayoutData)
} }
// Init initialize fields of ViewsContainer by default values // Init initialize fields of ViewsContainer by default values

View File

@ -18,7 +18,7 @@ const (
// //
// Internal type is `[]Animation`, other types converted to it during assignment. // Internal type is `[]Animation`, other types converted to it during assignment.
// See `Animation` description for more details. // See `Animation` description for more details.
AnimationTag = "animation" AnimationTag PropertyName = "animation"
// AnimationPaused is the constant for "animation-paused" property tag. // AnimationPaused is the constant for "animation-paused" property tag.
// //
@ -30,7 +30,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Animation is paused. // `true` or `1` or "true", "yes", "on", "1" - Animation is paused.
// `false` or `0` or "false", "no", "off", "0" - Animation is playing. // `false` or `0` or "false", "no", "off", "0" - Animation is playing.
AnimationPaused = "animation-paused" AnimationPaused PropertyName = "animation-paused"
// Transition is the constant for "transition" property tag. // Transition is the constant for "transition" property tag.
// //
@ -44,7 +44,7 @@ const (
// Supported types: `Params`. // Supported types: `Params`.
// //
// See `Params` description for more details. // See `Params` description for more details.
Transition = "transition" Transition PropertyName = "transition"
// PropertyTag is the constant for "property" property tag. // PropertyTag is the constant for "property" property tag.
// //
@ -55,7 +55,7 @@ const (
// //
// Internal type is `[]AnimatedProperty`, other types converted to it during assignment. // Internal type is `[]AnimatedProperty`, other types converted to it during assignment.
// See `AnimatedProperty` description for more details. // See `AnimatedProperty` description for more details.
PropertyTag = "property" PropertyTag PropertyName = "property"
// Duration is the constant for "duration" property tag. // Duration is the constant for "duration" property tag.
// //
@ -65,7 +65,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Duration = "duration" Duration PropertyName = "duration"
// Delay is the constant for "delay" property tag. // Delay is the constant for "delay" property tag.
// //
@ -77,7 +77,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Delay = "delay" Delay PropertyName = "delay"
// TimingFunction is the constant for "timing-function" property tag. // TimingFunction is the constant for "timing-function" property tag.
// //
@ -92,7 +92,7 @@ const (
// "ease-out"(`EaseOutTiming`) - Speed is fast at first, but decreases 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. // "ease-in-out"(`EaseInOutTiming`) - Speed is slow at first, but quickly increases and at the end it decreases again.
// "linear"(`LinearTiming`) - Constant speed. // "linear"(`LinearTiming`) - Constant speed.
TimingFunction = "timing-function" TimingFunction PropertyName = "timing-function"
// IterationCount is the constant for "iteration-count" property tag. // IterationCount is the constant for "iteration-count" property tag.
// //
@ -102,7 +102,7 @@ const (
// Supported types: `int`, `string`. // Supported types: `int`, `string`.
// //
// Internal type is `int`, other types converted to it during assignment. // Internal type is `int`, other types converted to it during assignment.
IterationCount = "iteration-count" IterationCount PropertyName = "iteration-count"
// AnimationDirection is the constant for "animation-direction" property tag. // AnimationDirection is the constant for "animation-direction" property tag.
// //
@ -117,7 +117,7 @@ const (
// `1`(`ReverseAnimation`) or "reverse" - The animation plays backwards, from the last position to the first, and then resets to the final position and plays again. // `1`(`ReverseAnimation`) or "reverse" - The animation plays backwards, from the last position to the first, and then resets to the final position and plays again.
// `2`(`AlternateAnimation`) or "alternate" - The animation changes direction in each cycle, that is, in the first cycle, it starts from the start position, reaches the end position, and in the second cycle, it continues from the end position and reaches the start position, and so on. // `2`(`AlternateAnimation`) or "alternate" - The animation changes direction in each cycle, that is, in the first cycle, it starts from the start position, reaches the end position, and in the second cycle, it continues from the end position and reaches the start position, and so on.
// `3`(`AlternateReverseAnimation`) or "alternate-reverse" - The animation starts playing from the end position and reaches the start position, and in the next cycle, continuing from the start position, it goes to the end position. // `3`(`AlternateReverseAnimation`) or "alternate-reverse" - The animation starts playing from the end position and reaches the start position, and in the next cycle, continuing from the start position, it goes to the end position.
AnimationDirection = "animation-direction" AnimationDirection PropertyName = "animation-direction"
// NormalAnimation is value of the "animation-direction" property. // NormalAnimation is value of the "animation-direction" property.
// The animation plays forwards each cycle. In other words, each time the animation cycles, // The animation plays forwards each cycle. In other words, each time the animation cycles,
@ -180,7 +180,7 @@ func CubicBezierTiming(x1, y1, x2, y2 float64) string {
// AnimatedProperty describes the change script of one property // AnimatedProperty describes the change script of one property
type AnimatedProperty struct { type AnimatedProperty struct {
// Tag is the name of the property // Tag is the name of the property
Tag string Tag PropertyName
// From is the initial value of the property // From is the initial value of the property
From any From any
// To is the final value of the property // To is the final value of the property
@ -190,12 +190,12 @@ type AnimatedProperty struct {
} }
type animationData struct { type animationData struct {
propertyList dataProperty
keyFramesName string keyFramesName string
usageCounter int usageCounter int
view View view View
listener func(view View, animation Animation, event string) listener func(view View, animation Animation, event PropertyName)
oldListeners map[string][]func(View, string) oldListeners map[PropertyName][]func(View, PropertyName)
oldAnimation []Animation oldAnimation []Animation
} }
@ -207,7 +207,7 @@ type Animation interface {
// Start starts the animation for the view specified by the first argument. // Start starts the animation for the view specified by the first argument.
// The second argument specifies the animation event listener (can be nil) // The second argument specifies the animation event listener (can be nil)
Start(view View, listener func(view View, animation Animation, event string)) bool Start(view View, listener func(view View, animation Animation, event PropertyName)) bool
// Stop stops the animation // Stop stops the animation
Stop() Stop()
// Pause pauses the animation // Pause pauses the animation
@ -215,7 +215,7 @@ type Animation interface {
// Resume resumes an animation that was stopped using the Pause method // Resume resumes an animation that was stopped using the Pause method
Resume() Resume()
writeTransitionString(tag string, buffer *strings.Builder) writeTransitionString(tag PropertyName, buffer *strings.Builder)
animationCSS(session Session) string animationCSS(session Session) string
transitionCSS(buffer *strings.Builder, session Session) transitionCSS(buffer *strings.Builder, session Session)
hasAnimatedProperty() bool hasAnimatedProperty() bool
@ -230,10 +230,11 @@ func parseAnimation(obj DataObject) Animation {
for i := 0; i < obj.PropertyCount(); i++ { for i := 0; i < obj.PropertyCount(); i++ {
if node := obj.Property(i); node != nil { if node := obj.Property(i); node != nil {
tag := PropertyName(node.Tag())
if node.Type() == TextNode { if node.Type() == TextNode {
animation.Set(node.Tag(), node.Text()) animation.Set(tag, node.Text())
} else { } else {
animation.Set(node.Tag(), node) animation.Set(tag, node)
} }
} }
} }
@ -251,6 +252,13 @@ func NewAnimation(params Params) Animation {
return animation return animation
} }
func (animation *animationData) init() {
animation.dataProperty.init()
animation.normalize = normalizeAnimationTag
animation.set = animationSet
animation.supportedProperties = []PropertyName{ID, PropertyTag, Duration, Delay, TimingFunction, IterationCount, AnimationDirection}
}
func (animation *animationData) animatedProperties() []AnimatedProperty { func (animation *animationData) animatedProperties() []AnimatedProperty {
value := animation.getRaw(PropertyTag) value := animation.getRaw(PropertyTag)
if value == nil { if value == nil {
@ -291,33 +299,28 @@ func (animation *animationData) unused(session Session) {
} }
} }
func (animation *animationData) normalizeTag(tag string) string { func normalizeAnimationTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
if tag == Direction { if tag == Direction {
return AnimationDirection return AnimationDirection
} }
return tag return tag
} }
func (animation *animationData) Set(tag string, value any) bool { func animationSet(properties Properties, tag PropertyName, value any) []PropertyName {
if value == nil { switch tag {
animation.Remove(tag)
return true
}
switch tag = animation.normalizeTag(tag); tag {
case ID: case ID:
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
text = strings.Trim(text, " \t\n\r") text = strings.Trim(text, " \t\n\r")
if text == "" { if text == "" {
delete(animation.properties, tag) properties.setRaw(tag, nil)
} else { } else {
animation.properties[tag] = text properties.setRaw(tag, text)
} }
return true return []PropertyName{tag}
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
case PropertyTag: case PropertyTag:
switch value := value.(type) { switch value := value.(type) {
@ -340,8 +343,8 @@ func (animation *animationData) Set(tag string, value any) bool {
} else if value.To == nil { } else if value.To == nil {
ErrorLog("AnimatedProperty.To is nil") ErrorLog("AnimatedProperty.To is nil")
} else { } else {
animation.properties[tag] = []AnimatedProperty{value} properties.setRaw(tag, []AnimatedProperty{value})
return true return []PropertyName{tag}
} }
case []AnimatedProperty: case []AnimatedProperty:
@ -369,8 +372,8 @@ func (animation *animationData) Set(tag string, value any) bool {
} }
} }
if len(props) > 0 { if len(props) > 0 {
animation.properties[tag] = props properties.setRaw(tag, props)
return true return []PropertyName{tag}
} else { } else {
ErrorLog("[]AnimatedProperty is empty") ErrorLog("[]AnimatedProperty is empty")
} }
@ -417,8 +420,8 @@ func (animation *animationData) Set(tag string, value any) bool {
switch value.Type() { switch value.Type() {
case ObjectNode: case ObjectNode:
if prop, ok := parseObject(value.Object()); ok { if prop, ok := parseObject(value.Object()); ok {
animation.properties[tag] = []AnimatedProperty{prop} properties.setRaw(tag, []AnimatedProperty{prop})
return true return []PropertyName{tag}
} }
case ArrayNode: case ArrayNode:
@ -433,8 +436,8 @@ func (animation *animationData) Set(tag string, value any) bool {
} }
} }
if len(props) > 0 { if len(props) > 0 {
animation.properties[tag] = props properties.setRaw(tag, props)
return true return []PropertyName{tag}
} }
default: default:
@ -446,36 +449,28 @@ func (animation *animationData) Set(tag string, value any) bool {
} }
case Duration: case Duration:
return animation.setFloatProperty(tag, value, 0, math.MaxFloat64) return setFloatProperty(properties, tag, value, 0, math.MaxFloat64)
case Delay: case Delay:
return animation.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64) return setFloatProperty(properties, tag, value, -math.MaxFloat64, math.MaxFloat64)
case TimingFunction: case TimingFunction:
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
animation.properties[tag] = text properties.setRaw(tag, text)
return true return []PropertyName{tag}
} }
case IterationCount: case IterationCount:
return animation.setIntProperty(tag, value) return setIntProperty(properties, tag, value)
case AnimationDirection: case AnimationDirection:
return animation.setEnumProperty(AnimationDirection, value, enumProperties[AnimationDirection].values) return setEnumProperty(properties, AnimationDirection, value, enumProperties[AnimationDirection].values)
default: default:
ErrorLogF(`The "%s" property is not supported by Animation`, tag) ErrorLogF(`The "%s" property is not supported by Animation`, tag)
} }
return false return nil
}
func (animation *animationData) Remove(tag string) {
delete(animation.properties, animation.normalizeTag(tag))
}
func (animation *animationData) Get(tag string) any {
return animation.getRaw(animation.normalizeTag(tag))
} }
func (animation *animationData) String() string { func (animation *animationData) String() string {
@ -488,7 +483,7 @@ func (animation *animationData) String() string {
if tag != PropertyTag { if tag != PropertyTag {
if value, ok := animation.properties[tag]; ok && value != nil { if value, ok := animation.properties[tag]; ok && value != nil {
buffer.WriteString("\n\t") buffer.WriteString("\n\t")
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, "\t") writePropertyValue(buffer, tag, value, "\t")
buffer.WriteRune(',') buffer.WriteRune(',')
@ -497,7 +492,7 @@ func (animation *animationData) String() string {
} }
writeProperty := func(prop AnimatedProperty, indent string) { writeProperty := func(prop AnimatedProperty, indent string) {
buffer.WriteString(prop.Tag) buffer.WriteString(string(prop.Tag))
buffer.WriteString("{\n") buffer.WriteString("{\n")
buffer.WriteString(indent) buffer.WriteString(indent)
buffer.WriteString("from = ") buffer.WriteString("from = ")
@ -512,7 +507,7 @@ func (animation *animationData) String() string {
tag := strconv.Itoa(key) + "%" tag := strconv.Itoa(key) + "%"
buffer.WriteString(tag) buffer.WriteString(tag)
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, PropertyName(tag), value, indent)
} }
buffer.WriteString("\n") buffer.WriteString("\n")
buffer.WriteString(indent[1:]) buffer.WriteString(indent[1:])
@ -522,7 +517,7 @@ func (animation *animationData) String() string {
if props := animation.animatedProperties(); len(props) > 0 { if props := animation.animatedProperties(); len(props) > 0 {
buffer.WriteString("\n\t") buffer.WriteString("\n\t")
buffer.WriteString(PropertyTag) buffer.WriteString(string(PropertyTag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
if len(props) > 1 { if len(props) > 1 {
buffer.WriteString("[\n") buffer.WriteString("[\n")
@ -606,15 +601,15 @@ func (animation *animationData) transitionCSS(buffer *strings.Builder, session S
} }
} }
func (animation *animationData) writeTransitionString(tag string, buffer *strings.Builder) { func (animation *animationData) writeTransitionString(tag PropertyName, buffer *strings.Builder) {
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString("{") buffer.WriteString("{")
lead := " " lead := " "
writeFloatProperty := func(name string) bool { writeFloatProperty := func(name PropertyName) bool {
if value := animation.getRaw(name); value != nil { if value := animation.getRaw(name); value != nil {
buffer.WriteString(lead) buffer.WriteString(lead)
buffer.WriteString(name) buffer.WriteString(string(name))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, name, value, "") writePropertyValue(buffer, name, value, "")
lead = ", " lead = ", "
@ -633,7 +628,7 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string
if value := animation.getRaw(TimingFunction); value != nil { if value := animation.getRaw(TimingFunction); value != nil {
if timingFunction, ok := value.(string); ok && timingFunction != "" { if timingFunction, ok := value.(string); ok && timingFunction != "" {
buffer.WriteString(lead) buffer.WriteString(lead)
buffer.WriteString(TimingFunction) buffer.WriteString(string(TimingFunction))
buffer.WriteString(" = ") buffer.WriteString(" = ")
if strings.ContainsAny(timingFunction, " ,()") { if strings.ContainsAny(timingFunction, " ,()") {
buffer.WriteRune('"') buffer.WriteRune('"')
@ -767,7 +762,7 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string {
return name return name
} }
func (view *viewData) SetAnimated(tag string, value any, animation Animation) bool { func (view *viewData) SetAnimated(tag PropertyName, value any, animation Animation) bool {
if animation == nil { if animation == nil {
return view.Set(tag, value) return view.Set(tag, value)
} }
@ -779,27 +774,30 @@ func (view *viewData) SetAnimated(tag string, value any, animation Animation) bo
session.updateProperty(htmlID, "ontransitionend", "transitionEndEvent(this, event)") session.updateProperty(htmlID, "ontransitionend", "transitionEndEvent(this, event)")
session.updateProperty(htmlID, "ontransitioncancel", "transitionCancelEvent(this, event)") session.updateProperty(htmlID, "ontransitioncancel", "transitionCancelEvent(this, event)")
if prevAnimation, ok := view.transitions[tag]; ok { transitions := getTransitionProperty(view)
view.singleTransition[tag] = prevAnimation var prevAnimation Animation = nil
} else { if transitions != nil {
view.singleTransition[tag] = nil if prev, ok := transitions[tag]; ok {
prevAnimation = prev
}
} }
view.transitions[tag] = animation view.singleTransition[tag] = prevAnimation
view.updateTransitionCSS() setTransition(view, tag, animation)
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
session.finishUpdateScript(htmlID) session.finishUpdateScript(htmlID)
result := view.Set(tag, value) result := view.Set(tag, value)
if !result { if !result {
delete(view.singleTransition, tag) delete(view.singleTransition, tag)
view.updateTransitionCSS() view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
} }
return result return result
} }
func (style *viewStyle) animationCSS(session Session) string { func animationCSS(properties Properties, session Session) string {
if value := style.getRaw(AnimationTag); value != nil { if value := properties.getRaw(AnimationTag); value != nil {
if animations, ok := value.([]Animation); ok { if animations, ok := value.([]Animation); ok {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
@ -820,78 +818,154 @@ func (style *viewStyle) animationCSS(session Session) string {
return "" return ""
} }
func (style *viewStyle) transitionCSS(session Session) string { func transitionCSS(properties Properties, session Session) string {
buffer := allocStringBuilder() if transitions := getTransitionProperty(properties); len(transitions) > 0 {
defer freeStringBuilder(buffer) buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
convert := map[string]string{ convert := map[PropertyName]string{
CellHeight: "grid-template-rows", CellHeight: "grid-template-rows",
CellWidth: "grid-template-columns", CellWidth: "grid-template-columns",
Row: "grid-row", Row: "grid-row",
Column: "grid-column", Column: "grid-column",
Clip: "clip-path", Clip: "clip-path",
Shadow: "box-shadow", Shadow: "box-shadow",
ColumnSeparator: "column-rule", ColumnSeparator: "column-rule",
FontName: "font", FontName: "font",
TextSize: "font-size", TextSize: "font-size",
TextLineThickness: "text-decoration-thickness", TextLineThickness: "text-decoration-thickness",
}
for tag, animation := range style.transitions {
if buffer.Len() > 0 {
buffer.WriteString(", ")
} }
if cssTag, ok := convert[tag]; ok { for tag, animation := range transitions {
buffer.WriteString(cssTag) if buffer.Len() > 0 {
} else { buffer.WriteString(", ")
buffer.WriteString(tag) }
if cssTag, ok := convert[tag]; ok {
buffer.WriteString(cssTag)
} else {
buffer.WriteString(string(tag))
}
animation.transitionCSS(buffer, session)
} }
animation.transitionCSS(buffer, session) return buffer.String()
} }
return buffer.String() return ""
} }
/*
func (view *viewData) updateTransitionCSS() { func (view *viewData) updateTransitionCSS() {
view.session.updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.session)) view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
} }
*/
func (style *viewStyle) Transition(tag string) Animation { func (style *viewStyle) Transition(tag PropertyName) Animation {
if style.transitions != nil { if transitions := getTransitionProperty(style); transitions != nil {
if anim, ok := style.transitions[tag]; ok { if anim, ok := transitions[tag]; ok {
return anim return anim
} }
} }
return nil return nil
} }
func (style *viewStyle) Transitions() map[string]Animation { func (style *viewStyle) Transitions() map[PropertyName]Animation {
result := map[string]Animation{} result := map[PropertyName]Animation{}
for tag, animation := range style.transitions { for tag, animation := range getTransitionProperty(style) {
result[tag] = animation result[tag] = animation
} }
return result return result
} }
func (style *viewStyle) SetTransition(tag string, animation Animation) { func (style *viewStyle) SetTransition(tag PropertyName, animation Animation) {
if animation == nil { setTransition(style, style.normalize(tag), animation)
delete(style.transitions, tag) }
} else {
style.transitions[tag] = animation func (view *viewData) SetTransition(tag PropertyName, animation Animation) {
setTransition(view, view.normalize(tag), animation)
if view.created {
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
} }
} }
func (view *viewData) SetTransition(tag string, animation Animation) { func setTransition(properties Properties, tag PropertyName, animation Animation) {
view.viewStyle.SetTransition(tag, animation) transitions := getTransitionProperty(properties)
if view.created {
view.session.updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.session)) if animation == nil {
if transitions != nil {
delete(transitions, tag)
if len(transitions) == 0 {
properties.setRaw(Transition, nil)
}
}
} else if transitions != nil {
transitions[tag] = animation
} else {
properties.setRaw(Transition, map[PropertyName]Animation{tag: animation})
} }
} }
func getTransitionProperty(properties Properties) map[PropertyName]Animation {
if value := properties.getRaw(Transition); value != nil {
if transitions, ok := value.(map[PropertyName]Animation); ok {
return transitions
}
}
return nil
}
func setAnimationProperty(properties Properties, tag PropertyName, value any) bool {
set := func(animations []Animation) {
properties.setRaw(tag, animations)
for _, animation := range animations {
animation.used()
}
}
switch value := value.(type) {
case Animation:
set([]Animation{value})
return true
case []Animation:
set(value)
return true
case DataObject:
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
set([]Animation{animation})
return true
}
case DataNode:
animations := []Animation{}
result := true
for i := 0; i < value.ArraySize(); i++ {
if obj := value.ArrayElement(i).Object(); obj != nil {
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
animations = append(animations, anim)
} else {
result = false
}
} else {
notCompatibleType(tag, value.ArrayElement(i))
result = false
}
}
if result && len(animations) > 0 {
set(animations)
}
return result
}
notCompatibleType(tag, value)
return false
}
// SetAnimated sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result: // SetAnimated sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
// true - success, // true - success,
// false - error (incompatible type or invalid format of a string value, see AppLog). // false - error (incompatible type or invalid format of a string value, see AppLog).
func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool { func SetAnimated(rootView View, viewID string, tag PropertyName, value any, animation Animation) bool {
if view := ViewByID(rootView, viewID); view != nil { if view := ViewByID(rootView, viewID); view != nil {
return view.SetAnimated(tag, value, animation) return view.SetAnimated(tag, value, animation)
} }
@ -906,7 +980,7 @@ func IsAnimationPaused(view View, subviewID ...string) bool {
// GetTransitions returns the subview transitions. The result is always non-nil. // GetTransitions returns the subview transitions. The result is always non-nil.
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned // If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
func GetTransitions(view View, subviewID ...string) map[string]Animation { func GetTransitions(view View, subviewID ...string) map[PropertyName]Animation {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -915,12 +989,12 @@ func GetTransitions(view View, subviewID ...string) map[string]Animation {
return view.Transitions() return view.Transitions()
} }
return map[string]Animation{} return map[PropertyName]Animation{}
} }
// GetTransition returns the subview property transition. If there is no transition for the given property then nil is returned. // GetTransition returns the subview property transition. If there is no transition for the given property then nil is returned.
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned // If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
func GetTransition(view View, subviewID, tag string) Animation { func GetTransition(view View, subviewID string, tag PropertyName) Animation {
if subviewID != "" { if subviewID != "" {
view = ViewByID(view, subviewID) view = ViewByID(view, subviewID)
} }
@ -934,7 +1008,7 @@ func GetTransition(view View, subviewID, tag string) Animation {
// AddTransition adds the transition for the subview property. // AddTransition adds the transition for the subview property.
// If the second argument (subviewID) is not specified or it is "" then the transition is added to the first argument (view) // If the second argument (subviewID) is not specified or it is "" then the transition is added to the first argument (view)
func AddTransition(view View, subviewID, tag string, animation Animation) bool { func AddTransition(view View, subviewID string, tag PropertyName, animation Animation) bool {
if tag != "" { if tag != "" {
if subviewID != "" { if subviewID != "" {
view = ViewByID(view, subviewID) view = ViewByID(view, subviewID)

View File

@ -1,7 +1,5 @@
package rui package rui
import "strings"
// Constants which describe values for view's animation events properties // Constants which describe values for view's animation events properties
const ( const (
// TransitionRunEvent is the constant for "transition-run-event" property tag. // TransitionRunEvent is the constant for "transition-run-event" property tag.
@ -20,7 +18,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(propertyName string)`, // `func(propertyName string)`,
// `func()`. // `func()`.
TransitionRunEvent = "transition-run-event" TransitionRunEvent PropertyName = "transition-run-event"
// TransitionStartEvent is the constant for "transition-start-event" property tag. // TransitionStartEvent is the constant for "transition-start-event" property tag.
// //
@ -38,7 +36,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(propertyName string)`, // `func(propertyName string)`,
// `func()`. // `func()`.
TransitionStartEvent = "transition-start-event" TransitionStartEvent PropertyName = "transition-start-event"
// TransitionEndEvent is the constant for "transition-end-event" property tag. // TransitionEndEvent is the constant for "transition-end-event" property tag.
// //
@ -56,7 +54,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(propertyName string)`, // `func(propertyName string)`,
// `func()`. // `func()`.
TransitionEndEvent = "transition-end-event" TransitionEndEvent PropertyName = "transition-end-event"
// TransitionCancelEvent is the constant for "transition-cancel-event" property tag. // TransitionCancelEvent is the constant for "transition-cancel-event" property tag.
// //
@ -76,7 +74,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(propertyName string)`, // `func(propertyName string)`,
// `func()`. // `func()`.
TransitionCancelEvent = "transition-cancel-event" TransitionCancelEvent PropertyName = "transition-cancel-event"
// AnimationStartEvent is the constant for "animation-start-event" property tag. // AnimationStartEvent is the constant for "animation-start-event" property tag.
// //
@ -95,7 +93,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(animationId string)`, // `func(animationId string)`,
// `func()`. // `func()`.
AnimationStartEvent = "animation-start-event" AnimationStartEvent PropertyName = "animation-start-event"
// AnimationEndEvent is the constant for "animation-end-event" property tag. // AnimationEndEvent is the constant for "animation-end-event" property tag.
// //
@ -114,7 +112,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(animationId string)`, // `func(animationId string)`,
// `func()`. // `func()`.
AnimationEndEvent = "animation-end-event" AnimationEndEvent PropertyName = "animation-end-event"
// AnimationCancelEvent is the constant for "animation-cancel-event" property tag. // AnimationCancelEvent is the constant for "animation-cancel-event" property tag.
// //
@ -135,7 +133,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(animationId string)`, // `func(animationId string)`,
// `func()`. // `func()`.
AnimationCancelEvent = "animation-cancel-event" AnimationCancelEvent PropertyName = "animation-cancel-event"
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag. // AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
// //
@ -154,128 +152,106 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(animationId string)`, // `func(animationId string)`,
// `func()`. // `func()`.
AnimationIterationEvent = "animation-iteration-event" AnimationIterationEvent PropertyName = "animation-iteration-event"
) )
var transitionEvents = map[string]struct{ jsEvent, jsFunc string }{ /*
TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"}, func setTransitionListener(properties Properties, tag PropertyName, value any) bool {
TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"}, if listeners, ok := valueToEventListeners[View, string](value); ok {
TransitionEndEvent: {jsEvent: "ontransitionend", jsFunc: "transitionEndEvent"}, if len(listeners) == 0 {
TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"}, properties.setRaw(tag, nil)
} } else {
properties.setRaw(tag, listeners)
func (view *viewData) setTransitionListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, string](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removeTransitionListener(tag)
} else if js, ok := transitionEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
} }
} else { return true
return false
} }
return true notCompatibleType(tag, value)
return false
} }
func (view *viewData) removeTransitionListener(tag string) { func (view *viewData) removeTransitionListener(tag PropertyName) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
if js, ok := transitionEvents[tag]; ok { if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent) view.session.removeProperty(view.htmlID(), js.jsEvent)
} }
} }
} }
func transitionEventsHtml(view View, buffer *strings.Builder) { func transitionEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range transitionEvents { for _, tag := range []PropertyName{TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent} {
if value := view.getRaw(tag); value != nil { if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 { if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(js.jsEvent) if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
} }
*/
func (view *viewData) handleTransitionEvents(tag string, data DataObject) { func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) {
if property, ok := data.PropertyValue("property"); ok { if propertyName, ok := data.PropertyValue("property"); ok {
property := PropertyName(propertyName)
if tag == TransitionEndEvent || tag == TransitionCancelEvent { if tag == TransitionEndEvent || tag == TransitionCancelEvent {
if animation, ok := view.singleTransition[property]; ok { if animation, ok := view.singleTransition[property]; ok {
delete(view.singleTransition, property) delete(view.singleTransition, property)
if animation != nil { setTransition(view, tag, animation)
view.transitions[property] = animation session := view.session
} else { session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, session))
delete(view.transitions, property)
}
view.updateTransitionCSS()
} }
} }
for _, listener := range getEventListeners[View, string](view, nil, tag) { for _, listener := range getEventListeners[View, PropertyName](view, nil, tag) {
listener(view, property) listener(view, property)
} }
} }
} }
var animationEvents = map[string]struct{ jsEvent, jsFunc string }{ /*
AnimationStartEvent: {jsEvent: "onanimationstart", jsFunc: "animationStartEvent"}, func setAnimationListener(properties Properties, tag PropertyName, value any) bool {
AnimationEndEvent: {jsEvent: "onanimationend", jsFunc: "animationEndEvent"}, if listeners, ok := valueToEventListeners[View, string](value); ok {
AnimationIterationEvent: {jsEvent: "onanimationiteration", jsFunc: "animationIterationEvent"}, if len(listeners) == 0 {
AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"}, properties.setRaw(tag, nil)
} } else {
properties.setRaw(tag, listeners)
func (view *viewData) setAnimationListener(tag string, value any) bool { }
listeners, ok := valueToEventListeners[View, string](value) return true
if !ok { }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return false
} }
if listeners == nil { func (view *viewData) removeAnimationListener(tag PropertyName) {
view.removeAnimationListener(tag)
} else if js, ok := animationEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
}
} else {
return false
}
return true
}
func (view *viewData) removeAnimationListener(tag string) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
if js, ok := animationEvents[tag]; ok { if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent) view.session.removeProperty(view.htmlID(), js.jsEvent)
} }
} }
} }
func animationEventsHtml(view View, buffer *strings.Builder) { func animationEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range animationEvents { for _, tag := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent} {
if value := view.getRaw(tag); value != nil { if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 { if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(js.jsEvent) if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
} }
*/
func (view *viewData) handleAnimationEvents(tag string, data DataObject) { func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) {
if listeners := getEventListeners[View, string](view, nil, tag); len(listeners) > 0 { if listeners := getEventListeners[View, string](view, nil, tag); len(listeners) > 0 {
id := "" id := ""
if name, ok := data.PropertyValue("name"); ok { if name, ok := data.PropertyValue("name"); ok {

View File

@ -1,6 +1,6 @@
package rui package rui
func (animation *animationData) Start(view View, listener func(view View, animation Animation, event string)) bool { func (animation *animationData) Start(view View, listener func(view View, animation Animation, event PropertyName)) bool {
if view == nil { if view == nil {
ErrorLog("nil View in animation.Start() function") ErrorLog("nil View in animation.Start() function")
return false return false
@ -19,12 +19,12 @@ func (animation *animationData) Start(view View, listener func(view View, animat
} }
} }
animation.oldListeners = map[string][]func(View, string){} animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
setListeners := func(event string, listener func(View, string)) { setListeners := func(event PropertyName, listener func(View, PropertyName)) {
var listeners []func(View, string) = nil var listeners []func(View, PropertyName) = nil
if value := view.Get(event); value != nil { if value := view.Get(event); value != nil {
if oldListeners, ok := value.([]func(View, string)); ok && len(oldListeners) > 0 { if oldListeners, ok := value.([]func(View, PropertyName)); ok && len(oldListeners) > 0 {
listeners = oldListeners listeners = oldListeners
} }
} }
@ -48,7 +48,7 @@ func (animation *animationData) Start(view View, listener func(view View, animat
func (animation *animationData) finish() { func (animation *animationData) finish() {
if animation.view != nil { if animation.view != nil {
for _, event := range []string{AnimationStartEvent, AnimationEndEvent, AnimationCancelEvent, AnimationIterationEvent} { for _, event := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationCancelEvent, AnimationIterationEvent} {
if listeners, ok := animation.oldListeners[event]; ok { if listeners, ok := animation.oldListeners[event]; ok {
animation.view.Set(event, listeners) animation.view.Set(event, listeners)
} else { } else {
@ -63,7 +63,7 @@ func (animation *animationData) finish() {
animation.view.Set(AnimationTag, "") animation.view.Set(AnimationTag, "")
} }
animation.oldListeners = map[string][]func(View, string){} animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
animation.view = nil animation.view = nil
animation.listener = nil animation.listener = nil
@ -86,13 +86,13 @@ func (animation *animationData) Resume() {
} }
} }
func (animation *animationData) onAnimationStart(view View, _ string) { func (animation *animationData) onAnimationStart(view View, _ PropertyName) {
if animation.view != nil && animation.listener != nil { if animation.view != nil && animation.listener != nil {
animation.listener(animation.view, animation, AnimationStartEvent) animation.listener(animation.view, animation, AnimationStartEvent)
} }
} }
func (animation *animationData) onAnimationEnd(view View, _ string) { func (animation *animationData) onAnimationEnd(view View, _ PropertyName) {
if animation.view != nil { if animation.view != nil {
animationView := animation.view animationView := animation.view
listener := animation.listener listener := animation.listener
@ -112,13 +112,13 @@ func (animation *animationData) onAnimationEnd(view View, _ string) {
} }
} }
func (animation *animationData) onAnimationIteration(view View, _ string) { func (animation *animationData) onAnimationIteration(view View, _ PropertyName) {
if animation.view != nil && animation.listener != nil { if animation.view != nil && animation.listener != nil {
animation.listener(animation.view, animation, AnimationIterationEvent) animation.listener(animation.view, animation, AnimationIterationEvent)
} }
} }
func (animation *animationData) onAnimationCancel(view View, _ string) { func (animation *animationData) onAnimationCancel(view View, _ PropertyName) {
if animation.view != nil { if animation.view != nil {
animationView := animation.view animationView := animation.view
listener := animation.listener listener := animation.listener

View File

@ -215,6 +215,14 @@ function appendToInnerHTML(elementId, content) {
} }
} }
function appendToInputValue(elementId, content) {
const element = document.getElementById(elementId);
if (element) {
element.value += content;
scanElementsSize();
}
}
function setDisabled(elementId, disabled) { function setDisabled(elementId, disabled) {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (element) { if (element) {

View File

@ -13,13 +13,12 @@ type audioPlayerData struct {
func NewAudioPlayer(session Session, params Params) AudioPlayer { func NewAudioPlayer(session Session, params Params) AudioPlayer {
view := new(audioPlayerData) view := new(audioPlayerData)
view.init(session) view.init(session)
view.tag = "AudioPlayer"
setInitParams(view, params) setInitParams(view, params)
return view return view
} }
func newAudioPlayer(session Session) View { func newAudioPlayer(session Session) View {
return NewAudioPlayer(session, nil) return new(audioPlayerData) // NewAudioPlayer(session, nil)
} }
func (player *audioPlayerData) init(session Session) { func (player *audioPlayerData) init(session Session) {
@ -27,10 +26,6 @@ func (player *audioPlayerData) init(session Session) {
player.tag = "AudioPlayer" player.tag = "AudioPlayer"
} }
func (player *audioPlayerData) String() string {
return getViewString(player, nil)
}
func (player *audioPlayerData) htmlTag() string { func (player *audioPlayerData) htmlTag() string {
return "audio" return "audio"
} }

View File

@ -78,7 +78,7 @@ type BackgroundElement interface {
} }
type backgroundElement struct { type backgroundElement struct {
propertyList dataProperty
} }
type backgroundImage struct { type backgroundImage struct {
@ -91,24 +91,16 @@ func createBackground(obj DataObject) BackgroundElement {
switch obj.Tag() { switch obj.Tag() {
case "image": case "image":
image := new(backgroundImage) result = NewBackgroundImage(nil)
image.properties = map[string]any{}
result = image
case "linear-gradient": case "linear-gradient":
gradient := new(backgroundLinearGradient) result = NewBackgroundLinearGradient(nil)
gradient.properties = map[string]any{}
result = gradient
case "radial-gradient": case "radial-gradient":
gradient := new(backgroundRadialGradient) result = NewBackgroundRadialGradient(nil)
gradient.properties = map[string]any{}
result = gradient
case "conic-gradient": case "conic-gradient":
gradient := new(backgroundConicGradient) result = NewBackgroundConicGradient(nil)
gradient.properties = map[string]any{}
result = gradient
default: default:
return nil return nil
@ -118,7 +110,7 @@ func createBackground(obj DataObject) BackgroundElement {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
if node := obj.Property(i); node.Type() == TextNode { if node := obj.Property(i); node.Type() == TextNode {
if value := node.Text(); value != "" { if value := node.Text(); value != "" {
result.Set(node.Tag(), value) result.Set(PropertyName(node.Tag()), value)
} }
} }
} }
@ -129,13 +121,21 @@ func createBackground(obj DataObject) BackgroundElement {
// NewBackgroundImage creates the new background image // NewBackgroundImage creates the new background image
func NewBackgroundImage(params Params) BackgroundElement { func NewBackgroundImage(params Params) BackgroundElement {
result := new(backgroundImage) result := new(backgroundImage)
result.properties = map[string]any{} result.init()
for tag, value := range params { for tag, value := range params {
result.Set(tag, value) result.Set(tag, value)
} }
return result return result
} }
func (image *backgroundImage) init() {
image.backgroundElement.init()
image.normalize = normalizeBackgroundImageTag
image.supportedProperties = []PropertyName{
Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign, backgroundFit, Source,
}
}
func (image *backgroundImage) Tag() string { func (image *backgroundImage) Tag() string {
return "image" return "image"
} }
@ -148,8 +148,8 @@ func (image *backgroundImage) Clone() BackgroundElement {
return result return result
} }
func (image *backgroundImage) normalizeTag(tag string) string { func normalizeBackgroundImageTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case "source": case "source":
tag = Source tag = Source
@ -167,21 +167,6 @@ func (image *backgroundImage) normalizeTag(tag string) string {
return tag return tag
} }
func (image *backgroundImage) Set(tag string, value any) bool {
tag = image.normalizeTag(tag)
switch tag {
case Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign,
backgroundFit, Source:
return image.backgroundElement.Set(tag, value)
}
return false
}
func (image *backgroundImage) Get(tag string) any {
return image.backgroundElement.Get(image.normalizeTag(tag))
}
func (image *backgroundImage) cssStyle(session Session) string { func (image *backgroundImage) cssStyle(session Session) string {
if src, ok := imageProperty(image, Source, session); ok && src != "" { if src, ok := imageProperty(image, Source, session); ok && src != "" {
buffer := allocStringBuilder() buffer := allocStringBuilder()
@ -252,7 +237,7 @@ func (image *backgroundImage) cssStyle(session Session) string {
} }
func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) { func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) {
image.writeToBuffer(buffer, indent, image.Tag(), []string{ image.writeToBuffer(buffer, indent, image.Tag(), []PropertyName{
Source, Source,
Width, Width,
Height, Height,
@ -267,3 +252,76 @@ func (image *backgroundImage) writeString(buffer *strings.Builder, indent string
func (image *backgroundImage) String() string { func (image *backgroundImage) String() string {
return runStringWriter(image) return runStringWriter(image)
} }
func setBackgroundProperty(properties Properties, value any) []PropertyName {
background := []BackgroundElement{}
error := func() []PropertyName {
notCompatibleType(Background, value)
return nil
}
switch value := value.(type) {
case BackgroundElement:
background = []BackgroundElement{value}
case []BackgroundElement:
background = value
case []DataValue:
for _, el := range value {
if el.IsObject() {
if element := createBackground(el.Object()); element != nil {
background = append(background, element)
} else {
return error()
}
} else if obj := ParseDataText(el.Value()); obj != nil {
if element := createBackground(obj); element != nil {
background = append(background, element)
} else {
return error()
}
} else {
return error()
}
}
case DataObject:
if element := createBackground(value); element != nil {
background = []BackgroundElement{element}
} else {
return error()
}
case []DataObject:
for _, obj := range value {
if element := createBackground(obj); element != nil {
background = append(background, element)
} else {
return error()
}
}
case string:
if obj := ParseDataText(value); obj != nil {
if element := createBackground(obj); element != nil {
background = []BackgroundElement{element}
} else {
return error()
}
} else {
return error()
}
}
if len(background) > 0 {
properties.setRaw(Background, background)
} else if properties.getRaw(Background) != nil {
properties.setRaw(Background, nil)
} else {
return []PropertyName{}
}
return []PropertyName{Background}
}

View File

@ -21,7 +21,7 @@ type BackgroundGradientAngle struct {
// NewBackgroundConicGradient creates the new background conic gradient // NewBackgroundConicGradient creates the new background conic gradient
func NewBackgroundConicGradient(params Params) BackgroundElement { func NewBackgroundConicGradient(params Params) BackgroundElement {
result := new(backgroundConicGradient) result := new(backgroundConicGradient)
result.properties = map[string]any{} result.init()
for tag, value := range params { for tag, value := range params {
result.Set(tag, value) result.Set(tag, value)
} }
@ -48,7 +48,6 @@ func (point *BackgroundGradientAngle) String() string {
case AngleUnit: case AngleUnit:
result += " " + value.String() result += " " + value.String()
} }
} }
@ -115,6 +114,15 @@ func (point *BackgroundGradientAngle) cssString(session Session, buffer *strings
} }
} }
func (gradient *backgroundConicGradient) init() {
gradient.backgroundElement.init()
gradient.normalize = normalizeConicGradientTag
gradient.set = backgroundConicGradientSet
gradient.supportedProperties = []PropertyName{
CenterX, CenterY, Repeating, From, Gradient,
}
}
func (gradient *backgroundConicGradient) Tag() string { func (gradient *backgroundConicGradient) Tag() string {
return "conic-gradient" return "conic-gradient"
} }
@ -127,8 +135,8 @@ func (image *backgroundConicGradient) Clone() BackgroundElement {
return result return result
} }
func (gradient *backgroundConicGradient) normalizeTag(tag string) string { func normalizeConicGradientTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case "x-center": case "x-center":
tag = CenterX tag = CenterX
@ -140,18 +148,50 @@ func (gradient *backgroundConicGradient) normalizeTag(tag string) string {
return tag return tag
} }
func (gradient *backgroundConicGradient) Set(tag string, value any) bool { func backgroundConicGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
tag = gradient.normalizeTag(tag)
switch tag { switch tag {
case CenterX, CenterY, Repeating, From:
return gradient.propertyList.Set(tag, value)
case Gradient: case Gradient:
return gradient.setGradient(value) switch value := value.(type) {
case string:
if value == "" {
return propertiesRemove(properties, tag)
}
if strings.Contains(value, ",") || strings.Contains(value, " ") {
if vector := parseGradientText(value); vector != nil {
properties.setRaw(Gradient, vector)
return []PropertyName{tag}
}
} else if isConstantName(value) {
properties.setRaw(Gradient, value)
return []PropertyName{tag}
}
ErrorLogF(`Invalid conic gradient: "%s"`, value)
case []BackgroundGradientAngle:
count := len(value)
if count < 2 {
ErrorLog("The gradient must contain at least 2 points")
return nil
}
for i, point := range value {
if point.Color == nil {
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
return nil
}
}
properties.setRaw(Gradient, value)
return []PropertyName{tag}
default:
notCompatibleType(tag, value)
}
return nil
} }
ErrorLogF(`"%s" property is not supported by BackgroundConicGradient`, tag) return propertiesSet(properties, tag, value)
return false
} }
func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) { func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) {
@ -216,57 +256,6 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
} }
return vector return vector
} }
func (gradient *backgroundConicGradient) setGradient(value any) bool {
if value == nil {
delete(gradient.properties, Gradient)
return true
}
switch value := value.(type) {
case string:
if value == "" {
delete(gradient.properties, Gradient)
return true
}
if strings.Contains(value, ",") || strings.Contains(value, " ") {
if vector := gradient.parseGradientText(value); vector != nil {
gradient.properties[Gradient] = vector
return true
}
return false
} else if value[0] == '@' {
gradient.properties[Gradient] = value
return true
}
ErrorLogF(`Invalid conic gradient: "%s"`, value)
return false
case []BackgroundGradientAngle:
count := len(value)
if count < 2 {
ErrorLog("The gradient must contain at least 2 points")
return false
}
for i, point := range value {
if point.Color == nil {
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
return false
}
}
gradient.properties[Gradient] = value
return true
}
return false
}
func (gradient *backgroundConicGradient) Get(tag string) any {
return gradient.backgroundElement.Get(gradient.normalizeTag(tag))
}
func (gradient *backgroundConicGradient) cssStyle(session Session) string { func (gradient *backgroundConicGradient) cssStyle(session Session) string {
points := []BackgroundGradientAngle{} points := []BackgroundGradientAngle{}
@ -339,7 +328,7 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string {
} }
func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) { func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) {
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{ gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
Gradient, Gradient,
CenterX, CenterX,
CenterY, CenterY,

View File

@ -72,7 +72,7 @@ type backgroundRadialGradient struct {
// NewBackgroundLinearGradient creates the new background linear gradient // NewBackgroundLinearGradient creates the new background linear gradient
func NewBackgroundLinearGradient(params Params) BackgroundElement { func NewBackgroundLinearGradient(params Params) BackgroundElement {
result := new(backgroundLinearGradient) result := new(backgroundLinearGradient)
result.properties = map[string]any{} result.init()
for tag, value := range params { for tag, value := range params {
result.Set(tag, value) result.Set(tag, value)
} }
@ -82,14 +82,14 @@ func NewBackgroundLinearGradient(params Params) BackgroundElement {
// NewBackgroundRadialGradient creates the new background radial gradient // NewBackgroundRadialGradient creates the new background radial gradient
func NewBackgroundRadialGradient(params Params) BackgroundElement { func NewBackgroundRadialGradient(params Params) BackgroundElement {
result := new(backgroundRadialGradient) result := new(backgroundRadialGradient)
result.properties = map[string]any{} result.init()
for tag, value := range params { for tag, value := range params {
result.Set(tag, value) result.Set(tag, value)
} }
return result return result
} }
func (gradient *backgroundGradient) parseGradientText(value string) []BackgroundGradientPoint { func parseGradientText(value string) []BackgroundGradientPoint {
elements := strings.Split(value, ",") elements := strings.Split(value, ",")
count := len(elements) count := len(elements)
if count < 2 { if count < 2 {
@ -107,31 +107,31 @@ func (gradient *backgroundGradient) parseGradientText(value string) []Background
return points return points
} }
func (gradient *backgroundGradient) Set(tag string, value any) bool { func backgroundGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag = strings.ToLower(tag); tag { switch tag {
case Repeating: case Repeating:
return gradient.setBoolProperty(tag, value) return setBoolProperty(properties, tag, value)
case Gradient: case Gradient:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if value != "" { if value != "" {
if strings.Contains(value, " ") || strings.Contains(value, ",") { if strings.Contains(value, " ") || strings.Contains(value, ",") {
if points := gradient.parseGradientText(value); len(points) >= 2 { if points := parseGradientText(value); len(points) >= 2 {
gradient.properties[Gradient] = points properties.setRaw(Gradient, points)
return true return []PropertyName{tag}
} }
} else if value[0] == '@' { } else if value[0] == '@' {
gradient.properties[Gradient] = value properties.setRaw(Gradient, value)
return true return []PropertyName{tag}
} }
} }
case []BackgroundGradientPoint: case []BackgroundGradientPoint:
if len(value) >= 2 { if len(value) >= 2 {
gradient.properties[Gradient] = value properties.setRaw(Gradient, value)
return true return []PropertyName{tag}
} }
case []Color: case []Color:
@ -141,8 +141,8 @@ func (gradient *backgroundGradient) Set(tag string, value any) bool {
for i, color := range value { for i, color := range value {
points[i].Color = color points[i].Color = color
} }
gradient.properties[Gradient] = points properties.setRaw(Gradient, points)
return true return []PropertyName{tag}
} }
case []GradientPoint: case []GradientPoint:
@ -153,17 +153,17 @@ func (gradient *backgroundGradient) Set(tag string, value any) bool {
points[i].Color = point.Color points[i].Color = point.Color
points[i].Pos = Percent(point.Offset * 100) points[i].Pos = Percent(point.Offset * 100)
} }
gradient.properties[Gradient] = points properties.setRaw(Gradient, points)
return true return []PropertyName{tag}
} }
} }
ErrorLogF("Invalid gradient %v", value) ErrorLogF("Invalid gradient %v", value)
return false return nil
} }
ErrorLogF("Property %s is not supported by a background gradient", tag) ErrorLogF("Property %s is not supported by a background gradient", tag)
return false return nil
} }
func (point *BackgroundGradientPoint) setValue(text string) bool { func (point *BackgroundGradientPoint) setValue(text string) bool {
@ -266,7 +266,7 @@ func (gradient *backgroundGradient) writeGradient(session Session, buffer *strin
case string: case string:
if value != "" && value[0] == '@' { if value != "" && value[0] == '@' {
if text, ok := session.Constant(value[1:]); ok { if text, ok := session.Constant(value[1:]); ok {
points = gradient.parseGradientText(text) points = parseGradientText(text)
} }
} }
@ -312,6 +312,13 @@ func (gradient *backgroundGradient) writeGradient(session Session, buffer *strin
return false return false
} }
func (gradient *backgroundLinearGradient) init() {
gradient.backgroundElement.init()
gradient.set = backgroundLinearGradientSet
gradient.supportedProperties = append(gradient.supportedProperties, Direction)
}
func (gradient *backgroundLinearGradient) Tag() string { func (gradient *backgroundLinearGradient) Tag() string {
return "linear-gradient" return "linear-gradient"
} }
@ -324,26 +331,26 @@ func (image *backgroundLinearGradient) Clone() BackgroundElement {
return result return result
} }
func (gradient *backgroundLinearGradient) Set(tag string, value any) bool { func backgroundLinearGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
if strings.ToLower(tag) == Direction { if tag == Direction {
switch value := value.(type) { switch value := value.(type) {
case AngleUnit: case AngleUnit:
gradient.properties[Direction] = value properties.setRaw(Direction, value)
return true return []PropertyName{tag}
case string: case string:
if gradient.setSimpleProperty(tag, value) { if setSimpleProperty(properties, tag, value) {
return true return []PropertyName{tag}
} }
if angle, ok := StringToAngleUnit(value); ok { if angle, ok := StringToAngleUnit(value); ok {
gradient.properties[Direction] = angle properties.setRaw(Direction, angle)
return true return []PropertyName{tag}
} }
} }
return gradient.setEnumProperty(tag, value, enumProperties[Direction].values) return setEnumProperty(properties, tag, value, enumProperties[Direction].values)
} }
return gradient.backgroundGradient.Set(tag, value) return backgroundGradientSet(properties, tag, value)
} }
func (gradient *backgroundLinearGradient) cssStyle(session Session) string { func (gradient *backgroundLinearGradient) cssStyle(session Session) string {
@ -400,7 +407,7 @@ func (gradient *backgroundLinearGradient) cssStyle(session Session) string {
} }
func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, indent string) { func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, indent string) {
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{ gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
Gradient, Gradient,
Repeating, Repeating,
Direction, Direction,
@ -411,6 +418,15 @@ func (gradient *backgroundLinearGradient) String() string {
return runStringWriter(gradient) return runStringWriter(gradient)
} }
func (gradient *backgroundRadialGradient) init() {
gradient.backgroundElement.init()
gradient.normalize = normalizeRadialGradientTag
gradient.set = backgroundRadialGradientSet
gradient.supportedProperties = append(gradient.supportedProperties, []PropertyName{
RadialGradientRadius, RadialGradientShape, CenterX, CenterY,
}...)
}
func (gradient *backgroundRadialGradient) Tag() string { func (gradient *backgroundRadialGradient) Tag() string {
return "radial-gradient" return "radial-gradient"
} }
@ -423,8 +439,8 @@ func (image *backgroundRadialGradient) Clone() BackgroundElement {
return result return result
} }
func (gradient *backgroundRadialGradient) normalizeTag(tag string) string { func normalizeRadialGradientTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case Radius: case Radius:
tag = RadialGradientRadius tag = RadialGradientRadius
@ -442,83 +458,75 @@ func (gradient *backgroundRadialGradient) normalizeTag(tag string) string {
return tag return tag
} }
func (gradient *backgroundRadialGradient) Set(tag string, value any) bool { func backgroundRadialGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
tag = gradient.normalizeTag(tag)
switch tag { switch tag {
case RadialGradientRadius: case RadialGradientRadius:
switch value := value.(type) { switch value := value.(type) {
case []SizeUnit: case []SizeUnit:
switch len(value) { switch len(value) {
case 0: case 0:
delete(gradient.properties, RadialGradientRadius) properties.setRaw(RadialGradientRadius, nil)
return true
case 1: case 1:
if value[0].Type == Auto { if value[0].Type == Auto {
delete(gradient.properties, RadialGradientRadius) properties.setRaw(RadialGradientRadius, nil)
} else { } else {
gradient.properties[RadialGradientRadius] = value[0] properties.setRaw(RadialGradientRadius, value[0])
} }
return true
default: default:
gradient.properties[RadialGradientRadius] = value properties.setRaw(RadialGradientRadius, value)
return true
} }
return []PropertyName{tag}
case []any: case []any:
switch len(value) { switch len(value) {
case 0: case 0:
delete(gradient.properties, RadialGradientRadius) properties.setRaw(RadialGradientRadius, nil)
return true return []PropertyName{tag}
case 1: case 1:
return gradient.Set(RadialGradientRadius, value[0]) return backgroundRadialGradientSet(properties, RadialGradientRadius, value[0])
default: default:
gradient.properties[RadialGradientRadius] = value properties.setRaw(RadialGradientRadius, value)
return true return []PropertyName{tag}
} }
case string: case string:
if gradient.setSimpleProperty(RadialGradientRadius, value) { if setSimpleProperty(properties, RadialGradientRadius, value) {
return true return []PropertyName{tag}
} }
if size, err := stringToSizeUnit(value); err == nil { if size, err := stringToSizeUnit(value); err == nil {
if size.Type == Auto { if size.Type == Auto {
delete(gradient.properties, RadialGradientRadius) properties.setRaw(RadialGradientRadius, nil)
} else { } else {
gradient.properties[RadialGradientRadius] = size properties.setRaw(RadialGradientRadius, size)
} }
return true return []PropertyName{tag}
} }
return gradient.setEnumProperty(RadialGradientRadius, value, enumProperties[RadialGradientRadius].values) return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values)
case SizeUnit: case SizeUnit:
if value.Type == Auto { if value.Type == Auto {
delete(gradient.properties, RadialGradientRadius) properties.setRaw(RadialGradientRadius, nil)
} else { } else {
gradient.properties[RadialGradientRadius] = value properties.setRaw(RadialGradientRadius, value)
} }
return true return []PropertyName{tag}
case int: case int:
n := value return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values)
if n >= 0 && n < len(enumProperties[RadialGradientRadius].values) {
return gradient.propertyList.Set(RadialGradientRadius, value)
}
} }
ErrorLogF(`Invalid value of "%s" property: %v`, tag, value) ErrorLogF(`Invalid value of "%s" property: %v`, tag, value)
return nil
case RadialGradientShape, CenterX, CenterY: case RadialGradientShape, CenterX, CenterY:
return gradient.propertyList.Set(tag, value) return propertiesSet(properties, tag, value)
} }
return gradient.backgroundGradient.Set(tag, value) return backgroundGradientSet(properties, tag, value)
}
func (gradient *backgroundRadialGradient) Get(tag string) any {
return gradient.backgroundGradient.Get(gradient.normalizeTag(tag))
} }
func (gradient *backgroundRadialGradient) cssStyle(session Session) string { func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
@ -652,7 +660,7 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
return buffer.String() return buffer.String()
} }
func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) { func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) {
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{ gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
Gradient, Gradient,
CenterX, CenterX,
CenterY, CenterY,

458
border.go
View File

@ -38,7 +38,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border.
// `4`(`DoubleLine`) or "double" - Double line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border.
LeftStyle = "left-style" LeftStyle PropertyName = "left-style"
// RightStyle is the constant for "right-style" property tag. // RightStyle is the constant for "right-style" property tag.
// //
@ -53,7 +53,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border.
// `4`(`DoubleLine`) or "double" - Double line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border.
RightStyle = "right-style" RightStyle PropertyName = "right-style"
// TopStyle is the constant for "top-style" property tag. // TopStyle is the constant for "top-style" property tag.
// //
@ -68,7 +68,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border.
// `4`(`DoubleLine`) or "double" - Double line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border.
TopStyle = "top-style" TopStyle PropertyName = "top-style"
// BottomStyle is the constant for "bottom-style" property tag. // BottomStyle is the constant for "bottom-style" property tag.
// //
@ -83,7 +83,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border.
// `4`(`DoubleLine`) or "double" - Double line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border.
BottomStyle = "bottom-style" BottomStyle PropertyName = "bottom-style"
// LeftWidth is the constant for "left-width" property tag. // LeftWidth is the constant for "left-width" property tag.
// //
@ -94,7 +94,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
LeftWidth = "left-width" LeftWidth PropertyName = "left-width"
// RightWidth is the constant for "right-width" property tag. // RightWidth is the constant for "right-width" property tag.
// //
@ -105,7 +105,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RightWidth = "right-width" RightWidth PropertyName = "right-width"
// TopWidth is the constant for "top-width" property tag. // TopWidth is the constant for "top-width" property tag.
// //
@ -116,7 +116,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopWidth = "top-width" TopWidth PropertyName = "top-width"
// BottomWidth is the constant for "bottom-width" property tag. // BottomWidth is the constant for "bottom-width" property tag.
// //
@ -127,7 +127,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomWidth = "bottom-width" BottomWidth PropertyName = "bottom-width"
// LeftColor is the constant for "left-color" property tag. // LeftColor is the constant for "left-color" property tag.
// //
@ -138,7 +138,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
LeftColor = "left-color" LeftColor PropertyName = "left-color"
// RightColor is the constant for "right-color" property tag. // RightColor is the constant for "right-color" property tag.
// //
@ -149,7 +149,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
RightColor = "right-color" RightColor PropertyName = "right-color"
// TopColor is the constant for "top-color" property tag. // TopColor is the constant for "top-color" property tag.
// //
@ -160,7 +160,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
TopColor = "top-color" TopColor PropertyName = "top-color"
// BottomColor is the constant for "bottom-color" property tag. // BottomColor is the constant for "bottom-color" property tag.
// //
@ -171,7 +171,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
BottomColor = "bottom-color" BottomColor PropertyName = "bottom-color"
) )
// BorderProperty is the interface of a view border data // BorderProperty is the interface of a view border data
@ -183,7 +183,7 @@ type BorderProperty interface {
// ViewBorders returns top, right, bottom and left borders information all together // ViewBorders returns top, right, bottom and left borders information all together
ViewBorders(session Session) ViewBorders ViewBorders(session Session) ViewBorders
delete(tag string) deleteTag(tag PropertyName) bool
cssStyle(builder cssBuilder, session Session) cssStyle(builder cssBuilder, session Session)
cssWidth(builder cssBuilder, session Session) cssWidth(builder cssBuilder, session Session)
cssColor(builder cssBuilder, session Session) cssColor(builder cssBuilder, session Session)
@ -193,12 +193,12 @@ type BorderProperty interface {
} }
type borderProperty struct { type borderProperty struct {
propertyList dataProperty
} }
func newBorderProperty(value any) BorderProperty { func newBorderProperty(value any) BorderProperty {
border := new(borderProperty) border := new(borderProperty)
border.properties = map[string]any{} border.init()
if value != nil { if value != nil {
switch value := value.(type) { switch value := value.(type) {
@ -270,9 +270,10 @@ func newBorderProperty(value any) BorderProperty {
// "width" (Width). Determines the line thickness (SizeUnit). // "width" (Width). Determines the line thickness (SizeUnit).
func NewBorder(params Params) BorderProperty { func NewBorder(params Params) BorderProperty {
border := new(borderProperty) border := new(borderProperty)
border.properties = map[string]any{} border.init()
if params != nil { if params != nil {
for _, tag := range []string{Style, Width, ColorTag, Left, Right, Top, Bottom, for _, tag := range []PropertyName{Style, Width, ColorTag, Left, Right, Top, Bottom,
LeftStyle, RightStyle, TopStyle, BottomStyle, LeftStyle, RightStyle, TopStyle, BottomStyle,
LeftWidth, RightWidth, TopWidth, BottomWidth, LeftWidth, RightWidth, TopWidth, BottomWidth,
LeftColor, RightColor, TopColor, BottomColor} { LeftColor, RightColor, TopColor, BottomColor} {
@ -284,8 +285,37 @@ func NewBorder(params Params) BorderProperty {
return border return border
} }
func (border *borderProperty) normalizeTag(tag string) string { func (border *borderProperty) init() {
tag = strings.ToLower(tag) border.dataProperty.init()
border.normalize = normalizeBorderTag
border.get = borderGet
border.set = borderSet
border.remove = borderRemove
border.supportedProperties = []PropertyName{
Left,
Right,
Top,
Bottom,
Style,
LeftStyle,
RightStyle,
TopStyle,
BottomStyle,
Width,
LeftWidth,
RightWidth,
TopWidth,
BottomWidth,
ColorTag,
LeftColor,
RightColor,
TopColor,
BottomColor,
}
}
func normalizeBorderTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag { switch tag {
case BorderLeft, CellBorderLeft: case BorderLeft, CellBorderLeft:
return Left return Left
@ -352,23 +382,23 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
buffer.WriteString("_{ ") buffer.WriteString("_{ ")
comma := false comma := false
write := func(tag string, value any) { write := func(tag PropertyName, value any) {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, BorderStyle, value, indent) writePropertyValue(buffer, BorderStyle, value, indent)
comma = true comma = true
} }
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := border.properties[tag]; ok { if value, ok := border.properties[tag]; ok {
write(tag, value) write(tag, value)
} }
} }
for _, side := range []string{Top, Right, Bottom, Left} { for _, side := range []PropertyName{Top, Right, Bottom, Left} {
style, okStyle := border.properties[side+"-"+Style] style, okStyle := border.properties[side+"-"+Style]
width, okWidth := border.properties[side+"-"+Width] width, okWidth := border.properties[side+"-"+Width]
color, okColor := border.properties[side+"-"+ColorTag] color, okColor := border.properties[side+"-"+ColorTag]
@ -378,7 +408,7 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
comma = false comma = false
} }
buffer.WriteString(side) buffer.WriteString(string(side))
buffer.WriteString(" = _{ ") buffer.WriteString(" = _{ ")
if okStyle { if okStyle {
write(Style, style) write(Style, style)
@ -401,164 +431,96 @@ func (border *borderProperty) String() string {
return runStringWriter(border) return runStringWriter(border)
} }
func (border *borderProperty) setSingleBorderObject(prefix string, obj DataObject) bool {
result := true
if text, ok := obj.PropertyValue(Style); ok {
if !border.setEnumProperty(prefix+"-style", text, enumProperties[BorderStyle].values) {
result = false
}
}
if text, ok := obj.PropertyValue(ColorTag); ok {
if !border.setColorProperty(prefix+"-color", text) {
result = false
}
}
if text, ok := obj.PropertyValue("width"); ok {
if !border.setSizeProperty(prefix+"-width", text) {
result = false
}
}
return result
}
func (border *borderProperty) setBorderObject(obj DataObject) bool { func (border *borderProperty) setBorderObject(obj DataObject) bool {
result := true result := true
for i := 0; i < obj.PropertyCount(); i++ {
for _, side := range []string{Top, Right, Bottom, Left} { if node := obj.Property(i); node != nil {
if node := obj.PropertyByTag(side); node != nil { tag := PropertyName(node.Tag())
if node.Type() == ObjectNode { switch node.Type() {
if !border.setSingleBorderObject(side, node.Object()) { case TextNode:
if borderSet(border, tag, node.Text()) == nil {
result = false result = false
} }
} else {
notCompatibleType(side, node)
result = false
}
}
}
if text, ok := obj.PropertyValue(Style); ok { case ObjectNode:
values := split4Values(text) if borderSet(border, tag, node.Object()) == nil {
styles := enumProperties[BorderStyle].values
switch len(values) {
case 1:
if !border.setEnumProperty(Style, values[0], styles) {
result = false
}
case 4:
for n, tag := range [4]string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
if !border.setEnumProperty(tag, values[n], styles) {
result = false result = false
} }
}
default: default:
notCompatibleType(Style, text) result = false
}
} else {
result = false result = false
} }
} }
if text, ok := obj.PropertyValue(ColorTag); ok {
values := split4Values(text)
switch len(values) {
case 1:
if !border.setColorProperty(ColorTag, values[0]) {
return false
}
case 4:
for n, tag := range [4]string{TopColor, RightColor, BottomColor, LeftColor} {
if !border.setColorProperty(tag, values[n]) {
return false
}
}
default:
notCompatibleType(ColorTag, text)
result = false
}
}
if text, ok := obj.PropertyValue(Width); ok {
values := split4Values(text)
switch len(values) {
case 1:
if !border.setSizeProperty(Width, values[0]) {
result = false
}
case 4:
for n, tag := range [4]string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
if !border.setSizeProperty(tag, values[n]) {
result = false
}
}
default:
notCompatibleType(Width, text)
result = false
}
}
return result return result
} }
func (border *borderProperty) Remove(tag string) { func borderRemove(properties Properties, tag PropertyName) []PropertyName {
tag = border.normalizeTag(tag) result := []PropertyName{}
removeTag := func(t PropertyName) {
if properties.getRaw(t) != nil {
properties.setRaw(t, nil)
result = append(result, t)
}
}
switch tag { switch tag {
case Style: case Style:
for _, t := range []string{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} { for _, t := range []PropertyName{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
delete(border.properties, t) removeTag(t)
} }
case Width: case Width:
for _, t := range []string{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} { for _, t := range []PropertyName{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
delete(border.properties, t) removeTag(t)
} }
case ColorTag: case ColorTag:
for _, t := range []string{tag, TopColor, RightColor, BottomColor, LeftColor} { for _, t := range []PropertyName{tag, TopColor, RightColor, BottomColor, LeftColor} {
delete(border.properties, t) removeTag(t)
} }
case Left, Right, Top, Bottom: case Left, Right, Top, Bottom:
border.Remove(tag + "-style") removeTag(tag + "-style")
border.Remove(tag + "-width") removeTag(tag + "-width")
border.Remove(tag + "-color") removeTag(tag + "-color")
case LeftStyle, RightStyle, TopStyle, BottomStyle: case LeftStyle, RightStyle, TopStyle, BottomStyle:
delete(border.properties, tag) removeTag(tag)
if style, ok := border.properties[Style]; ok && style != nil { if style := properties.getRaw(Style); style != nil {
for _, t := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} { for _, t := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
if t != tag { if t != tag {
if _, ok := border.properties[t]; !ok { if properties.getRaw(t) == nil {
border.properties[t] = style properties.setRaw(t, style)
result = append(result, t)
} }
} }
} }
} }
case LeftWidth, RightWidth, TopWidth, BottomWidth: case LeftWidth, RightWidth, TopWidth, BottomWidth:
delete(border.properties, tag) removeTag(tag)
if width, ok := border.properties[Width]; ok && width != nil { if width := properties.getRaw(Width); width != nil {
for _, t := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} { for _, t := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
if t != tag { if t != tag {
if _, ok := border.properties[t]; !ok { if properties.getRaw(t) == nil {
border.properties[t] = width properties.setRaw(t, width)
result = append(result, t)
} }
} }
} }
} }
case LeftColor, RightColor, TopColor, BottomColor: case LeftColor, RightColor, TopColor, BottomColor:
delete(border.properties, tag) removeTag(tag)
if color, ok := border.properties[ColorTag]; ok && color != nil { if color := properties.getRaw(ColorTag); color != nil {
for _, t := range []string{TopColor, RightColor, BottomColor, LeftColor} { for _, t := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
if t != tag { if t != tag {
if _, ok := border.properties[t]; !ok { if properties.getRaw(t) == nil {
border.properties[t] = color properties.setRaw(t, color)
result = append(result, t)
} }
} }
} }
@ -567,80 +529,118 @@ func (border *borderProperty) Remove(tag string) {
default: default:
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag) ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
} }
return result
} }
func (border *borderProperty) Set(tag string, value any) bool { func borderSet(properties Properties, tag PropertyName, value any) []PropertyName {
if value == nil {
border.Remove(tag)
return true
}
tag = border.normalizeTag(tag) setSingleBorderObject := func(prefix PropertyName, obj DataObject) []PropertyName {
result := []PropertyName{}
if text, ok := obj.PropertyValue(string(Style)); ok {
props := setEnumProperty(properties, prefix+"-style", text, enumProperties[BorderStyle].values)
if props == nil {
return nil
}
result = append(result, props...)
}
if text, ok := obj.PropertyValue(string(ColorTag)); ok {
props := setColorProperty(properties, prefix+"-color", text)
if props == nil && len(result) == 0 {
return nil
}
result = append(result, props...)
}
if text, ok := obj.PropertyValue("width"); ok {
props := setSizeProperty(properties, prefix+"-width", text)
if props == nil && len(result) == 0 {
return nil
}
result = append(result, props...)
}
if len(result) > 0 {
result = append(result, prefix)
}
return result
}
switch tag { switch tag {
case Style: case Style:
if border.setEnumProperty(Style, value, enumProperties[BorderStyle].values) { if result := setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values); result != nil {
for _, side := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} { for _, side := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
delete(border.properties, side) if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
} }
return true return result
} }
case Width: case Width:
if border.setSizeProperty(Width, value) { if result := setSizeProperty(properties, Width, value); result != nil {
for _, side := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} { for _, side := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
delete(border.properties, side) if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
} }
return true return result
} }
case ColorTag: case ColorTag:
if border.setColorProperty(ColorTag, value) { if result := setColorProperty(properties, ColorTag, value); result != nil {
for _, side := range []string{TopColor, RightColor, BottomColor, LeftColor} { for _, side := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
delete(border.properties, side) if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
} }
return true return result
} }
case LeftStyle, RightStyle, TopStyle, BottomStyle: case LeftStyle, RightStyle, TopStyle, BottomStyle:
return border.setEnumProperty(tag, value, enumProperties[BorderStyle].values) return setEnumProperty(properties, tag, value, enumProperties[BorderStyle].values)
case LeftWidth, RightWidth, TopWidth, BottomWidth: case LeftWidth, RightWidth, TopWidth, BottomWidth:
return border.setSizeProperty(tag, value) return setSizeProperty(properties, tag, value)
case LeftColor, RightColor, TopColor, BottomColor: case LeftColor, RightColor, TopColor, BottomColor:
return border.setColorProperty(tag, value) return setColorProperty(properties, tag, value)
case Left, Right, Top, Bottom: case Left, Right, Top, Bottom:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if obj := ParseDataText(value); obj != nil { if obj := ParseDataText(value); obj != nil {
return border.setSingleBorderObject(tag, obj) return setSingleBorderObject(tag, obj)
} }
case DataObject: case DataObject:
return border.setSingleBorderObject(tag, value) return setSingleBorderObject(tag, value)
case BorderProperty: case BorderProperty:
result := []PropertyName{}
styleTag := tag + "-" + Style styleTag := tag + "-" + Style
if style := value.Get(styleTag); value != nil { if style := value.Get(styleTag); value != nil {
border.properties[styleTag] = style properties.setRaw(styleTag, style)
result = append(result, styleTag)
} }
colorTag := tag + "-" + ColorTag colorTag := tag + "-" + ColorTag
if color := value.Get(colorTag); value != nil { if color := value.Get(colorTag); value != nil {
border.properties[colorTag] = color properties.setRaw(colorTag, color)
result = append(result, colorTag)
} }
widthTag := tag + "-" + Width widthTag := tag + "-" + Width
if width := value.Get(widthTag); value != nil { if width := value.Get(widthTag); value != nil {
border.properties[widthTag] = width properties.setRaw(widthTag, width)
result = append(result, widthTag)
} }
return true return result
case ViewBorder: case ViewBorder:
border.properties[tag+"-"+Style] = value.Style properties.setRaw(tag+"-"+Style, value.Style)
border.properties[tag+"-"+Width] = value.Width properties.setRaw(tag+"-"+Width, value.Width)
border.properties[tag+"-"+ColorTag] = value.Color properties.setRaw(tag+"-"+ColorTag, value.Color)
return true return []PropertyName{tag + "-" + Style, tag + "-" + Width, tag + "-" + ColorTag}
} }
fallthrough fallthrough
@ -648,105 +648,119 @@ func (border *borderProperty) Set(tag string, value any) bool {
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag) ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
} }
return false return nil
} }
func (border *borderProperty) Get(tag string) any { func borderGet(properties Properties, tag PropertyName) any {
tag = border.normalizeTag(tag) if result := properties.getRaw(tag); result != nil {
if result, ok := border.properties[tag]; ok {
return result return result
} }
switch tag { switch tag {
case Left, Right, Top, Bottom: case Left, Right, Top, Bottom:
result := newBorderProperty(nil) result := newBorderProperty(nil)
if style, ok := border.properties[tag+"-"+Style]; ok { if style := properties.getRaw(tag + "-" + Style); style != nil {
result.Set(Style, style) result.Set(Style, style)
} else if style, ok := border.properties[Style]; ok { } else if style := properties.getRaw(Style); style != nil {
result.Set(Style, style) result.Set(Style, style)
} }
if width, ok := border.properties[tag+"-"+Width]; ok { if width := properties.getRaw(tag + "-" + Width); width != nil {
result.Set(Width, width) result.Set(Width, width)
} else if width, ok := border.properties[Width]; ok { } else if width := properties.getRaw(Width); width != nil {
result.Set(Width, width) result.Set(Width, width)
} }
if color, ok := border.properties[tag+"-"+ColorTag]; ok { if color := properties.getRaw(tag + "-" + ColorTag); color != nil {
result.Set(ColorTag, color) result.Set(ColorTag, color)
} else if color, ok := border.properties[ColorTag]; ok { } else if color := properties.getRaw(ColorTag); color != nil {
result.Set(ColorTag, color) result.Set(ColorTag, color)
} }
return result return result
case LeftStyle, RightStyle, TopStyle, BottomStyle: case LeftStyle, RightStyle, TopStyle, BottomStyle:
if style, ok := border.properties[tag]; ok { if style := properties.getRaw(tag); style != nil {
return style return style
} }
return border.properties[Style] return properties.getRaw(Style)
case LeftWidth, RightWidth, TopWidth, BottomWidth: case LeftWidth, RightWidth, TopWidth, BottomWidth:
if width, ok := border.properties[tag]; ok { if width := properties.getRaw(tag); width != nil {
return width return width
} }
return border.properties[Width] return properties.getRaw(Width)
case LeftColor, RightColor, TopColor, BottomColor: case LeftColor, RightColor, TopColor, BottomColor:
if color, ok := border.properties[tag]; ok { if color := properties.getRaw(tag); color != nil {
return color return color
} }
return border.properties[ColorTag] return properties.getRaw(ColorTag)
} }
return nil return nil
} }
func (border *borderProperty) delete(tag string) { func (border *borderProperty) deleteTag(tag PropertyName) bool {
tag = border.normalizeTag(tag)
remove := []string{} result := false
removeTags := func(tags []PropertyName) {
for _, tag := range tags {
if border.getRaw(tag) != nil {
border.setRaw(tag, nil)
result = true
}
}
}
switch tag { switch tag {
case Style: case Style:
remove = []string{Style, LeftStyle, RightStyle, TopStyle, BottomStyle} removeTags([]PropertyName{Style, LeftStyle, RightStyle, TopStyle, BottomStyle})
case Width: case Width:
remove = []string{Width, LeftWidth, RightWidth, TopWidth, BottomWidth} removeTags([]PropertyName{Width, LeftWidth, RightWidth, TopWidth, BottomWidth})
case ColorTag: case ColorTag:
remove = []string{ColorTag, LeftColor, RightColor, TopColor, BottomColor} removeTags([]PropertyName{ColorTag, LeftColor, RightColor, TopColor, BottomColor})
case Left, Right, Top, Bottom: case Left, Right, Top, Bottom:
if border.Get(Style) != nil { if border.Get(Style) != nil {
border.properties[tag+"-"+Style] = 0 border.properties[tag+"-"+Style] = 0
remove = []string{tag + "-" + ColorTag, tag + "-" + Width} result = true
removeTags([]PropertyName{tag + "-" + ColorTag, tag + "-" + Width})
} else { } else {
remove = []string{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width} removeTags([]PropertyName{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width})
} }
case LeftStyle, RightStyle, TopStyle, BottomStyle: case LeftStyle, RightStyle, TopStyle, BottomStyle:
if border.Get(Style) != nil { if border.getRaw(tag) != nil {
border.properties[tag] = 0 if border.Get(Style) != nil {
} else { border.properties[tag] = 0
remove = []string{tag} result = true
} else {
removeTags([]PropertyName{tag})
}
} }
case LeftWidth, RightWidth, TopWidth, BottomWidth: case LeftWidth, RightWidth, TopWidth, BottomWidth:
if border.Get(Width) != nil { if border.getRaw(tag) != nil {
border.properties[tag] = AutoSize() if border.Get(Width) != nil {
} else { border.properties[tag] = AutoSize()
remove = []string{tag} result = true
} else {
removeTags([]PropertyName{tag})
}
} }
case LeftColor, RightColor, TopColor, BottomColor: case LeftColor, RightColor, TopColor, BottomColor:
if border.Get(ColorTag) != nil { if border.getRaw(tag) != nil {
border.properties[tag] = 0 if border.Get(ColorTag) != nil {
} else { border.properties[tag] = 0
remove = []string{tag} result = true
} else {
removeTags([]PropertyName{tag})
}
} }
} }
for _, tag := range remove { return result
delete(border.properties, tag)
}
} }
func (border *borderProperty) ViewBorders(session Session) ViewBorders { func (border *borderProperty) ViewBorders(session Session) ViewBorders {
@ -755,7 +769,7 @@ func (border *borderProperty) ViewBorders(session Session) ViewBorders {
defWidth, _ := sizeProperty(border, Width, session) defWidth, _ := sizeProperty(border, Width, session)
defColor, _ := colorProperty(border, ColorTag, session) defColor, _ := colorProperty(border, ColorTag, session)
getBorder := func(prefix string) ViewBorder { getBorder := func(prefix PropertyName) ViewBorder {
var result ViewBorder var result ViewBorder
var ok bool var ok bool
if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok { if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok {
@ -784,9 +798,9 @@ func (border *borderProperty) cssStyle(builder cssBuilder, session Session) {
if borders.Top.Style == borders.Right.Style && if borders.Top.Style == borders.Right.Style &&
borders.Top.Style == borders.Left.Style && borders.Top.Style == borders.Left.Style &&
borders.Top.Style == borders.Bottom.Style { borders.Top.Style == borders.Bottom.Style {
builder.add(BorderStyle, values[borders.Top.Style]) builder.add(string(BorderStyle), values[borders.Top.Style])
} else { } else {
builder.addValues(BorderStyle, " ", values[borders.Top.Style], builder.addValues(string(BorderStyle), " ", values[borders.Top.Style],
values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style]) values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style])
} }
} }
@ -870,11 +884,25 @@ func (border *ViewBorders) AllTheSame() bool {
border.Top.Width.Equal(border.Bottom.Width) border.Top.Width.Equal(border.Bottom.Width)
} }
func getBorder(style Properties, tag string) BorderProperty { func getBorderProperty(properties Properties, tag PropertyName) BorderProperty {
if value := style.Get(tag); value != nil { if value := properties.getRaw(tag); value != nil {
if border, ok := value.(BorderProperty); ok { if border, ok := value.(BorderProperty); ok {
return border return border
} }
} }
return nil return nil
} }
func setBorderPropertyElement(properties Properties, mainTag, tag PropertyName, value any) []PropertyName {
border := getBorderProperty(properties, mainTag)
if border == nil {
border = NewBorder(nil)
if border.Set(tag, value) {
properties.setRaw(mainTag, border)
return []PropertyName{mainTag, tag}
}
} else if border.Set(tag, value) {
return []PropertyName{mainTag, tag}
}
return nil
}

177
bounds.go
View File

@ -16,26 +16,33 @@ type BoundsProperty interface {
} }
type boundsPropertyData struct { type boundsPropertyData struct {
propertyList dataProperty
} }
// NewBoundsProperty creates the new BoundsProperty object. // NewBoundsProperty creates the new BoundsProperty object.
// The following SizeUnit properties can be used: "left" (Left), "right" (Right), "top" (Top), and "bottom" (Bottom). // The following SizeUnit properties can be used: "left" (Left), "right" (Right), "top" (Top), and "bottom" (Bottom).
func NewBoundsProperty(params Params) BoundsProperty { func NewBoundsProperty(params Params) BoundsProperty {
bounds := new(boundsPropertyData) bounds := new(boundsPropertyData)
bounds.properties = map[string]any{} bounds.init()
if params != nil { if params != nil {
for _, tag := range []string{Top, Right, Bottom, Left} { for _, tag := range bounds.supportedProperties {
if value, ok := params[tag]; ok { if value, ok := params[tag]; ok && value != nil {
bounds.Set(tag, value) bounds.set(bounds, tag, value)
} }
} }
} }
return bounds return bounds
} }
func (bounds *boundsPropertyData) normalizeTag(tag string) string { func (bounds *boundsPropertyData) init() {
tag = strings.ToLower(tag) bounds.dataProperty.init()
bounds.normalize = normalizeBoundsTag
bounds.supportedProperties = []PropertyName{Top, Right, Bottom, Left}
}
func normalizeBoundsTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag { switch tag {
case MarginTop, PaddingTop, CellPaddingTop, case MarginTop, PaddingTop, CellPaddingTop,
"top-margin", "top-padding", "top-cell-padding": "top-margin", "top-padding", "top-cell-padding":
@ -64,12 +71,12 @@ func (bounds *boundsPropertyData) String() string {
func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent string) { func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ") buffer.WriteString("_{ ")
comma := false comma := false
for _, tag := range []string{Top, Right, Bottom, Left} { for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
if value, ok := bounds.properties[tag]; ok { if value, ok := bounds.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -78,38 +85,6 @@ func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent st
buffer.WriteString(" }") buffer.WriteString(" }")
} }
func (bounds *boundsPropertyData) Remove(tag string) {
bounds.propertyList.Remove(bounds.normalizeTag(tag))
}
func (bounds *boundsPropertyData) Set(tag string, value any) bool {
if value == nil {
bounds.Remove(tag)
return true
}
tag = bounds.normalizeTag(tag)
switch tag {
case Top, Right, Bottom, Left:
return bounds.setSizeProperty(tag, value)
default:
ErrorLogF(`"%s" property is not compatible with the BoundsProperty`, tag)
}
return false
}
func (bounds *boundsPropertyData) Get(tag string) any {
tag = bounds.normalizeTag(tag)
if value, ok := bounds.properties[tag]; ok {
return value
}
return nil
}
func (bounds *boundsPropertyData) Bounds(session Session) Bounds { func (bounds *boundsPropertyData) Bounds(session Session) Bounds {
top, _ := sizeProperty(bounds, Top, session) top, _ := sizeProperty(bounds, Top, session)
right, _ := sizeProperty(bounds, Right, session) right, _ := sizeProperty(bounds, Right, session)
@ -141,7 +116,7 @@ func (bounds *Bounds) SetAll(value SizeUnit) {
bounds.Left = value bounds.Left = value
} }
func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTag string, properties Properties, session Session) { func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTag PropertyName, properties Properties, session Session) {
bounds.Top = AutoSize() bounds.Top = AutoSize()
if size, ok := sizeProperty(properties, tag, session); ok { if size, ok := sizeProperty(properties, tag, session); ok {
bounds.Top = size bounds.Top = size
@ -216,11 +191,11 @@ func (bounds *Bounds) String() string {
bounds.Bottom.String() + "," + bounds.Left.String() bounds.Bottom.String() + "," + bounds.Left.String()
} }
func (bounds *Bounds) cssValue(tag string, builder cssBuilder, session Session) { func (bounds *Bounds) cssValue(tag PropertyName, builder cssBuilder, session Session) {
if bounds.allFieldsEqual() { if bounds.allFieldsEqual() {
builder.add(tag, bounds.Top.cssString("0", session)) builder.add(string(tag), bounds.Top.cssString("0", session))
} else { } else {
builder.addValues(tag, " ", builder.addValues(string(tag), " ",
bounds.Top.cssString("0", session), bounds.Top.cssString("0", session),
bounds.Right.cssString("0", session), bounds.Right.cssString("0", session),
bounds.Bottom.cssString("0", session), bounds.Bottom.cssString("0", session),
@ -234,8 +209,8 @@ func (bounds *Bounds) cssString(session Session) string {
return builder.finish() return builder.finish()
} }
func (properties *propertyList) setBounds(tag string, value any) bool { func setBoundsProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if strings.Contains(value, ",") { if strings.Contains(value, ",") {
@ -247,88 +222,119 @@ func (properties *propertyList) setBounds(tag string, value any) bool {
case 4: case 4:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
for i, tag := range []string{Top, Right, Bottom, Left} { for i, tag := range []PropertyName{Top, Right, Bottom, Left} {
if !bounds.Set(tag, values[i]) { if !bounds.Set(tag, values[i]) {
notCompatibleType(tag, value) return nil
return false
} }
} }
properties.properties[tag] = bounds properties.setRaw(tag, bounds)
return true return []PropertyName{tag}
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
return properties.setSizeProperty(tag, value) return setSizeProperty(properties, tag, value)
case SizeUnit: case SizeUnit:
properties.properties[tag] = value properties.setRaw(tag, value)
case float32: case float32:
properties.properties[tag] = Px(float64(value)) properties.setRaw(tag, Px(float64(value)))
case float64: case float64:
properties.properties[tag] = Px(value) properties.setRaw(tag, Px(value))
case Bounds: case Bounds:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
if value.Top.Type != Auto { if value.Top.Type != Auto {
bounds.Set(Top, value.Top) bounds.setRaw(Top, value.Top)
} }
if value.Right.Type != Auto { if value.Right.Type != Auto {
bounds.Set(Right, value.Right) bounds.setRaw(Right, value.Right)
} }
if value.Bottom.Type != Auto { if value.Bottom.Type != Auto {
bounds.Set(Bottom, value.Bottom) bounds.setRaw(Bottom, value.Bottom)
} }
if value.Left.Type != Auto { if value.Left.Type != Auto {
bounds.Set(Left, value.Left) bounds.setRaw(Left, value.Left)
} }
properties.properties[tag] = bounds properties.setRaw(tag, bounds)
case BoundsProperty: case BoundsProperty:
properties.properties[tag] = value properties.setRaw(tag, value)
case DataObject: case DataObject:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
for _, tag := range []string{Top, Right, Bottom, Left} { for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
if text, ok := value.PropertyValue(tag); ok { if text, ok := value.PropertyValue(string(tag)); ok {
if !bounds.Set(tag, text) { if !bounds.Set(tag, text) {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
} }
properties.properties[tag] = bounds properties.setRaw(tag, bounds)
default: default:
if n, ok := isInt(value); ok { if n, ok := isInt(value); ok {
properties.properties[tag] = Px(float64(n)) properties.setRaw(tag, Px(float64(n)))
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) boundsProperty(tag string) BoundsProperty { func removeBoundsPropertySide(properties Properties, mainTag, sideTag PropertyName) []PropertyName {
if value, ok := properties.properties[tag]; ok { if bounds := getBoundsProperty(properties, mainTag); bounds != nil {
if bounds.getRaw(sideTag) != nil {
bounds.Remove(sideTag)
if bounds.empty() {
bounds = nil
}
properties.setRaw(mainTag, bounds)
return []PropertyName{mainTag, sideTag}
}
}
return []PropertyName{}
}
func setBoundsPropertySide(properties Properties, mainTag, sideTag PropertyName, value any) []PropertyName {
if value == nil {
return removeBoundsPropertySide(properties, mainTag, sideTag)
}
bounds := getBoundsProperty(properties, mainTag)
if bounds == nil {
bounds = NewBoundsProperty(nil)
}
if bounds.Set(sideTag, value) {
properties.setRaw(mainTag, bounds)
return []PropertyName{mainTag, sideTag}
}
notCompatibleType(sideTag, value)
return nil
}
func getBoundsProperty(properties Properties, tag PropertyName) BoundsProperty {
if value := properties.getRaw(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
for _, t := range []string{Top, Right, Bottom, Left} { for _, t := range []PropertyName{Top, Right, Bottom, Left} {
bounds.Set(t, value) bounds.Set(t, value)
} }
return bounds return bounds
case SizeUnit: case SizeUnit:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
for _, t := range []string{Top, Right, Bottom, Left} { for _, t := range []PropertyName{Top, Right, Bottom, Left} {
bounds.Set(t, value) bounds.Set(t, value)
} }
return bounds return bounds
@ -345,29 +351,10 @@ func (properties *propertyList) boundsProperty(tag string) BoundsProperty {
} }
} }
return NewBoundsProperty(nil) return nil
} }
func (properties *propertyList) removeBoundsSide(mainTag, sideTag string) { func getBounds(properties Properties, tag PropertyName, session Session) (Bounds, bool) {
bounds := properties.boundsProperty(mainTag)
if bounds.Get(sideTag) != nil {
bounds.Remove(sideTag)
properties.properties[mainTag] = bounds
}
}
func (properties *propertyList) setBoundsSide(mainTag, sideTag string, value any) bool {
bounds := properties.boundsProperty(mainTag)
if bounds.Set(sideTag, value) {
properties.properties[mainTag] = bounds
return true
}
notCompatibleType(sideTag, value)
return false
}
func boundsProperty(properties Properties, tag string, session Session) (Bounds, bool) {
if value := properties.Get(tag); value != nil { if value := properties.Get(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case string: case string:

View File

@ -18,6 +18,7 @@ func NewButton(session Session, params Params) Button {
func newButton(session Session) View { func newButton(session Session) View {
return NewButton(session, nil) return NewButton(session, nil)
//return new(buttonData)
} }
func (button *buttonData) CreateSuperView(session Session) View { func (button *buttonData) CreateSuperView(session Session) View {

View File

@ -1,14 +1,12 @@
package rui package rui
import "strings"
// DrawFunction is the constant for "draw-function" property tag. // DrawFunction is the constant for "draw-function" property tag.
// //
// Used by `CanvasView`. // Used by `CanvasView`.
// Property sets the draw function of `CanvasView`. // Property sets the draw function of `CanvasView`.
// //
// Supported types: `func(Canvas)`. // Supported types: `func(Canvas)`.
const DrawFunction = "draw-function" const DrawFunction PropertyName = "draw-function"
// CanvasView interface of a custom draw view // CanvasView interface of a custom draw view
type CanvasView interface { type CanvasView interface {
@ -20,7 +18,6 @@ type CanvasView interface {
type canvasViewData struct { type canvasViewData struct {
viewData viewData
drawer func(Canvas)
} }
// NewCanvasView creates the new custom draw view // NewCanvasView creates the new custom draw view
@ -32,21 +29,21 @@ func NewCanvasView(session Session, params Params) CanvasView {
} }
func newCanvasView(session Session) View { func newCanvasView(session Session) View {
return NewCanvasView(session, nil) return new(canvasViewData)
} }
// Init initialize fields of ViewsContainer by default values // Init initialize fields of ViewsContainer by default values
func (canvasView *canvasViewData) init(session Session) { func (canvasView *canvasViewData) init(session Session) {
canvasView.viewData.init(session) canvasView.viewData.init(session)
canvasView.tag = "CanvasView" canvasView.tag = "CanvasView"
canvasView.normalize = normalizeCanvasViewTag
canvasView.set = canvasViewSet
canvasView.remove = canvasViewRemove
} }
func (canvasView *canvasViewData) String() string { func normalizeCanvasViewTag(tag PropertyName) PropertyName {
return getViewString(canvasView, nil) tag = defaultNormalize(tag)
}
func (canvasView *canvasViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case "draw-func": case "draw-func":
tag = DrawFunction tag = DrawFunction
@ -54,51 +51,36 @@ func (canvasView *canvasViewData) normalizeTag(tag string) string {
return tag return tag
} }
func (canvasView *canvasViewData) Remove(tag string) { func canvasViewRemove(view View, tag PropertyName) []PropertyName {
canvasView.remove(canvasView.normalizeTag(tag))
}
func (canvasView *canvasViewData) remove(tag string) {
if tag == DrawFunction { if tag == DrawFunction {
canvasView.drawer = nil if view.getRaw(DrawFunction) != nil {
canvasView.Redraw() view.setRaw(DrawFunction, nil)
canvasView.propertyChangedEvent(tag) if canvasView, ok := view.(CanvasView); ok {
} else { canvasView.Redraw()
canvasView.viewData.remove(tag) }
return []PropertyName{DrawFunction}
}
return []PropertyName{}
} }
return viewRemove(view, tag)
} }
func (canvasView *canvasViewData) Set(tag string, value any) bool { func canvasViewSet(view View, tag PropertyName, value any) []PropertyName {
return canvasView.set(canvasView.normalizeTag(tag), value)
}
func (canvasView *canvasViewData) set(tag string, value any) bool {
if tag == DrawFunction { if tag == DrawFunction {
if value == nil { if fn, ok := value.(func(Canvas)); ok {
canvasView.drawer = nil view.setRaw(DrawFunction, fn)
} else if fn, ok := value.(func(Canvas)); ok {
canvasView.drawer = fn
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
canvasView.Redraw() if canvasView, ok := view.(CanvasView); ok {
canvasView.propertyChangedEvent(tag) canvasView.Redraw()
return true }
return []PropertyName{DrawFunction}
} }
return canvasView.viewData.set(tag, value) return viewSet(view, tag, value)
}
func (canvasView *canvasViewData) Get(tag string) any {
return canvasView.get(canvasView.normalizeTag(tag))
}
func (canvasView *canvasViewData) get(tag string) any {
if tag == DrawFunction {
return canvasView.drawer
}
return canvasView.viewData.get(tag)
} }
func (canvasView *canvasViewData) htmlTag() string { func (canvasView *canvasViewData) htmlTag() string {
@ -106,14 +88,14 @@ func (canvasView *canvasViewData) htmlTag() string {
} }
func (canvasView *canvasViewData) Redraw() { func (canvasView *canvasViewData) Redraw() {
if canvasView.drawer != nil { canvas := newCanvas(canvasView)
canvas := newCanvas(canvasView) canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height)
canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height) if value := canvasView.getRaw(DrawFunction); value != nil {
if canvasView.drawer != nil { if drawer, ok := value.(func(Canvas)); ok {
canvasView.drawer(canvas) drawer(canvas)
} }
canvas.finishDraw()
} }
canvas.finishDraw()
} }
func (canvasView *canvasViewData) onResize(self View, x, y, width, height float64) { func (canvasView *canvasViewData) onResize(self View, x, y, width, height float64) {

View File

@ -20,7 +20,7 @@ import (
// `func(checkbox rui.Checkbox)`, // `func(checkbox rui.Checkbox)`,
// `func(checked bool)`, // `func(checked bool)`,
// `func()`. // `func()`.
const CheckboxChangedEvent = "checkbox-event" const CheckboxChangedEvent PropertyName = "checkbox-event"
// Checkbox represent a Checkbox view // Checkbox represent a Checkbox view
type Checkbox interface { type Checkbox interface {
@ -29,171 +29,132 @@ type Checkbox interface {
type checkboxData struct { type checkboxData struct {
viewsContainerData viewsContainerData
checkedListeners []func(Checkbox, bool)
} }
// NewCheckbox create new Checkbox object and return it // NewCheckbox create new Checkbox object and return it
func NewCheckbox(session Session, params Params) Checkbox { func NewCheckbox(session Session, params Params) Checkbox {
view := new(checkboxData) view := new(checkboxData)
view.init(session) view.init(session)
setInitParams(view, Params{
ClickEvent: checkboxClickListener,
KeyDownEvent: checkboxKeyListener,
})
setInitParams(view, params) setInitParams(view, params)
return view return view
} }
func newCheckbox(session Session) View { func newCheckbox(session Session) View {
return NewCheckbox(session, nil) return new(checkboxData)
} }
func (button *checkboxData) init(session Session) { func (button *checkboxData) init(session Session) {
button.viewsContainerData.init(session) button.viewsContainerData.init(session)
button.tag = "Checkbox" button.tag = "Checkbox"
button.systemClass = "ruiGridLayout ruiCheckbox" button.systemClass = "ruiGridLayout ruiCheckbox"
button.checkedListeners = []func(Checkbox, bool){} button.set = button.setFunc
} button.remove = button.removeFunc
button.changed = checkboxPropertyChanged
func (button *checkboxData) String() string { button.setRaw(ClickEvent, checkboxClickListener)
return getViewString(button, nil) button.setRaw(KeyDownEvent, checkboxKeyListener)
} }
func (button *checkboxData) Focusable() bool { func (button *checkboxData) Focusable() bool {
return true return true
} }
func (button *checkboxData) Get(tag string) any { func checkboxPropertyChanged(view View, tag PropertyName) {
switch strings.ToLower(tag) {
case CheckboxChangedEvent:
return button.checkedListeners
}
return button.viewsContainerData.Get(tag)
}
func (button *checkboxData) Set(tag string, value any) bool {
return button.set(tag, value)
}
func (button *checkboxData) set(tag string, value any) bool {
switch tag { switch tag {
case CheckboxChangedEvent:
if !button.setChangedListener(value) {
notCompatibleType(tag, value)
return false
}
case Checked: case Checked:
oldChecked := button.checked() session := view.Session()
if !button.setBoolProperty(Checked, value) { checked := IsCheckboxChecked(view)
return false if listeners := GetCheckboxChangedListeners(view); len(listeners) > 0 {
} if checkbox, ok := view.(Checkbox); ok {
if button.created { for _, listener := range listeners {
checked := button.checked() listener(checkbox, checked)
if checked != oldChecked { }
button.changedCheckboxState(checked)
} }
} }
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
checkboxHtml(view, buffer, checked)
session.updateInnerHTML(view.htmlID()+"checkbox", buffer.String())
case CheckboxHorizontalAlign, CheckboxVerticalAlign: case CheckboxHorizontalAlign, CheckboxVerticalAlign:
if !button.setEnumProperty(tag, value, enumProperties[tag].values) { htmlID := view.htmlID()
return false session := view.Session()
} updateCSSStyle(htmlID, session)
if button.created { updateInnerHTML(htmlID, session)
htmlID := button.htmlID()
updateCSSStyle(htmlID, button.session)
updateInnerHTML(htmlID, button.session)
}
case VerticalAlign: case VerticalAlign:
if !button.setEnumProperty(tag, value, enumProperties[tag].values) { view.Session().updateCSSProperty(view.htmlID()+"content", "align-items", checkboxVerticalAlignCSS(view))
return false
}
if button.created {
button.session.updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign())
}
case HorizontalAlign: case HorizontalAlign:
if !button.setEnumProperty(tag, value, enumProperties[tag].values) { view.Session().updateCSSProperty(view.htmlID()+"content", "justify-items", checkboxHorizontalAlignCSS(view))
return false
}
if button.created {
button.session.updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign())
}
case CellVerticalAlign, CellHorizontalAlign, CellWidth, CellHeight:
return false
case AccentColor: case AccentColor:
if !button.setColorProperty(AccentColor, value) { updateInnerHTML(view.htmlID(), view.Session())
return false
}
if button.created {
updateInnerHTML(button.htmlID(), button.session)
}
default: default:
return button.viewsContainerData.set(tag, value) viewsContainerPropertyChanged(view, tag)
} }
button.propertyChangedEvent(tag)
return true
} }
func (button *checkboxData) Remove(tag string) { func (button *checkboxData) setFunc(view View, tag PropertyName, value any) []PropertyName {
button.remove(strings.ToLower(tag))
}
func (button *checkboxData) remove(tag string) {
switch tag { switch tag {
case ClickEvent: case ClickEvent:
if !button.viewsContainerData.set(ClickEvent, checkboxClickListener) { if button.viewsContainerData.setFunc(view, ClickEvent, value) != nil {
delete(button.properties, tag) if value := view.getRaw(ClickEvent); value != nil {
if listeners, ok := value.([]func(View, MouseEvent)); ok {
listeners = append(listeners, checkboxClickListener)
view.setRaw(ClickEvent, listeners)
return []PropertyName{ClickEvent}
}
}
return button.viewsContainerData.setFunc(view, ClickEvent, checkboxClickListener)
} }
return nil
case KeyDownEvent: case KeyDownEvent:
if !button.viewsContainerData.set(KeyDownEvent, checkboxKeyListener) { if button.viewsContainerData.setFunc(view, KeyDownEvent, value) != nil {
delete(button.properties, tag) if value := view.getRaw(KeyDownEvent); value != nil {
if listeners, ok := value.([]func(View, KeyEvent)); ok {
listeners = append(listeners, checkboxKeyListener)
view.setRaw(KeyDownEvent, listeners)
return []PropertyName{KeyDownEvent}
}
}
return button.viewsContainerData.setFunc(view, KeyDownEvent, checkboxKeyListener)
} }
return nil
case CheckboxChangedEvent: case CheckboxChangedEvent:
if len(button.checkedListeners) > 0 { return setViewEventListener[Checkbox, bool](view, tag, value)
button.checkedListeners = []func(Checkbox, bool){}
}
case Checked: case Checked:
oldChecked := button.checked() return setBoolProperty(view, Checked, value)
delete(button.properties, tag)
if button.created && oldChecked {
button.changedCheckboxState(false)
}
case CheckboxHorizontalAlign, CheckboxVerticalAlign: case CellVerticalAlign, CellHorizontalAlign, CellWidth, CellHeight:
delete(button.properties, tag) ErrorLogF(`"%s" property is not compatible with the BoundsProperty`, string(tag))
if button.created { return nil
htmlID := button.htmlID()
updateCSSStyle(htmlID, button.session)
updateInnerHTML(htmlID, button.session)
}
case VerticalAlign:
delete(button.properties, tag)
if button.created {
button.session.updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign())
}
case HorizontalAlign:
delete(button.properties, tag)
if button.created {
button.session.updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign())
}
default:
button.viewsContainerData.remove(tag)
return
} }
button.propertyChangedEvent(tag)
return button.viewsContainerData.setFunc(view, tag, value)
}
func (button *checkboxData) removeFunc(view View, tag PropertyName) []PropertyName {
switch tag {
case ClickEvent:
button.setRaw(ClickEvent, checkboxClickListener)
return []PropertyName{ClickEvent}
case KeyDownEvent:
button.setRaw(KeyDownEvent, checkboxKeyListener)
return []PropertyName{ClickEvent}
}
return button.viewsContainerData.removeFunc(view, tag)
} }
func (button *checkboxData) checked() bool { func (button *checkboxData) checked() bool {
@ -201,8 +162,9 @@ func (button *checkboxData) checked() bool {
return checked return checked
} }
/*
func (button *checkboxData) changedCheckboxState(state bool) { func (button *checkboxData) changedCheckboxState(state bool) {
for _, listener := range button.checkedListeners { for _, listener := range GetCheckboxChangedListeners(button) {
listener(button, state) listener(button, state)
} }
@ -212,8 +174,9 @@ func (button *checkboxData) changedCheckboxState(state bool) {
button.htmlCheckbox(buffer, state) button.htmlCheckbox(buffer, state)
button.Session().updateInnerHTML(button.htmlID()+"checkbox", buffer.String()) button.Session().updateInnerHTML(button.htmlID()+"checkbox", buffer.String())
} }
*/
func checkboxClickListener(view View) { func checkboxClickListener(view View, _ MouseEvent) {
view.Set(Checked, !IsCheckboxChecked(view)) view.Set(Checked, !IsCheckboxChecked(view))
BlurView(view) BlurView(view)
} }
@ -225,17 +188,6 @@ func checkboxKeyListener(view View, event KeyEvent) {
} }
} }
func (button *checkboxData) setChangedListener(value any) bool {
listeners, ok := valueToEventListeners[Checkbox, bool](value)
if !ok {
return false
} else if listeners == nil {
listeners = []func(Checkbox, bool){}
}
button.checkedListeners = listeners
return true
}
func (button *checkboxData) cssStyle(self View, builder cssBuilder) { func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
session := button.Session() session := button.Session()
vAlign := GetCheckboxVerticalAlign(button) vAlign := GetCheckboxVerticalAlign(button)
@ -265,7 +217,8 @@ func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
button.viewsContainerData.cssStyle(self, builder) button.viewsContainerData.cssStyle(self, builder)
} }
func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) { func checkboxHtml(button View, buffer *strings.Builder, checked bool) (int, int) {
//func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) {
vAlign := GetCheckboxVerticalAlign(button) vAlign := GetCheckboxVerticalAlign(button)
hAlign := GetCheckboxHorizontalAlign(button) hAlign := GetCheckboxHorizontalAlign(button)
@ -317,7 +270,7 @@ func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool)
func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) { func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
vCheckboxAlign, hCheckboxAlign := button.htmlCheckbox(buffer, IsCheckboxChecked(button)) vCheckboxAlign, hCheckboxAlign := checkboxHtml(button, buffer, IsCheckboxChecked(button))
buffer.WriteString(`<div id="`) buffer.WriteString(`<div id="`)
buffer.WriteString(button.htmlID()) buffer.WriteString(button.htmlID())
@ -335,11 +288,11 @@ func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
} }
buffer.WriteString(" align-items: ") buffer.WriteString(" align-items: ")
buffer.WriteString(button.cssVerticalAlign()) buffer.WriteString(checkboxVerticalAlignCSS(button))
buffer.WriteRune(';') buffer.WriteRune(';')
buffer.WriteString(" justify-items: ") buffer.WriteString(" justify-items: ")
buffer.WriteString(button.cssHorizontalAlign()) buffer.WriteString(checkboxHorizontalAlignCSS(button))
buffer.WriteRune(';') buffer.WriteRune(';')
buffer.WriteString(`">`) buffer.WriteString(`">`)
@ -347,8 +300,8 @@ func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteString(`</div>`) buffer.WriteString(`</div>`)
} }
func (button *checkboxData) cssHorizontalAlign() string { func checkboxHorizontalAlignCSS(view View) string {
align := GetHorizontalAlign(button) align := GetHorizontalAlign(view)
values := enumProperties[CellHorizontalAlign].cssValues values := enumProperties[CellHorizontalAlign].cssValues
if align >= 0 && align < len(values) { if align >= 0 && align < len(values) {
return values[align] return values[align]
@ -356,8 +309,8 @@ func (button *checkboxData) cssHorizontalAlign() string {
return values[0] return values[0]
} }
func (button *checkboxData) cssVerticalAlign() string { func checkboxVerticalAlignCSS(view View) string {
align := GetVerticalAlign(button) align := GetVerticalAlign(view)
values := enumProperties[CellVerticalAlign].cssValues values := enumProperties[CellVerticalAlign].cssValues
if align >= 0 && align < len(values) { if align >= 0 && align < len(values) {
return values[align] return values[align]

View File

@ -25,7 +25,7 @@ const (
// `func(newColor rui.Color)`, // `func(newColor rui.Color)`,
// `func(picker rui.ColorPicker)`, // `func(picker rui.ColorPicker)`,
// `func()`. // `func()`.
ColorChangedEvent = "color-changed" ColorChangedEvent PropertyName = "color-changed"
// ColorPickerValue is the constant for "color-picker-value" property tag. // ColorPickerValue is the constant for "color-picker-value" property tag.
// //
@ -36,7 +36,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
ColorPickerValue = "color-picker-value" ColorPickerValue PropertyName = "color-picker-value"
) )
// ColorPicker represent a ColorPicker view // ColorPicker represent a ColorPicker view
@ -46,8 +46,6 @@ type ColorPicker interface {
type colorPickerData struct { type colorPickerData struct {
viewData viewData
dataList
colorChangedListeners []func(ColorPicker, Color, Color)
} }
// NewColorPicker create new ColorPicker object and return it // NewColorPicker create new ColorPicker object and return it
@ -59,125 +57,69 @@ func NewColorPicker(session Session, params Params) ColorPicker {
} }
func newColorPicker(session Session) View { func newColorPicker(session Session) View {
return NewColorPicker(session, nil) return new(colorPickerData)
} }
func (picker *colorPickerData) init(session Session) { func (picker *colorPickerData) init(session Session) {
picker.viewData.init(session) picker.viewData.init(session)
picker.tag = "ColorPicker" picker.tag = "ColorPicker"
picker.hasHtmlDisabled = true picker.hasHtmlDisabled = true
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.properties[Padding] = Px(0) picker.properties[Padding] = Px(0)
picker.dataListInit() picker.normalize = normalizeColorPickerTag
picker.set = colorPickerSet
picker.changed = colorPickerPropertyChanged
} }
func (picker *colorPickerData) String() string { func normalizeColorPickerTag(tag PropertyName) PropertyName {
return getViewString(picker, nil) tag = defaultNormalize(tag)
}
func (picker *colorPickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case Value, ColorTag: case Value, ColorTag:
return ColorPickerValue return ColorPickerValue
} }
return picker.normalizeDataListTag(tag) return normalizeDataListTag(tag)
} }
func (picker *colorPickerData) Remove(tag string) { func colorPickerSet(view View, tag PropertyName, value any) []PropertyName {
picker.remove(picker.normalizeTag(tag))
}
func (picker *colorPickerData) remove(tag string) {
switch tag { switch tag {
case ColorChangedEvent: case ColorChangedEvent:
if len(picker.colorChangedListeners) > 0 { return setEventWithOldListener[ColorPicker, Color](view, tag, value)
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.propertyChangedEvent(tag)
}
case ColorPickerValue: case ColorPickerValue:
oldColor := GetColorPickerValue(picker) oldColor := GetColorPickerValue(view)
delete(picker.properties, ColorPickerValue) result := setColorProperty(view, ColorPickerValue, value)
picker.colorChanged(oldColor) if result != nil {
view.setRaw("old-color", oldColor)
}
return result
case DataList: case DataList:
if len(picker.dataList.dataList) > 0 { return setDataList(view, value, "")
picker.setDataList(picker, []string{}, true)
}
default:
picker.viewData.remove(tag)
}
}
func (picker *colorPickerData) Set(tag string, value any) bool {
return picker.set(picker.normalizeTag(tag), value)
}
func (picker *colorPickerData) set(tag string, value any) bool {
if value == nil {
picker.remove(tag)
return true
} }
return viewSet(view, tag, value)
}
func colorPickerPropertyChanged(view View, tag PropertyName) {
switch tag { switch tag {
case ColorChangedEvent:
listeners, ok := valueToEventWithOldListeners[ColorPicker, Color](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(ColorPicker, Color, Color){}
}
picker.colorChangedListeners = listeners
picker.propertyChangedEvent(tag)
return true
case ColorPickerValue: case ColorPickerValue:
oldColor := GetColorPickerValue(picker) color := GetColorPickerValue(view)
if picker.setColorProperty(ColorPickerValue, value) { view.Session().callFunc("setInputValue", view.htmlID(), color.rgbString())
picker.colorChanged(oldColor)
return true
}
case DataList: if listeners := GetColorChangedListeners(view); len(listeners) > 0 {
return picker.setDataList(picker, value, picker.created) oldColor := Color(0)
if value := view.getRaw("old-color"); value != nil {
oldColor = value.(Color)
}
for _, listener := range listeners {
listener(view, color, oldColor)
}
}
default: default:
return picker.viewData.set(tag, value) viewPropertyChanged(view, tag)
} }
return false
}
func (picker *colorPickerData) colorChanged(oldColor Color) {
if newColor := GetColorPickerValue(picker); oldColor != newColor {
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
}
for _, listener := range picker.colorChangedListeners {
listener(picker, newColor, oldColor)
}
picker.propertyChangedEvent(ColorTag)
}
}
func (picker *colorPickerData) Get(tag string) any {
return picker.get(picker.normalizeTag(tag))
}
func (picker *colorPickerData) get(tag string) any {
switch tag {
case ColorChangedEvent:
return picker.colorChangedListeners
case DataList:
return picker.dataList.dataList
default:
return picker.viewData.get(tag)
}
} }
func (picker *colorPickerData) htmlTag() string { func (picker *colorPickerData) htmlTag() string {
@ -185,7 +127,10 @@ func (picker *colorPickerData) htmlTag() string {
} }
func (picker *colorPickerData) htmlSubviews(self View, buffer *strings.Builder) { func (picker *colorPickerData) htmlSubviews(self View, buffer *strings.Builder) {
picker.dataListHtmlSubviews(self, buffer) dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
text, _ = session.resolveConstants(text)
return text
})
} }
func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder) { func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder) {
@ -200,20 +145,23 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`) buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
} }
picker.dataListHtmlProperties(picker, buffer) dataListHtmlProperties(picker, buffer)
} }
func (picker *colorPickerData) handleCommand(self View, command string, data DataObject) bool { func (picker *colorPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyValue("text"); ok {
oldColor := GetColorPickerValue(picker)
if color, ok := StringToColor(text); ok { if color, ok := StringToColor(text); ok {
oldColor := GetColorPickerValue(picker)
picker.properties[ColorPickerValue] = color picker.properties[ColorPickerValue] = color
if color != oldColor { if color != oldColor {
for _, listener := range picker.colorChangedListeners { for _, listener := range GetColorChangedListeners(picker) {
listener(picker, color, oldColor) listener(picker, color, oldColor)
} }
if listener, ok := picker.changeListener[ColorPickerValue]; ok {
listener(picker, ColorPickerValue)
}
} }
} }
} }
@ -233,7 +181,7 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok { if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok {
return value return value
} }
for _, tag := range []string{ColorPickerValue, Value, ColorTag} { for _, tag := range []PropertyName{ColorPickerValue, Value, ColorTag} {
if value := valueFromStyle(view, tag); value != nil { if value := valueFromStyle(view, tag); value != nil {
if result, ok := valueToColor(value, view.Session()); ok { if result, ok := valueToColor(value, view.Session()); ok {
return result return result

View File

@ -2,7 +2,6 @@ package rui
import ( import (
"strconv" "strconv"
"strings"
) )
// Constants for [ColumnLayout] specific properties and events // Constants for [ColumnLayout] specific properties and events
@ -18,7 +17,7 @@ const (
// Values: // Values:
// `0` or "0" - Use "column-width" to control how many columns will be created. // `0` or "0" - Use "column-width" to control how many columns will be created.
// >= `0` or >= "0" - Тhe number of columns into which the content is divided. // >= `0` or >= "0" - Тhe number of columns into which the content is divided.
ColumnCount = "column-count" ColumnCount PropertyName = "column-count"
// ColumnWidth is the constant for "column-width" property tag. // ColumnWidth is the constant for "column-width" property tag.
// //
@ -29,7 +28,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ColumnWidth = "column-width" ColumnWidth PropertyName = "column-width"
// ColumnGap is the constant for "column-gap" property tag. // ColumnGap is the constant for "column-gap" property tag.
// //
@ -40,7 +39,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ColumnGap = "column-gap" ColumnGap PropertyName = "column-gap"
// ColumnSeparator is the constant for "column-separator" property tag. // ColumnSeparator is the constant for "column-separator" property tag.
// //
@ -51,7 +50,7 @@ const (
// //
// Internal type is `ColumnSeparatorProperty`, other types converted to it during assignment. // Internal type is `ColumnSeparatorProperty`, other types converted to it during assignment.
// See `ColumnSeparatorProperty` and `ViewBorder` description for more details. // See `ColumnSeparatorProperty` and `ViewBorder` description for more details.
ColumnSeparator = "column-separator" ColumnSeparator PropertyName = "column-separator"
// ColumnSeparatorStyle is the constant for "column-separator-style" property tag. // ColumnSeparatorStyle is the constant for "column-separator-style" property tag.
// //
@ -66,7 +65,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a separator. // `2`(`DashedLine`) or "dashed" - Dashed line as a separator.
// `3`(`DottedLine`) or "dotted" - Dotted line as a separator. // `3`(`DottedLine`) or "dotted" - Dotted line as a separator.
// `4`(`DoubleLine`) or "double" - Double line as a separator. // `4`(`DoubleLine`) or "double" - Double line as a separator.
ColumnSeparatorStyle = "column-separator-style" ColumnSeparatorStyle PropertyName = "column-separator-style"
// ColumnSeparatorWidth is the constant for "column-separator-width" property tag. // ColumnSeparatorWidth is the constant for "column-separator-width" property tag.
// //
@ -77,7 +76,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ColumnSeparatorWidth = "column-separator-width" ColumnSeparatorWidth PropertyName = "column-separator-width"
// ColumnSeparatorColor is the constant for "column-separator-color" property tag. // ColumnSeparatorColor is the constant for "column-separator-color" property tag.
// //
@ -88,7 +87,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
ColumnSeparatorColor = "column-separator-color" ColumnSeparatorColor PropertyName = "column-separator-color"
// ColumnFill is the constant for "column-fill" property tag. // ColumnFill is the constant for "column-fill" property tag.
// //
@ -100,7 +99,7 @@ const (
// Values: // Values:
// `0`(`ColumnFillBalance`) or "balance" - Content is equally divided between columns. // `0`(`ColumnFillBalance`) or "balance" - Content is equally divided between columns.
// `1`(`ColumnFillAuto`) or "auto" - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty. // `1`(`ColumnFillAuto`) or "auto" - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
ColumnFill = "column-fill" ColumnFill PropertyName = "column-fill"
// ColumnSpanAll is the constant for "column-span-all" property tag. // ColumnSpanAll is the constant for "column-span-all" property tag.
// //
@ -113,7 +112,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - View will span across all columns. // `true` or `1` or "true", "yes", "on", "1" - View will span across all columns.
// `false` or `0` or "false", "no", "off", "0" - View will be a part of a column. // `false` or `0` or "false", "no", "off", "0" - View will be a part of a column.
ColumnSpanAll = "column-span-all" ColumnSpanAll PropertyName = "column-span-all"
) )
// ColumnLayout represent a ColumnLayout view // ColumnLayout represent a ColumnLayout view
@ -134,22 +133,20 @@ func NewColumnLayout(session Session, params Params) ColumnLayout {
} }
func newColumnLayout(session Session) View { func newColumnLayout(session Session) View {
return NewColumnLayout(session, nil) return new(columnLayoutData)
} }
// Init initialize fields of ColumnLayout by default values // Init initialize fields of ColumnLayout by default values
func (ColumnLayout *columnLayoutData) init(session Session) { func (columnLayout *columnLayoutData) init(session Session) {
ColumnLayout.viewsContainerData.init(session) columnLayout.viewsContainerData.init(session)
ColumnLayout.tag = "ColumnLayout" columnLayout.tag = "ColumnLayout"
//ColumnLayout.systemClass = "ruiColumnLayout" columnLayout.normalize = normalizeColumnLayoutTag
columnLayout.changed = columnLayoutPropertyChanged
//columnLayout.systemClass = "ruiColumnLayout"
} }
func (columnLayout *columnLayoutData) String() string { func normalizeColumnLayoutTag(tag PropertyName) PropertyName {
return getViewString(columnLayout, nil) tag = defaultNormalize(tag)
}
func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case Gap: case Gap:
return ColumnGap return ColumnGap
@ -157,62 +154,28 @@ func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
return tag return tag
} }
func (columnLayout *columnLayoutData) Get(tag string) any { func columnLayoutPropertyChanged(view View, tag PropertyName) {
return columnLayout.get(columnLayout.normalizeTag(tag)) switch tag {
} case ColumnSeparator:
css := ""
func (columnLayout *columnLayoutData) Remove(tag string) { session := view.Session()
columnLayout.remove(columnLayout.normalizeTag(tag)) if value := view.getRaw(ColumnSeparator); value != nil {
} separator := value.(ColumnSeparatorProperty)
css = separator.cssValue(view.Session())
func (columnLayout *columnLayoutData) remove(tag string) {
columnLayout.viewsContainerData.remove(tag)
if columnLayout.created {
switch tag {
case ColumnCount, ColumnWidth, ColumnGap:
columnLayout.session.updateCSSProperty(columnLayout.htmlID(), tag, "")
case ColumnSeparator:
columnLayout.session.updateCSSProperty(columnLayout.htmlID(), "column-rule", "")
} }
} session.updateCSSProperty(view.htmlID(), "column-rule", css)
}
func (columnLayout *columnLayoutData) Set(tag string, value any) bool { case ColumnCount:
return columnLayout.set(columnLayout.normalizeTag(tag), value) session := view.Session()
} if count, ok := intProperty(view, tag, session, 0); ok && count > 0 {
session.updateCSSProperty(view.htmlID(), string(ColumnCount), strconv.Itoa(count))
func (columnLayout *columnLayoutData) set(tag string, value any) bool { } else {
if value == nil { session.updateCSSProperty(view.htmlID(), string(ColumnCount), "auto")
columnLayout.remove(tag)
return true
}
if !columnLayout.viewsContainerData.set(tag, value) {
return false
}
if columnLayout.created {
switch tag {
case ColumnSeparator:
css := ""
session := columnLayout.Session()
if val, ok := columnLayout.properties[ColumnSeparator]; ok {
separator := val.(ColumnSeparatorProperty)
css = separator.cssValue(columnLayout.Session())
}
session.updateCSSProperty(columnLayout.htmlID(), "column-rule", css)
case ColumnCount:
session := columnLayout.Session()
if count, ok := intProperty(columnLayout, tag, session, 0); ok && count > 0 {
session.updateCSSProperty(columnLayout.htmlID(), tag, strconv.Itoa(count))
} else {
session.updateCSSProperty(columnLayout.htmlID(), tag, "auto")
}
} }
default:
viewsContainerPropertyChanged(view, tag)
} }
return true
} }
// GetColumnCount returns int value which specifies number of columns into which the content of // GetColumnCount returns int value which specifies number of columns into which the content of

View File

@ -18,14 +18,14 @@ type ColumnSeparatorProperty interface {
} }
type columnSeparatorProperty struct { type columnSeparatorProperty struct {
propertyList dataProperty
} }
func newColumnSeparatorProperty(value any) ColumnSeparatorProperty { func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
if value == nil { if value == nil {
separator := new(columnSeparatorProperty) separator := new(columnSeparatorProperty)
separator.properties = map[string]any{} separator.init()
return separator return separator
} }
@ -35,17 +35,18 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
case DataObject: case DataObject:
separator := new(columnSeparatorProperty) separator := new(columnSeparatorProperty)
separator.properties = map[string]any{} separator.init()
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if val, ok := value.PropertyValue(tag); ok && val != "" { if val, ok := value.PropertyValue(string(tag)); ok && val != "" {
separator.set(tag, value) propertiesSet(separator, tag, value)
} }
} }
return separator return separator
case ViewBorder: case ViewBorder:
separator := new(columnSeparatorProperty) separator := new(columnSeparatorProperty)
separator.properties = map[string]any{ separator.init()
separator.properties = map[PropertyName]any{
Style: value.Style, Style: value.Style,
Width: value.Width, Width: value.Width,
ColorTag: value.Color, ColorTag: value.Color,
@ -67,9 +68,9 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
// "width" (Width). Determines the line thickness (SizeUnit). // "width" (Width). Determines the line thickness (SizeUnit).
func NewColumnSeparator(params Params) ColumnSeparatorProperty { func NewColumnSeparator(params Params) ColumnSeparatorProperty {
separator := new(columnSeparatorProperty) separator := new(columnSeparatorProperty)
separator.properties = map[string]any{} separator.init()
if params != nil { if params != nil {
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := params[tag]; ok && value != nil { if value, ok := params[tag]; ok && value != nil {
separator.Set(tag, value) separator.Set(tag, value)
} }
@ -78,8 +79,14 @@ func NewColumnSeparator(params Params) ColumnSeparatorProperty {
return separator return separator
} }
func (separator *columnSeparatorProperty) normalizeTag(tag string) string { func (separator *columnSeparatorProperty) init() {
tag = strings.ToLower(tag) separator.dataProperty.init()
separator.normalize = normalizeVolumnSeparatorTag
separator.supportedProperties = []PropertyName{Style, Width, ColorTag}
}
func normalizeVolumnSeparatorTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag { switch tag {
case ColumnSeparatorStyle, "separator-style": case ColumnSeparatorStyle, "separator-style":
return Style return Style
@ -97,12 +104,12 @@ func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
func (separator *columnSeparatorProperty) writeString(buffer *strings.Builder, indent string) { func (separator *columnSeparatorProperty) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ") buffer.WriteString("_{ ")
comma := false comma := false
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := separator.properties[tag]; ok { if value, ok := separator.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, BorderStyle, value, indent) writePropertyValue(buffer, BorderStyle, value, indent)
comma = true comma = true
@ -116,47 +123,12 @@ func (separator *columnSeparatorProperty) String() string {
return runStringWriter(separator) return runStringWriter(separator)
} }
func (separator *columnSeparatorProperty) Remove(tag string) { func getColumnSeparatorProperty(properties Properties) ColumnSeparatorProperty {
if val := properties.getRaw(ColumnSeparator); val != nil {
switch tag = separator.normalizeTag(tag); tag { if separator, ok := val.(ColumnSeparatorProperty); ok {
case Style, Width, ColorTag: return separator
delete(separator.properties, tag) }
default:
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
} }
}
func (separator *columnSeparatorProperty) Set(tag string, value any) bool {
tag = separator.normalizeTag(tag)
if value == nil {
separator.remove(tag)
return true
}
switch tag {
case Style:
return separator.setEnumProperty(Style, value, enumProperties[BorderStyle].values)
case Width:
return separator.setSizeProperty(Width, value)
case ColorTag:
return separator.setColorProperty(ColorTag, value)
}
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
return false
}
func (separator *columnSeparatorProperty) Get(tag string) any {
tag = separator.normalizeTag(tag)
if result, ok := separator.properties[tag]; ok {
return result
}
return nil return nil
} }

View File

@ -36,6 +36,9 @@ func InitCustomView(customView CustomView, tag string, session Session, params P
return true return true
} }
func (customView *CustomViewData) init(session Session) {
}
// SuperView returns a super view // SuperView returns a super view
func (customView *CustomViewData) SuperView() View { func (customView *CustomViewData) SuperView() View {
return customView.superView return customView.superView
@ -57,29 +60,36 @@ func (customView *CustomViewData) setTag(tag string) {
// Get returns a value of the property with name defined by the argument. // Get returns a value of the property with name defined by the argument.
// The type of return value depends on the property. If the property is not set then nil is returned. // The type of return value depends on the property. If the property is not set then nil is returned.
func (customView *CustomViewData) Get(tag string) any { func (customView *CustomViewData) Get(tag PropertyName) any {
return customView.superView.Get(tag) return customView.superView.Get(tag)
} }
func (customView *CustomViewData) getRaw(tag string) any { func (customView *CustomViewData) getRaw(tag PropertyName) any {
return customView.superView.getRaw(tag) return customView.superView.getRaw(tag)
} }
func (customView *CustomViewData) setRaw(tag string, value any) { func (customView *CustomViewData) setRaw(tag PropertyName, value any) {
customView.superView.setRaw(tag, value) customView.superView.setRaw(tag, value)
} }
func (customView *CustomViewData) setContent(value any) bool {
if container, ok := customView.superView.(ViewsContainer); ok {
return container.setContent(value)
}
return false
}
// Set sets the value (second argument) of the property with name defined by the first argument. // Set 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 // 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 // a description of the error is written to the log
func (customView *CustomViewData) Set(tag string, value any) bool { func (customView *CustomViewData) Set(tag PropertyName, value any) bool {
return customView.superView.Set(tag, value) return customView.superView.Set(tag, value)
} }
// SetAnimated sets the value (second argument) of the property with name defined by the first argument. // 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 // 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 // a description of the error is written to the log
func (customView *CustomViewData) SetAnimated(tag string, value any, animation Animation) bool { func (customView *CustomViewData) SetAnimated(tag PropertyName, value any, animation Animation) bool {
return customView.superView.SetAnimated(tag, value, animation) return customView.superView.SetAnimated(tag, value, animation)
} }
@ -88,20 +98,24 @@ func (customView *CustomViewData) SetParams(params Params) bool {
} }
// SetChangeListener set the function to track the change of the View property // SetChangeListener set the function to track the change of the View property
func (customView *CustomViewData) SetChangeListener(tag string, listener func(View, string)) { func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
customView.superView.SetChangeListener(tag, listener) customView.superView.SetChangeListener(tag, listener)
} }
// Remove removes the property with name defined by the argument // Remove removes the property with name defined by the argument
func (customView *CustomViewData) Remove(tag string) { func (customView *CustomViewData) Remove(tag PropertyName) {
customView.superView.Remove(tag) customView.superView.Remove(tag)
} }
// AllTags returns an array of the set properties // AllTags returns an array of the set properties
func (customView *CustomViewData) AllTags() []string { func (customView *CustomViewData) AllTags() []PropertyName {
return customView.superView.AllTags() return customView.superView.AllTags()
} }
func (customView *CustomViewData) empty() bool {
return customView.superView.empty()
}
// Clear removes all properties // Clear removes all properties
func (customView *CustomViewData) Clear() { func (customView *CustomViewData) Clear() {
customView.superView.Clear() customView.superView.Clear()
@ -182,7 +196,7 @@ func (customView *CustomViewData) onItemResize(self View, index string, x, y, wi
customView.superView.onItemResize(customView.superView, index, x, y, width, height) customView.superView.onItemResize(customView.superView, index, x, y, width, height)
} }
func (customView *CustomViewData) handleCommand(self View, command string, data DataObject) bool { func (customView *CustomViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
return customView.superView.handleCommand(customView.superView, command, data) return customView.superView.handleCommand(customView.superView, command, data)
} }
@ -210,6 +224,10 @@ func (customView *CustomViewData) htmlProperties(self View, buffer *strings.Buil
customView.superView.htmlProperties(customView.superView, buffer) customView.superView.htmlProperties(customView.superView, buffer)
} }
func (customView *CustomViewData) htmlDisabledProperty() bool {
return customView.superView.htmlDisabledProperty()
}
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) { func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
customView.superView.cssStyle(customView.superView, builder) customView.superView.cssStyle(customView.superView, builder)
} }
@ -274,9 +292,9 @@ func (customView *CustomViewData) ViewIndex(view View) int {
return -1 return -1
} }
func (customView *CustomViewData) exscludeTags() []string { func (customView *CustomViewData) exscludeTags() []PropertyName {
if customView.superView != nil { if customView.superView != nil {
exsclude := []string{} exsclude := []PropertyName{}
for tag, value := range customView.defaultParams { for tag, value := range customView.defaultParams {
if value == customView.superView.getRaw(tag) { if value == customView.superView.getRaw(tag) {
exsclude = append(exsclude, tag) exsclude = append(exsclude, tag)
@ -290,7 +308,10 @@ func (customView *CustomViewData) exscludeTags() []string {
// String convert internal representation of a [CustomViewData] into a string. // String convert internal representation of a [CustomViewData] into a string.
func (customView *CustomViewData) String() string { func (customView *CustomViewData) String() string {
if customView.superView != nil { if customView.superView != nil {
return getViewString(customView, customView.exscludeTags()) buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
writeViewStyle(customView.tag, customView, buffer, "", customView.exscludeTags())
return buffer.String()
} }
return customView.tag + " { }" return customView.tag + " { }"
} }
@ -302,7 +323,7 @@ func (customView *CustomViewData) setScroll(x, y, width, height float64) {
} }
// Transition returns the transition animation of the property(tag). Returns nil is there is no transition animation. // Transition returns the transition animation of the property(tag). Returns nil is there is no transition animation.
func (customView *CustomViewData) Transition(tag string) Animation { func (customView *CustomViewData) Transition(tag PropertyName) Animation {
if customView.superView != nil { if customView.superView != nil {
return customView.superView.Transition(tag) return customView.superView.Transition(tag)
} }
@ -310,17 +331,17 @@ func (customView *CustomViewData) Transition(tag string) Animation {
} }
// Transitions returns a map of transition animations. The result is always non-nil. // Transitions returns a map of transition animations. The result is always non-nil.
func (customView *CustomViewData) Transitions() map[string]Animation { func (customView *CustomViewData) Transitions() map[PropertyName]Animation {
if customView.superView != nil { if customView.superView != nil {
return customView.superView.Transitions() return customView.superView.Transitions()
} }
return map[string]Animation{} return map[PropertyName]Animation{}
} }
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and // SetTransition sets the transition animation for the property if "animation" argument is not nil, and
// removes the transition animation of the property if "animation" argument is nil. // removes the transition animation of the property if "animation" argument is nil.
// The "tag" argument is the property name. // The "tag" argument is the property name.
func (customView *CustomViewData) SetTransition(tag string, animation Animation) { func (customView *CustomViewData) SetTransition(tag PropertyName, animation Animation) {
if customView.superView != nil { if customView.superView != nil {
customView.superView.SetTransition(tag, animation) customView.superView.SetTransition(tag, animation)
} }

View File

@ -212,12 +212,12 @@ func (object *dataObject) ToParams() Params {
switch node.Type() { switch node.Type() {
case TextNode: case TextNode:
if text := node.Text(); text != "" { if text := node.Text(); text != "" {
params[node.Tag()] = text params[PropertyName(node.Tag())] = text
} }
case ObjectNode: case ObjectNode:
if obj := node.Object(); obj != nil { if obj := node.Object(); obj != nil {
params[node.Tag()] = node.Object() params[PropertyName(node.Tag())] = node.Object()
} }
case ArrayNode: case ArrayNode:
@ -234,7 +234,7 @@ func (object *dataObject) ToParams() Params {
} }
} }
if len(array) > 0 { if len(array) > 0 {
params[node.Tag()] = array params[PropertyName(node.Tag())] = array
} }
} }
} }

View File

@ -1,6 +1,11 @@
package rui package rui
import "strings" import (
"fmt"
"strconv"
"strings"
"time"
)
const ( const (
// DataList is the constant for "data-list" property tag. // DataList is the constant for "data-list" property tag.
@ -98,23 +103,14 @@ const (
// `[]Color` - An array of color values which will be converted to a string array. // `[]Color` - An array of color values which will be converted to a string array.
// `[]SizeUnit` - an array of size unit values which will be converted to a string array. // `[]SizeUnit` - an array of size unit values which will be converted to a string array.
// `[]any` - this array must contain only types which were listed in Types section. // `[]any` - this array must contain only types which were listed in Types section.
DataList = "data-list" DataList PropertyName = "data-list"
) )
type dataList struct { func dataListID(view View) string {
dataList []string
dataListHtml bool
}
func (list *dataList) dataListInit() {
list.dataList = []string{}
}
func (list *dataList) dataListID(view View) string {
return view.htmlID() + "-datalist" return view.htmlID() + "-datalist"
} }
func (list *dataList) normalizeDataListTag(tag string) string { func normalizeDataListTag(tag PropertyName) PropertyName {
switch tag { switch tag {
case "datalist": case "datalist":
return DataList return DataList
@ -123,6 +119,210 @@ func (list *dataList) normalizeDataListTag(tag string) string {
return tag return tag
} }
func setDataList(properties Properties, value any, dateTimeFormat string) []PropertyName {
if items, ok := anyToStringArray(value, timeFormat); ok {
properties.setRaw(DataList, items)
return []PropertyName{DataList}
}
notCompatibleType(DataList, value)
return nil
}
func anyToStringArray(value any, dateTimeFormat string) ([]string, bool) {
switch value := value.(type) {
case string:
return []string{value}, true
case []string:
return value, true
case []DataValue:
items := make([]string, 0, len(value))
for _, val := range value {
if !val.IsObject() {
items = append(items, val.Value())
}
}
return items, true
case []fmt.Stringer:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []Color:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []SizeUnit:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []AngleUnit:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []float32:
items := make([]string, len(value))
for i, val := range value {
items[i] = fmt.Sprintf("%g", float64(val))
}
return items, true
case []float64:
items := make([]string, len(value))
for i, val := range value {
items[i] = fmt.Sprintf("%g", val)
}
return items, true
case []int:
return intArrayToStringArray(value), true
case []uint:
return intArrayToStringArray(value), true
case []int8:
return intArrayToStringArray(value), true
case []uint8:
return intArrayToStringArray(value), true
case []int16:
return intArrayToStringArray(value), true
case []uint16:
return intArrayToStringArray(value), true
case []int32:
return intArrayToStringArray(value), true
case []uint32:
return intArrayToStringArray(value), true
case []int64:
return intArrayToStringArray(value), true
case []uint64:
return intArrayToStringArray(value), true
case []bool:
items := make([]string, len(value))
for i, val := range value {
if val {
items[i] = "true"
} else {
items[i] = "false"
}
}
return items, true
case []time.Time:
if dateTimeFormat == "" {
dateTimeFormat = dateFormat + " " + timeFormat
}
items := make([]string, len(value))
for i, val := range value {
items[i] = val.Format(dateTimeFormat)
}
return items, true
case []any:
items := make([]string, 0, len(value))
for _, v := range value {
switch val := v.(type) {
case string:
items = append(items, val)
case fmt.Stringer:
items = append(items, val.String())
case bool:
if val {
items = append(items, "true")
} else {
items = append(items, "false")
}
case float32:
items = append(items, fmt.Sprintf("%g", float64(val)))
case float64:
items = append(items, fmt.Sprintf("%g", val))
case rune:
items = append(items, string(val))
default:
if n, ok := isInt(v); ok {
items = append(items, strconv.Itoa(n))
} else {
return []string{}, false
}
}
}
return items, true
}
return []string{}, false
}
func getDataListProperty(properties Properties) []string {
if value := properties.getRaw(DataList); value != nil {
if items, ok := value.([]string); ok {
return items
}
}
return nil
}
func dataListHtmlSubviews(view View, buffer *strings.Builder, normalizeItem func(text string, session Session) string) {
if items := getDataListProperty(view); len(items) > 0 {
session := view.Session()
buffer.WriteString(`<datalist id="`)
buffer.WriteString(dataListID(view))
buffer.WriteString(`">`)
for _, text := range items {
text = normalizeItem(text, session)
if strings.ContainsRune(text, '"') {
text = strings.ReplaceAll(text, `"`, `&#34;`)
}
if strings.ContainsRune(text, '\n') {
text = strings.ReplaceAll(text, "\n", `\n`)
}
buffer.WriteString(`<option value="`)
buffer.WriteString(text)
buffer.WriteString(`"></option>`)
}
buffer.WriteString(`</datalist>`)
}
}
func dataListHtmlProperties(view View, buffer *strings.Builder) {
if len(getDataListProperty(view)) > 0 {
buffer.WriteString(` list="`)
buffer.WriteString(dataListID(view))
buffer.WriteString(`"`)
}
}
/*
func (list *dataList) setDataList(view View, value any, created bool) bool { func (list *dataList) setDataList(view View, value any, created bool) bool {
items, ok := anyToStringArray(value) items, ok := anyToStringArray(value)
if !ok { if !ok {
@ -133,7 +333,7 @@ func (list *dataList) setDataList(view View, value any, created bool) bool {
list.dataList = items list.dataList = items
if created { if created {
session := view.Session() session := view.Session()
dataListID := list.dataListID(view) dataListID := dataListID(view)
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
@ -162,7 +362,7 @@ func (list *dataList) dataListHtmlSubviews(view View, buffer *strings.Builder) {
func (list *dataList) dataListHtmlCode(view View, buffer *strings.Builder) { func (list *dataList) dataListHtmlCode(view View, buffer *strings.Builder) {
buffer.WriteString(`<datalist id="`) buffer.WriteString(`<datalist id="`)
buffer.WriteString(list.dataListID(view)) buffer.WriteString(dataListID(view))
buffer.WriteString(`">`) buffer.WriteString(`">`)
list.dataListItemsHtml(buffer) list.dataListItemsHtml(buffer)
buffer.WriteString(`</datalist>`) buffer.WriteString(`</datalist>`)
@ -185,10 +385,11 @@ func (list *dataList) dataListItemsHtml(buffer *strings.Builder) {
func (list *dataList) dataListHtmlProperties(view View, buffer *strings.Builder) { func (list *dataList) dataListHtmlProperties(view View, buffer *strings.Builder) {
if len(list.dataList) > 0 { if len(list.dataList) > 0 {
buffer.WriteString(` list="`) buffer.WriteString(` list="`)
buffer.WriteString(list.dataListID(view)) buffer.WriteString(dataListID(view))
buffer.WriteString(`"`) buffer.WriteString(`"`)
} }
} }
*/
// GetDataList returns the data list of an editor. // GetDataList returns the data list of an editor.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
@ -198,11 +399,7 @@ func GetDataList(view View, subviewID ...string) []string {
} }
if view != nil { if view != nil {
if value := view.Get(DataList); value != nil { return getDataListProperty(view)
if list, ok := value.([]string); ok {
return list
}
}
} }
return []string{} return []string{}

View File

@ -27,7 +27,7 @@ const (
// `func(newDate time.Time)`, // `func(newDate time.Time)`,
// `func(picker rui.DatePicker)`, // `func(picker rui.DatePicker)`,
// `func()`. // `func()`.
DateChangedEvent = "date-changed" DateChangedEvent PropertyName = "date-changed"
// DatePickerMin is the constant for "date-picker-min" property tag. // DatePickerMin is the constant for "date-picker-min" property tag.
// //
@ -50,7 +50,7 @@ const (
// "MM/DD/YYYY" - "01/02/2024". // "MM/DD/YYYY" - "01/02/2024".
// "MM/DD/YY" - "01/02/24". // "MM/DD/YY" - "01/02/24".
// "MMDDYY" - "010224". // "MMDDYY" - "010224".
DatePickerMin = "date-picker-min" DatePickerMin PropertyName = "date-picker-min"
// DatePickerMax is the constant for "date-picker-max" property tag. // DatePickerMax is the constant for "date-picker-max" property tag.
// //
@ -73,7 +73,7 @@ const (
// "MM/DD/YYYY" - "01/02/2024". // "MM/DD/YYYY" - "01/02/2024".
// "MM/DD/YY" - "01/02/24". // "MM/DD/YY" - "01/02/24".
// "MMDDYY" - "010224". // "MMDDYY" - "010224".
DatePickerMax = "date-picker-max" DatePickerMax PropertyName = "date-picker-max"
// DatePickerStep is the constant for "date-picker-step" property tag. // DatePickerStep is the constant for "date-picker-step" property tag.
// //
@ -84,7 +84,7 @@ const (
// //
// Values: // Values:
// >= `0` or >= "0" - Step value in days used to increment or decrement date. // >= `0` or >= "0" - Step value in days used to increment or decrement date.
DatePickerStep = "date-picker-step" DatePickerStep PropertyName = "date-picker-step"
// DatePickerValue is the constant for "date-picker-value" property tag. // DatePickerValue is the constant for "date-picker-value" property tag.
// //
@ -107,7 +107,7 @@ const (
// "MM/DD/YYYY" - "01/02/2024". // "MM/DD/YYYY" - "01/02/2024".
// "MM/DD/YY" - "01/02/24". // "MM/DD/YY" - "01/02/24".
// "MMDDYY" - "010224". // "MMDDYY" - "010224".
DatePickerValue = "date-picker-value" DatePickerValue PropertyName = "date-picker-value"
dateFormat = "2006-01-02" dateFormat = "2006-01-02"
) )
@ -119,8 +119,6 @@ type DatePicker interface {
type datePickerData struct { type datePickerData struct {
viewData viewData
dataList
dateChangedListeners []func(DatePicker, time.Time, time.Time)
} }
// NewDatePicker create new DatePicker object and return it // NewDatePicker create new DatePicker object and return it
@ -132,248 +130,164 @@ func NewDatePicker(session Session, params Params) DatePicker {
} }
func newDatePicker(session Session) View { func newDatePicker(session Session) View {
return NewDatePicker(session, nil) return new(datePickerData) // NewDatePicker(session, nil)
} }
func (picker *datePickerData) init(session Session) { func (picker *datePickerData) init(session Session) {
picker.viewData.init(session) picker.viewData.init(session)
picker.tag = "DatePicker" picker.tag = "DatePicker"
picker.hasHtmlDisabled = true picker.hasHtmlDisabled = true
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){} picker.normalize = normalizeDatePickerTag
picker.dataListInit() picker.set = datePickerSet
} picker.changed = datePickerPropertyChanged
func (picker *datePickerData) String() string {
return getViewString(picker, nil)
} }
func (picker *datePickerData) Focusable() bool { func (picker *datePickerData) Focusable() bool {
return true return true
} }
func (picker *datePickerData) normalizeTag(tag string) string { func normalizeDatePickerTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case Type, Min, Max, Step, Value: case Type, Min, Max, Step, Value:
return "date-picker-" + tag return "date-picker-" + tag
} }
return tag return normalizeDataListTag(tag)
} }
func (picker *datePickerData) Remove(tag string) { func stringToDate(value string) (time.Time, bool) {
picker.remove(picker.normalizeTag(tag)) format := "20060102"
} if strings.ContainsRune(value, '-') {
if part := strings.Split(value, "-"); len(part) == 3 {
func (picker *datePickerData) remove(tag string) { if part[0] != "" && part[0][0] > '9' {
switch tag { if len(part[2]) == 2 {
case DateChangedEvent: format = "Jan-02-06"
if len(picker.dateChangedListeners) > 0 { } else {
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){} format = "Jan-02-2006"
picker.propertyChangedEvent(tag) }
} } else if part[1] != "" && part[1][0] > '9' {
return format = "02-Jan-2006"
} else {
case DatePickerMin: format = "2006-01-02"
delete(picker.properties, DatePickerMin)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Min)
}
case DatePickerMax:
delete(picker.properties, DatePickerMax)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Max)
}
case DatePickerStep:
delete(picker.properties, DatePickerStep)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Step)
}
case DatePickerValue:
if _, ok := picker.properties[DatePickerValue]; ok {
oldDate := GetDatePickerValue(picker)
delete(picker.properties, DatePickerValue)
date := GetDatePickerValue(picker)
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
} }
for _, listener := range picker.dateChangedListeners { }
listener(picker, date, oldDate) } else if strings.ContainsRune(value, ' ') {
if part := strings.Split(value, " "); len(part) == 3 {
if part[0] != "" && part[0][0] > '9' {
format = "January 02, 2006"
} else {
format = "02 January 2006"
} }
} else {
return
} }
} else if strings.ContainsRune(value, '/') {
case DataList: if part := strings.Split(value, "/"); len(part) == 3 {
if len(picker.dataList.dataList) > 0 { if len(part[2]) == 2 {
picker.setDataList(picker, []string{}, true) format = "01/02/06"
} else {
format = "01/02/2006"
}
} }
} else if len(value) == 6 {
default: format = "010206"
picker.viewData.remove(tag)
return
}
picker.propertyChangedEvent(tag)
}
func (picker *datePickerData) Set(tag string, value any) bool {
return picker.set(picker.normalizeTag(tag), value)
}
func (picker *datePickerData) set(tag string, value any) bool {
if value == nil {
picker.remove(tag)
return true
} }
setTimeValue := func(tag string) (time.Time, bool) { if date, err := time.Parse(format, value); err == nil {
return date, true
}
return time.Now(), false
}
func datePickerSet(view View, tag PropertyName, value any) []PropertyName {
setDateValue := func(tag PropertyName) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case time.Time: case time.Time:
picker.properties[tag] = value view.setRaw(tag, value)
return value, true return []PropertyName{tag}
case string: case string:
if text, ok := picker.Session().resolveConstants(value); ok { if isConstantName(value) {
format := "20060102" view.setRaw(tag, value)
if strings.ContainsRune(text, '-') { return []PropertyName{tag}
if part := strings.Split(text, "-"); len(part) == 3 { }
if part[0] != "" && part[0][0] > '9' {
if len(part[2]) == 2 {
format = "Jan-02-06"
} else {
format = "Jan-02-2006"
}
} else if part[1] != "" && part[1][0] > '9' {
format = "02-Jan-2006"
} else {
format = "2006-01-02"
}
}
} else if strings.ContainsRune(text, ' ') {
if part := strings.Split(text, " "); len(part) == 3 {
if part[0] != "" && part[0][0] > '9' {
format = "January 02, 2006"
} else {
format = "02 January 2006"
}
}
} else if strings.ContainsRune(text, '/') {
if part := strings.Split(text, "/"); len(part) == 3 {
if len(part[2]) == 2 {
format = "01/02/06"
} else {
format = "01/02/2006"
}
}
} else if len(text) == 6 {
format = "010206"
}
if date, err := time.Parse(format, text); err == nil { if date, ok := stringToDate(value); ok {
picker.properties[tag] = value view.setRaw(tag, date)
return date, true return []PropertyName{tag}
}
} }
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return time.Now(), false return nil
} }
switch tag { switch tag {
case DatePickerMin, DatePickerMax:
return setDateValue(tag)
case DatePickerStep:
return setIntProperty(view, DatePickerStep, value)
case DatePickerValue:
view.setRaw("old-date", GetDatePickerValue(view))
return setDateValue(tag)
case DateChangedEvent:
return setEventWithOldListener[DatePicker, time.Time](view, tag, value)
case DataList:
return setDataList(view, value, dateFormat)
}
return viewSet(view, tag, value)
}
func datePickerPropertyChanged(view View, tag PropertyName) {
session := view.Session()
switch tag {
case DatePickerMin: case DatePickerMin:
old, oldOK := getDateProperty(picker, DatePickerMin, Min) if date, ok := GetDatePickerMin(view); ok {
if date, ok := setTimeValue(DatePickerMin); ok { session.updateProperty(view.htmlID(), "min", date.Format(dateFormat))
if !oldOK || date != old { } else {
if picker.created { session.removeProperty(view.htmlID(), "min")
picker.session.updateProperty(picker.htmlID(), Min, date.Format(dateFormat))
}
picker.propertyChangedEvent(tag)
}
return true
} }
case DatePickerMax: case DatePickerMax:
old, oldOK := getDateProperty(picker, DatePickerMax, Max) if date, ok := GetDatePickerMax(view); ok {
if date, ok := setTimeValue(DatePickerMax); ok { session.updateProperty(view.htmlID(), "max", date.Format(dateFormat))
if !oldOK || date != old { } else {
if picker.created { session.removeProperty(view.htmlID(), "max")
picker.session.updateProperty(picker.htmlID(), Max, date.Format(dateFormat))
}
picker.propertyChangedEvent(tag)
}
return true
} }
case DatePickerStep: case DatePickerStep:
oldStep := GetDatePickerStep(picker) if step := GetDatePickerStep(view); step > 0 {
if picker.setIntProperty(DatePickerStep, value) { session.updateProperty(view.htmlID(), "step", strconv.Itoa(step))
if step := GetDatePickerStep(picker); oldStep != step { } else {
if picker.created { session.removeProperty(view.htmlID(), "step")
if step > 0 {
picker.session.updateProperty(picker.htmlID(), Step, strconv.Itoa(step))
} else {
picker.session.removeProperty(picker.htmlID(), Step)
}
}
picker.propertyChangedEvent(tag)
}
return true
} }
case DatePickerValue: case DatePickerValue:
oldDate := GetDatePickerValue(picker) date := GetDatePickerValue(view)
if date, ok := setTimeValue(DatePickerValue); ok { session.callFunc("setInputValue", view.htmlID(), date.Format(dateFormat))
if date != oldDate {
if picker.created { if listeners := GetDateChangedListeners(view); len(listeners) > 0 {
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat)) oldDate := time.Now()
if value := view.getRaw("old-date"); value != nil {
if date, ok := value.(time.Time); ok {
oldDate = date
} }
for _, listener := range picker.dateChangedListeners {
listener(picker, date, oldDate)
}
picker.propertyChangedEvent(tag)
} }
return true for _, listener := range listeners {
listener(view, date, oldDate)
}
} }
case DateChangedEvent:
listeners, ok := valueToEventWithOldListeners[DatePicker, time.Time](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(DatePicker, time.Time, time.Time){}
}
picker.dateChangedListeners = listeners
picker.propertyChangedEvent(tag)
return true
case DataList:
return picker.setDataList(picker, value, picker.created)
default: default:
return picker.viewData.set(tag, value) viewPropertyChanged(view, tag)
}
return false
}
func (picker *datePickerData) Get(tag string) any {
return picker.get(picker.normalizeTag(tag))
}
func (picker *datePickerData) get(tag string) any {
switch tag {
case DateChangedEvent:
return picker.dateChangedListeners
case DataList:
return picker.dataList.dataList
default:
return picker.viewData.get(tag)
} }
} }
@ -382,7 +296,13 @@ func (picker *datePickerData) htmlTag() string {
} }
func (picker *datePickerData) htmlSubviews(self View, buffer *strings.Builder) { func (picker *datePickerData) htmlSubviews(self View, buffer *strings.Builder) {
picker.dataListHtmlSubviews(self, buffer) dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
text, _ = session.resolveConstants(text)
if date, ok := stringToDate(text); ok {
return date.Format(dateFormat)
}
return text
})
} }
func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) { func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) {
@ -417,10 +337,10 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder)
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`) buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
} }
picker.dataListHtmlProperties(picker, buffer) dataListHtmlProperties(picker, buffer)
} }
func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool { func (picker *datePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyValue("text"); ok {
@ -428,9 +348,12 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
oldValue := GetDatePickerValue(picker) oldValue := GetDatePickerValue(picker)
picker.properties[DatePickerValue] = value picker.properties[DatePickerValue] = value
if value != oldValue { if value != oldValue {
for _, listener := range picker.dateChangedListeners { for _, listener := range GetDateChangedListeners(picker) {
listener(picker, value, oldValue) listener(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[DatePickerValue]; ok {
listener(picker, DatePickerValue)
}
} }
} }
} }
@ -440,7 +363,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
return picker.viewData.handleCommand(self, command, data) return picker.viewData.handleCommand(self, command, data)
} }
func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) { func getDateProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool) {
valueToTime := func(value any) (time.Time, bool) { valueToTime := func(value any) (time.Time, bool) {
if value != nil { if value != nil {
switch value := value.(type) { switch value := value.(type) {
@ -449,7 +372,7 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
case string: case string:
if text, ok := view.Session().resolveConstants(value); ok { if text, ok := view.Session().resolveConstants(value); ok {
if result, err := time.Parse(dateFormat, text); err == nil { if result, ok := stringToDate(text); ok {
return result, true return result, true
} }
} }
@ -463,9 +386,11 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
return result, true return result, true
} }
if value := valueFromStyle(view, shortTag); value != nil { for _, tag := range []PropertyName{mainTag, shortTag} {
if result, ok := valueToTime(value); ok { if value := valueFromStyle(view, tag); value != nil {
return result, true if result, ok := valueToTime(value); ok {
return result, true
}
} }
} }
} }

View File

@ -13,7 +13,7 @@ const (
// //
// `string` - Summary as a text. // `string` - Summary as a text.
// `View` - Summary as a view, in this case it can be quite complex if needed. // `View` - Summary as a view, in this case it can be quite complex if needed.
Summary = "summary" Summary PropertyName = "summary"
// Expanded is the constant for "expanded" property tag. // Expanded is the constant for "expanded" property tag.
// //
@ -25,7 +25,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Content is visible. // `true` or `1` or "true", "yes", "on", "1" - Content is visible.
// `false` or `0` or "false", "no", "off", "0" - Content is collapsed(hidden). // `false` or `0` or "false", "no", "off", "0" - Content is collapsed(hidden).
Expanded = "expanded" Expanded PropertyName = "expanded"
) )
// DetailsView represent a DetailsView view, which is a collapsible container of views // DetailsView represent a DetailsView view, which is a collapsible container of views
@ -46,19 +46,21 @@ func NewDetailsView(session Session, params Params) DetailsView {
} }
func newDetailsView(session Session) View { func newDetailsView(session Session) View {
return NewDetailsView(session, nil) return new(detailsViewData)
} }
// Init initialize fields of DetailsView by default values // Init initialize fields of DetailsView by default values
func (detailsView *detailsViewData) init(session Session) { func (detailsView *detailsViewData) init(session Session) {
detailsView.viewsContainerData.init(session) detailsView.viewsContainerData.init(session)
detailsView.tag = "DetailsView" detailsView.tag = "DetailsView"
detailsView.set = detailsView.setFunc
detailsView.changed = detailsViewPropertyChanged
//detailsView.systemClass = "ruiDetailsView" //detailsView.systemClass = "ruiDetailsView"
} }
func (detailsView *detailsViewData) Views() []View { func (detailsView *detailsViewData) Views() []View {
views := detailsView.viewsContainerData.Views() views := detailsView.viewsContainerData.Views()
if summary := detailsView.get(Summary); summary != nil { if summary := detailsView.Get(Summary); summary != nil {
switch summary := summary.(type) { switch summary := summary.(type) {
case View: case View:
return append([]View{summary}, views...) return append([]View{summary}, views...)
@ -67,94 +69,53 @@ func (detailsView *detailsViewData) Views() []View {
return views return views
} }
func (detailsView *detailsViewData) Remove(tag string) { func (detailsView *detailsViewData) setFunc(self View, tag PropertyName, value any) []PropertyName {
detailsView.remove(strings.ToLower(tag))
}
func (detailsView *detailsViewData) remove(tag string) {
detailsView.viewsContainerData.remove(tag)
if detailsView.created {
switch tag {
case Summary:
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
case Expanded:
detailsView.session.removeProperty(detailsView.htmlID(), "open")
}
}
}
func (detailsView *detailsViewData) Set(tag string, value any) bool {
return detailsView.set(strings.ToLower(tag), value)
}
func (detailsView *detailsViewData) set(tag string, value any) bool {
if value == nil {
detailsView.remove(tag)
return true
}
switch tag { switch tag {
case Summary: case Summary:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
detailsView.properties[Summary] = value detailsView.setRaw(Summary, value)
case View: case View:
detailsView.properties[Summary] = value detailsView.setRaw(Summary, value)
value.setParentID(detailsView.htmlID()) value.setParentID(detailsView.htmlID())
case DataObject: case DataObject:
if view := CreateViewFromObject(detailsView.Session(), value); view != nil { if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
detailsView.properties[Summary] = view detailsView.setRaw(Summary, view)
view.setParentID(detailsView.htmlID()) view.setParentID(detailsView.htmlID())
} else { } else {
return false return nil
} }
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
}
if detailsView.created {
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
} }
return []PropertyName{tag}
}
return detailsView.viewsContainerData.setFunc(detailsView, tag, value)
}
func detailsViewPropertyChanged(view View, tag PropertyName) {
switch tag {
case Summary:
updateInnerHTML(view.htmlID(), view.Session())
case Expanded: case Expanded:
if !detailsView.setBoolProperty(tag, value) { if IsDetailsExpanded(view) {
notCompatibleType(tag, value) view.Session().updateProperty(view.htmlID(), "open", "")
return false } else {
} view.Session().removeProperty(view.htmlID(), "open")
if detailsView.created {
if IsDetailsExpanded(detailsView) {
detailsView.session.updateProperty(detailsView.htmlID(), "open", "")
} else {
detailsView.session.removeProperty(detailsView.htmlID(), "open")
}
} }
case NotTranslate: case NotTranslate:
if !detailsView.viewData.set(tag, value) { updateInnerHTML(view.htmlID(), view.Session())
return false
}
if detailsView.created {
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
}
default: default:
return detailsView.viewsContainerData.Set(tag, value) viewsContainerPropertyChanged(view, tag)
} }
detailsView.propertyChangedEvent(tag)
return true
}
func (detailsView *detailsViewData) Get(tag string) any {
return detailsView.get(strings.ToLower(tag))
}
func (detailsView *detailsViewData) get(tag string) any {
return detailsView.viewsContainerData.get(tag)
} }
func (detailsView *detailsViewData) htmlTag() string { func (detailsView *detailsViewData) htmlTag() string {
@ -190,11 +151,13 @@ func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Buil
detailsView.viewsContainerData.htmlSubviews(self, buffer) detailsView.viewsContainerData.htmlSubviews(self, buffer)
} }
func (detailsView *detailsViewData) handleCommand(self View, command string, data DataObject) bool { func (detailsView *detailsViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
if command == "details-open" { if command == "details-open" {
if n, ok := dataIntProperty(data, "open"); ok { if n, ok := dataIntProperty(data, "open"); ok {
detailsView.properties[Expanded] = (n != 0) detailsView.properties[Expanded] = (n != 0)
detailsView.propertyChangedEvent(Expanded) if listener, ok := detailsView.changeListener[Current]; ok {
listener(detailsView, Current)
}
} }
return true return true
} }

View File

@ -1,7 +1,6 @@
package rui package rui
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@ -19,20 +18,15 @@ import (
// index - Index of a newly selected item. // index - Index of a newly selected item.
// //
// Allowed listener formats: // Allowed listener formats:
const DropDownEvent = "drop-down-event" const DropDownEvent PropertyName = "drop-down-event"
// DropDownList represent a DropDownList view // DropDownList represent a DropDownList view
type DropDownList interface { type DropDownList interface {
View View
getItems() []string
} }
type dropDownListData struct { type dropDownListData struct {
viewData viewData
items []string
disabledItems []any
itemSeparators []any
dropDownListener []func(DropDownList, int, int)
} }
// NewDropDownList create new DropDownList object and return it // NewDropDownList create new DropDownList object and return it
@ -44,167 +38,86 @@ func NewDropDownList(session Session, params Params) DropDownList {
} }
func newDropDownList(session Session) View { func newDropDownList(session Session) View {
return NewDropDownList(session, nil) return new(dropDownListData)
} }
func (list *dropDownListData) init(session Session) { func (list *dropDownListData) init(session Session) {
list.viewData.init(session) list.viewData.init(session)
list.tag = "DropDownList" list.tag = "DropDownList"
list.hasHtmlDisabled = true list.hasHtmlDisabled = true
list.items = []string{} list.normalize = normalizeDropDownListTag
list.disabledItems = []any{} list.set = dropDownListSet
list.itemSeparators = []any{} list.changed = dropDownListPropertyChanged
list.dropDownListener = []func(DropDownList, int, int){}
}
func (list *dropDownListData) String() string {
return getViewString(list, nil)
} }
func (list *dropDownListData) Focusable() bool { func (list *dropDownListData) Focusable() bool {
return true return true
} }
func (list *dropDownListData) Remove(tag string) { func normalizeDropDownListTag(tag PropertyName) PropertyName {
list.remove(strings.ToLower(tag)) tag = defaultNormalize(tag)
if tag == "separators" {
return ItemSeparators
}
return tag
} }
func (list *dropDownListData) remove(tag string) { func dropDownListSet(view View, tag PropertyName, value any) []PropertyName {
switch tag { switch tag {
case Items: case Items:
if len(list.items) > 0 { if items, ok := anyToStringArray(value, ""); ok {
list.items = []string{} return setArrayPropertyValue(view, tag, items)
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(tag)
} }
notCompatibleType(Items, value)
return nil
case DisabledItems: case DisabledItems, ItemSeparators:
if len(list.disabledItems) > 0 { if items, ok := parseIndicesArray(value); ok {
list.disabledItems = []any{} return setArrayPropertyValue(view, tag, items)
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(tag)
}
case ItemSeparators, "separators":
if len(list.itemSeparators) > 0 {
list.itemSeparators = []any{}
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(ItemSeparators)
} }
notCompatibleType(tag, value)
return nil
case DropDownEvent: case DropDownEvent:
if len(list.dropDownListener) > 0 { return setEventWithOldListener[DropDownList, int](view, tag, value)
list.dropDownListener = []func(DropDownList, int, int){}
list.propertyChangedEvent(tag)
}
case Current: case Current:
oldCurrent := GetCurrent(list) if view, ok := view.(View); ok {
delete(list.properties, Current) view.setRaw("old-current", GetCurrent(view))
if oldCurrent != 0 { }
if list.created { return setIntProperty(view, Current, value)
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0) }
return viewSet(view, tag, value)
}
func dropDownListPropertyChanged(view View, tag PropertyName) {
switch tag {
case Items, DisabledItems, ItemSeparators:
updateInnerHTML(view.htmlID(), view.Session())
case Current:
current := GetCurrent(view)
view.Session().callFunc("selectDropDownListItem", view.htmlID(), current)
if list, ok := view.(DropDownList); ok {
oldCurrent := -1
if value := view.getRaw("old-current"); value != nil {
if n, ok := value.(int); ok {
oldCurrent = n
}
}
for _, listener := range GetDropDownListeners(view) {
listener(list, current, oldCurrent)
} }
list.onSelectedItemChanged(0, oldCurrent)
} }
default: default:
list.viewData.remove(tag) viewPropertyChanged(view, tag)
} }
} }
func (list *dropDownListData) Set(tag string, value any) bool {
return list.set(strings.ToLower(tag), value)
}
func (list *dropDownListData) set(tag string, value any) bool {
if value == nil {
list.remove(tag)
return true
}
switch tag {
case Items:
return list.setItems(value)
case DisabledItems:
items, ok := list.parseIndicesArray(value)
if !ok {
notCompatibleType(tag, value)
return false
}
list.disabledItems = items
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(tag)
return true
case ItemSeparators, "separators":
items, ok := list.parseIndicesArray(value)
if !ok {
notCompatibleType(ItemSeparators, value)
return false
}
list.itemSeparators = items
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(ItemSeparators)
return true
case DropDownEvent:
listeners, ok := valueToEventWithOldListeners[DropDownList, int](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(DropDownList, int, int){}
}
list.dropDownListener = listeners
list.propertyChangedEvent(tag)
return true
case Current:
oldCurrent := GetCurrent(list)
if !list.setIntProperty(Current, value) {
return false
}
if current := GetCurrent(list); oldCurrent != current {
if list.created {
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
}
list.onSelectedItemChanged(current, oldCurrent)
}
return true
}
return list.viewData.set(tag, value)
}
func (list *dropDownListData) setItems(value any) bool {
items, ok := anyToStringArray(value)
if !ok {
notCompatibleType(Items, value)
return false
}
list.items = items
if list.created {
updateInnerHTML(list.htmlID(), list.session)
}
list.propertyChangedEvent(Items)
return true
}
func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](array []T) []string { func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](array []T) []string {
items := make([]string, len(array)) items := make([]string, len(array))
for i, val := range array { for i, val := range array {
@ -213,150 +126,11 @@ func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32
return items return items
} }
func anyToStringArray(value any) ([]string, bool) { func parseIndicesArray(value any) ([]any, bool) {
switch value := value.(type) { switch value := value.(type) {
case string: case int:
return []string{value}, true return []any{value}, true
case []string:
return value, true
case []DataValue:
items := make([]string, 0, len(value))
for _, val := range value {
if !val.IsObject() {
items = append(items, val.Value())
}
}
return items, true
case []fmt.Stringer:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []Color:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []SizeUnit:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []AngleUnit:
items := make([]string, len(value))
for i, str := range value {
items[i] = str.String()
}
return items, true
case []float32:
items := make([]string, len(value))
for i, val := range value {
items[i] = fmt.Sprintf("%g", float64(val))
}
return items, true
case []float64:
items := make([]string, len(value))
for i, val := range value {
items[i] = fmt.Sprintf("%g", val)
}
return items, true
case []int:
return intArrayToStringArray(value), true
case []uint:
return intArrayToStringArray(value), true
case []int8:
return intArrayToStringArray(value), true
case []uint8:
return intArrayToStringArray(value), true
case []int16:
return intArrayToStringArray(value), true
case []uint16:
return intArrayToStringArray(value), true
case []int32:
return intArrayToStringArray(value), true
case []uint32:
return intArrayToStringArray(value), true
case []int64:
return intArrayToStringArray(value), true
case []uint64:
return intArrayToStringArray(value), true
case []bool:
items := make([]string, len(value))
for i, val := range value {
if val {
items[i] = "true"
} else {
items[i] = "false"
}
}
return items, true
case []any:
items := make([]string, 0, len(value))
for _, v := range value {
switch val := v.(type) {
case string:
items = append(items, val)
case fmt.Stringer:
items = append(items, val.String())
case bool:
if val {
items = append(items, "true")
} else {
items = append(items, "false")
}
case float32:
items = append(items, fmt.Sprintf("%g", float64(val)))
case float64:
items = append(items, fmt.Sprintf("%g", val))
case rune:
items = append(items, string(val))
default:
if n, ok := isInt(v); ok {
items = append(items, strconv.Itoa(n))
} else {
return []string{}, false
}
}
}
return items, true
}
return []string{}, false
}
func (list *dropDownListData) parseIndicesArray(value any) ([]any, bool) {
switch value := value.(type) {
case []int: case []int:
items := make([]any, len(value)) items := make([]any, len(value))
for i, n := range value { for i, n := range value {
@ -365,108 +139,72 @@ func (list *dropDownListData) parseIndicesArray(value any) ([]any, bool) {
return items, true return items, true
case []any: case []any:
items := make([]any, len(value)) items := make([]any, 0, len(value))
for i, val := range value { for _, val := range value {
if val == nil { if val != nil {
return nil, false switch val := val.(type) {
} case string:
if isConstantName(val) {
switch val := val.(type) { items = append(items, val)
case string: } else if n, err := strconv.Atoi(val); err == nil {
if isConstantName(val) { items = append(items, n)
items[i] = val } else {
} else { return nil, false
n, err := strconv.Atoi(val) }
if err != nil {
default:
if n, ok := isInt(val); ok {
items = append(items, n)
} else {
return nil, false return nil, false
} }
items[i] = n
} }
default: }
if n, ok := isInt(val); ok { }
items[i] = n return items, true
case []string:
items := make([]any, 0, len(value))
for _, str := range value {
if str = strings.Trim(str, " \t"); str != "" {
if isConstantName(str) {
items = append(items, str)
} else if n, err := strconv.Atoi(str); err == nil {
items = append(items, n)
} else { } else {
return nil, false return nil, false
} }
} }
} }
return items, true return items, true
case string: case string:
values := strings.Split(value, ",") return parseIndicesArray(strings.Split(value, ","))
items := make([]any, len(values))
for i, str := range values {
str = strings.Trim(str, " ")
if str == "" {
return nil, false
}
if isConstantName(str) {
items[i] = str
} else {
n, err := strconv.Atoi(str)
if err != nil {
return nil, false
}
items[i] = n
}
}
return items, true
case []DataValue: case []DataValue:
items := make([]any, 0, len(value)) items := make([]string, 0, len(value))
for _, val := range value { for _, val := range value {
if !val.IsObject() { if !val.IsObject() {
items = append(items, val.Value()) items = append(items, val.Value())
} }
} }
return list.parseIndicesArray(items) return parseIndicesArray(items)
} }
return nil, false return nil, false
} }
func (list *dropDownListData) Get(tag string) any {
return list.get(strings.ToLower(tag))
}
func (list *dropDownListData) get(tag string) any {
switch tag {
case Items:
return list.items
case DisabledItems:
return list.disabledItems
case ItemSeparators:
return list.itemSeparators
case Current:
result, _ := intProperty(list, Current, list.session, 0)
return result
case DropDownEvent:
return list.dropDownListener
}
return list.viewData.get(tag)
}
func (list *dropDownListData) getItems() []string {
return list.items
}
func (list *dropDownListData) htmlTag() string { func (list *dropDownListData) htmlTag() string {
return "select" return "select"
} }
func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) { func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) {
if list.items != nil { if items := GetDropDownItems(list); len(items) > 0 {
current := GetCurrent(list) current := GetCurrent(list)
notTranslate := GetNotTranslate(list) notTranslate := GetNotTranslate(list)
disabledItems := GetDropDownDisabledItems(list) disabledItems := GetDropDownDisabledItems(list)
separators := GetDropDownItemSeparators(list) separators := GetDropDownItemSeparators(list)
for i, item := range list.items { for i, item := range items {
disabled := false disabled := false
for _, index := range disabledItems { for _, index := range disabledItems {
if i == index { if i == index {
@ -503,22 +241,18 @@ func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder)
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`) buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
} }
func (list *dropDownListData) onSelectedItemChanged(number, old int) { func (list *dropDownListData) handleCommand(self View, command PropertyName, data DataObject) bool {
for _, listener := range list.dropDownListener {
listener(list, number, old)
}
list.propertyChangedEvent(Current)
}
func (list *dropDownListData) handleCommand(self View, command string, data DataObject) bool {
switch command { switch command {
case "itemSelected": case "itemSelected":
if text, ok := data.PropertyValue("number"); ok { if text, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(text); err == nil { if number, err := strconv.Atoi(text); err == nil {
if GetCurrent(list) != number && number >= 0 && number < len(list.items) { items := GetDropDownItems(list)
if GetCurrent(list) != number && number >= 0 && number < len(items) {
old := GetCurrent(list) old := GetCurrent(list)
list.properties[Current] = number list.properties[Current] = number
list.onSelectedItemChanged(number, old) for _, listener := range GetDropDownListeners(list) {
listener(list, number, old)
}
} }
} else { } else {
ErrorLog(err.Error()) ErrorLog(err.Error())
@ -544,14 +278,16 @@ func GetDropDownItems(view View, subviewID ...string) []string {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
if view != nil { if view != nil {
if list, ok := view.(DropDownList); ok { if value := view.Get(Items); value != nil {
return list.getItems() if items, ok := value.([]string); ok {
return items
}
} }
} }
return []string{} return []string{}
} }
func getIndicesArray(view View, tag string) []int { func getIndicesArray(view View, tag PropertyName) []int {
if view != nil { if view != nil {
if value := view.Get(tag); value != nil { if value := view.Get(tag); value != nil {
if values, ok := value.([]any); ok { if values, ok := value.([]any); ok {

View File

@ -26,7 +26,7 @@ const (
// `func(newText string)`, // `func(newText string)`,
// `func(editView rui.EditView)`, // `func(editView rui.EditView)`,
// `func()`. // `func()`.
EditTextChangedEvent = "edit-text-changed" EditTextChangedEvent PropertyName = "edit-text-changed"
// EditViewType is the constant for "edit-view-type" property tag. // EditViewType is the constant for "edit-view-type" property tag.
// //
@ -43,7 +43,7 @@ const (
// `4`(`URLText`) or "url" - Internet address input editor. // `4`(`URLText`) or "url" - Internet address input editor.
// `5`(`PhoneText`) or "phone" - Phone number editor. // `5`(`PhoneText`) or "phone" - Phone number editor.
// `6`(`MultiLineText`) or "multiline" - Multi-line text editor. // `6`(`MultiLineText`) or "multiline" - Multi-line text editor.
EditViewType = "edit-view-type" EditViewType PropertyName = "edit-view-type"
// EditViewPattern is the constant for "edit-view-pattern" property tag. // EditViewPattern is the constant for "edit-view-pattern" property tag.
// //
@ -51,7 +51,7 @@ const (
// Regular expression to limit editing of a text. // Regular expression to limit editing of a text.
// //
// Supported types: `string`. // Supported types: `string`.
EditViewPattern = "edit-view-pattern" EditViewPattern PropertyName = "edit-view-pattern"
// Spellcheck is the constant for "spellcheck" property tag. // Spellcheck is the constant for "spellcheck" property tag.
// //
@ -64,7 +64,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Enable spell checker for text. // `true` or `1` or "true", "yes", "on", "1" - Enable spell checker for text.
// `false` or `0` or "false", "no", "off", "0" - Disable spell checker for text. // `false` or `0` or "false", "no", "off", "0" - Disable spell checker for text.
Spellcheck = "spellcheck" Spellcheck PropertyName = "spellcheck"
) )
// Constants for the values of an [EditView] "edit-view-type" property // Constants for the values of an [EditView] "edit-view-type" property
@ -97,12 +97,11 @@ type EditView interface {
// AppendText appends text to the current text of an EditView view // AppendText appends text to the current text of an EditView view
AppendText(text string) AppendText(text string)
textChanged(newText, oldText string)
} }
type editViewData struct { type editViewData struct {
viewData viewData
dataList
textChangeListeners []func(EditView, string, string)
} }
// NewEditView create new EditView object and return it // NewEditView create new EditView object and return it
@ -114,27 +113,24 @@ func NewEditView(session Session, params Params) EditView {
} }
func newEditView(session Session) View { func newEditView(session Session) View {
return NewEditView(session, nil) return new(editViewData) // NewEditView(session, nil)
} }
func (edit *editViewData) init(session Session) { func (edit *editViewData) init(session Session) {
edit.viewData.init(session) edit.viewData.init(session)
edit.hasHtmlDisabled = true edit.hasHtmlDisabled = true
edit.textChangeListeners = []func(EditView, string, string){}
edit.tag = "EditView" edit.tag = "EditView"
edit.dataListInit() edit.normalize = normalizeEditViewTag
} edit.set = editViewSet
edit.changed = editViewPropertyChanged
func (edit *editViewData) String() string {
return getViewString(edit, nil)
} }
func (edit *editViewData) Focusable() bool { func (edit *editViewData) Focusable() bool {
return true return true
} }
func (edit *editViewData) normalizeTag(tag string) string { func normalizeEditViewTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case Type, "edit-type": case Type, "edit-type":
return EditViewType return EditViewType
@ -149,279 +145,109 @@ func (edit *editViewData) normalizeTag(tag string) string {
return EditWrap return EditWrap
} }
return edit.normalizeDataListTag(tag) return normalizeDataListTag(tag)
} }
func (edit *editViewData) Remove(tag string) { func editViewSet(view View, tag PropertyName, value any) []PropertyName {
edit.remove(edit.normalizeTag(tag))
}
func (edit *editViewData) remove(tag string) {
_, exists := edit.properties[tag]
switch tag { switch tag {
case Hint:
if exists {
delete(edit.properties, Hint)
if edit.created {
edit.session.removeProperty(edit.htmlID(), "placeholder")
}
edit.propertyChangedEvent(tag)
}
case MaxLength:
if exists {
delete(edit.properties, MaxLength)
if edit.created {
edit.session.removeProperty(edit.htmlID(), "maxlength")
}
edit.propertyChangedEvent(tag)
}
case ReadOnly, Spellcheck:
if exists {
delete(edit.properties, tag)
if edit.created {
edit.session.updateProperty(edit.htmlID(), tag, false)
}
edit.propertyChangedEvent(tag)
}
case EditTextChangedEvent:
if len(edit.textChangeListeners) > 0 {
edit.textChangeListeners = []func(EditView, string, string){}
edit.propertyChangedEvent(tag)
}
case Text: case Text:
if exists { if text, ok := value.(string); ok {
oldText := GetText(edit) old := ""
delete(edit.properties, tag) if val := view.getRaw(Text); val != nil {
if oldText != "" { if txt, ok := val.(string); ok {
edit.textChanged("", oldText) old = txt
if edit.created {
edit.session.callFunc("setInputValue", edit.htmlID(), "")
} }
} }
view.setRaw("old-text", old)
view.setRaw(tag, text)
return []PropertyName{tag}
} }
case EditViewPattern: notCompatibleType(tag, value)
if exists { return nil
oldText := GetEditViewPattern(edit)
delete(edit.properties, tag)
if oldText != "" {
if edit.created {
edit.session.removeProperty(edit.htmlID(), Pattern)
}
edit.propertyChangedEvent(tag)
}
}
case EditViewType: case Hint:
if exists { if text, ok := value.(string); ok {
oldType := GetEditViewType(edit) return setStringPropertyValue(view, tag, strings.Trim(text, " \t\n"))
delete(edit.properties, tag)
if oldType != 0 {
if edit.created {
updateInnerHTML(edit.parentHTMLID(), edit.session)
}
edit.propertyChangedEvent(tag)
}
}
case EditWrap:
if exists {
oldWrap := IsEditViewWrap(edit)
delete(edit.properties, tag)
if GetEditViewType(edit) == MultiLineText {
if wrap := IsEditViewWrap(edit); wrap != oldWrap {
if edit.created {
if wrap {
edit.session.updateProperty(edit.htmlID(), "wrap", "soft")
} else {
edit.session.updateProperty(edit.htmlID(), "wrap", "off")
}
}
edit.propertyChangedEvent(tag)
}
}
} }
notCompatibleType(tag, value)
return nil
case DataList: case DataList:
if len(edit.dataList.dataList) > 0 { setDataList(view, value, "")
edit.setDataList(edit, []string{}, true)
}
default: case EditTextChangedEvent:
edit.viewData.remove(tag) return setEventWithOldListener[EditView, string](view, tag, value)
} }
return viewSet(view, tag, value)
} }
func (edit *editViewData) Set(tag string, value any) bool { func editViewPropertyChanged(view View, tag PropertyName) {
return edit.set(edit.normalizeTag(tag), value) session := view.Session()
}
func (edit *editViewData) set(tag string, value any) bool {
if value == nil {
edit.remove(tag)
return true
}
switch tag { switch tag {
case Text: case Text:
if text, ok := value.(string); ok { text := GetText(view)
oldText := GetText(edit) session.callFunc("setInputValue", view.htmlID(), text)
edit.properties[Text] = text
if text = GetText(edit); oldText != text { if edit, ok := view.(EditView); ok {
edit.textChanged(text, oldText) old := ""
if edit.created { if val := view.getRaw("old-text"); val != nil {
edit.session.callFunc("setInputValue", edit.htmlID(), text) if txt, ok := val.(string); ok {
old = txt
} }
} }
return true edit.textChanged(text, old)
} }
return false
case Hint: case Hint:
if text, ok := value.(string); ok { if text := GetHint(view); text != "" {
oldText := GetHint(edit) session.updateProperty(view.htmlID(), "placeholder", text)
edit.properties[Hint] = text } else {
if text = GetHint(edit); oldText != text { session.removeProperty(view.htmlID(), "placeholder")
if edit.created {
if text != "" {
edit.session.updateProperty(edit.htmlID(), "placeholder", text)
} else {
edit.session.removeProperty(edit.htmlID(), "placeholder")
}
}
edit.propertyChangedEvent(tag)
}
return true
} }
return false
case MaxLength: case MaxLength:
oldMaxLength := GetMaxLength(edit) if maxLength := GetMaxLength(view); maxLength > 0 {
if edit.setIntProperty(MaxLength, value) { session.updateProperty(view.htmlID(), "maxlength", strconv.Itoa(maxLength))
if maxLength := GetMaxLength(edit); maxLength != oldMaxLength { } else {
if edit.created { session.removeProperty(view.htmlID(), "maxlength")
if maxLength > 0 {
edit.session.updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength))
} else {
edit.session.removeProperty(edit.htmlID(), "maxlength")
}
}
edit.propertyChangedEvent(tag)
}
return true
} }
return false
case ReadOnly: case ReadOnly:
if edit.setBoolProperty(ReadOnly, value) { if IsReadOnly(view) {
if edit.created { session.updateProperty(view.htmlID(), "readonly", "")
if IsReadOnly(edit) { } else {
edit.session.updateProperty(edit.htmlID(), ReadOnly, "") session.removeProperty(view.htmlID(), "readonly")
} else {
edit.session.removeProperty(edit.htmlID(), ReadOnly)
}
}
edit.propertyChangedEvent(tag)
return true
} }
return false
case Spellcheck: case Spellcheck:
if edit.setBoolProperty(Spellcheck, value) { session.updateProperty(view.htmlID(), "spellcheck", IsSpellcheck(view))
if edit.created {
edit.session.updateProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit))
}
edit.propertyChangedEvent(tag)
return true
}
return false
case EditViewPattern: case EditViewPattern:
oldText := GetEditViewPattern(edit) if text := GetEditViewPattern(view); text != "" {
if text, ok := value.(string); ok { session.updateProperty(view.htmlID(), "pattern", text)
edit.properties[EditViewPattern] = text } else {
if text = GetEditViewPattern(edit); oldText != text { session.removeProperty(view.htmlID(), "pattern")
if edit.created {
if text != "" {
edit.session.updateProperty(edit.htmlID(), Pattern, text)
} else {
edit.session.removeProperty(edit.htmlID(), Pattern)
}
}
edit.propertyChangedEvent(tag)
}
return true
} }
return false
case EditViewType: case EditViewType:
oldType := GetEditViewType(edit) updateInnerHTML(view.parentHTMLID(), session)
if edit.setEnumProperty(EditViewType, value, enumProperties[EditViewType].values) {
if GetEditViewType(edit) != oldType {
if edit.created {
updateInnerHTML(edit.parentHTMLID(), edit.session)
}
edit.propertyChangedEvent(tag)
}
return true
}
return false
case EditWrap: case EditWrap:
oldWrap := IsEditViewWrap(edit) if wrap := IsEditViewWrap(view); wrap {
if edit.setBoolProperty(EditWrap, value) { session.updateProperty(view.htmlID(), "wrap", "soft")
if GetEditViewType(edit) == MultiLineText { } else {
if wrap := IsEditViewWrap(edit); wrap != oldWrap { session.updateProperty(view.htmlID(), "wrap", "off")
if edit.created {
if wrap {
edit.session.updateProperty(edit.htmlID(), "wrap", "soft")
} else {
edit.session.updateProperty(edit.htmlID(), "wrap", "off")
}
}
edit.propertyChangedEvent(tag)
}
}
return true
} }
return false
case DataList: case DataList:
return edit.setDataList(edit, value, edit.created) updateInnerHTML(view.htmlID(), session)
case EditTextChangedEvent: default:
listeners, ok := valueToEventWithOldListeners[EditView, string](value) viewPropertyChanged(view, tag)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(EditView, string, string){}
}
edit.textChangeListeners = listeners
edit.propertyChangedEvent(tag)
return true
} }
return edit.viewData.set(tag, value)
}
func (edit *editViewData) Get(tag string) any {
return edit.get(edit.normalizeTag(tag))
}
func (edit *editViewData) get(tag string) any {
switch tag {
case EditTextChangedEvent:
return edit.textChangeListeners
case DataList:
return edit.dataList.dataList
}
return edit.viewData.get(tag)
} }
func (edit *editViewData) AppendText(text string) { func (edit *editViewData) AppendText(text string) {
@ -432,21 +258,24 @@ func (edit *editViewData) AppendText(text string) {
textValue += text textValue += text
edit.properties[Text] = textValue edit.properties[Text] = textValue
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text) edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
edit.session.callFunc("appendToInputValue", edit.htmlID(), text)
edit.textChanged(textValue, oldText) edit.textChanged(textValue, oldText)
return return
} }
} }
edit.set(Text, text) edit.setRaw(Text, text)
} else { } else {
edit.set(Text, GetText(edit)+text) edit.setRaw(Text, GetText(edit)+text)
} }
} }
func (edit *editViewData) textChanged(newText, oldText string) { func (edit *editViewData) textChanged(newText, oldText string) {
for _, listener := range edit.textChangeListeners { for _, listener := range GetTextChangedListeners(edit) {
listener(edit, newText, oldText) listener(edit, newText, oldText)
} }
edit.propertyChangedEvent(Text) if listener, ok := edit.changeListener[Text]; ok {
listener(edit, Text)
}
} }
func (edit *editViewData) htmlTag() string { func (edit *editViewData) htmlTag() string {
@ -462,7 +291,9 @@ func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteString(text) buffer.WriteString(text)
} }
} }
edit.dataListHtmlSubviews(self, buffer) dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
return text
})
} }
func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
@ -547,16 +378,16 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
} }
} }
edit.dataListHtmlProperties(edit, buffer) dataListHtmlProperties(edit, buffer)
} }
func (edit *editViewData) handleCommand(self View, command string, data DataObject) bool { func (edit *editViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
oldText := GetText(edit) oldText := GetText(edit)
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyValue("text"); ok {
edit.properties[Text] = text edit.setRaw(Text, text)
if text := GetText(edit); text != oldText { if text != oldText {
edit.textChanged(text, oldText) edit.textChanged(text, oldText)
} }
} }

526
events.go Normal file
View File

@ -0,0 +1,526 @@
package rui
import "strings"
var eventJsFunc = map[PropertyName]struct{ jsEvent, jsFunc string }{
FocusEvent: {jsEvent: "onfocus", jsFunc: "focusEvent"},
LostFocusEvent: {jsEvent: "onblur", jsFunc: "blurEvent"},
KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
ClickEvent: {jsEvent: "onclick", jsFunc: "clickEvent"},
DoubleClickEvent: {jsEvent: "ondblclick", jsFunc: "doubleClickEvent"},
MouseDown: {jsEvent: "onmousedown", jsFunc: "mouseDownEvent"},
MouseUp: {jsEvent: "onmouseup", jsFunc: "mouseUpEvent"},
MouseMove: {jsEvent: "onmousemove", jsFunc: "mouseMoveEvent"},
MouseOut: {jsEvent: "onmouseout", jsFunc: "mouseOutEvent"},
MouseOver: {jsEvent: "onmouseover", jsFunc: "mouseOverEvent"},
ContextMenuEvent: {jsEvent: "oncontextmenu", jsFunc: "contextMenuEvent"},
PointerDown: {jsEvent: "onpointerdown", jsFunc: "pointerDownEvent"},
PointerUp: {jsEvent: "onpointerup", jsFunc: "pointerUpEvent"},
PointerMove: {jsEvent: "onpointermove", jsFunc: "pointerMoveEvent"},
PointerCancel: {jsEvent: "onpointercancel", jsFunc: "pointerCancelEvent"},
PointerOut: {jsEvent: "onpointerout", jsFunc: "pointerOutEvent"},
PointerOver: {jsEvent: "onpointerover", jsFunc: "pointerOverEvent"},
TouchStart: {jsEvent: "ontouchstart", jsFunc: "touchStartEvent"},
TouchEnd: {jsEvent: "ontouchend", jsFunc: "touchEndEvent"},
TouchMove: {jsEvent: "ontouchmove", jsFunc: "touchMoveEvent"},
TouchCancel: {jsEvent: "ontouchcancel", jsFunc: "touchCancelEvent"},
TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"},
TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"},
TransitionEndEvent: {jsEvent: "ontransitionend", jsFunc: "transitionEndEvent"},
TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"},
AnimationStartEvent: {jsEvent: "onanimationstart", jsFunc: "animationStartEvent"},
AnimationEndEvent: {jsEvent: "onanimationend", jsFunc: "animationEndEvent"},
AnimationIterationEvent: {jsEvent: "onanimationiteration", jsFunc: "animationIterationEvent"},
AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"},
}
func valueToNoParamListeners[V any](value any) ([]func(V), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V):
return []func(V){value}, true
case func():
fn := func(V) {
value()
}
return []func(V){fn}, true
case []func(V):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V):
listeners[i] = v
case func():
listeners[i] = func(V) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func valueToEventListeners[V View, E any](value any) ([]func(V, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E):
return []func(V, E){value}, true
case func(E):
fn := func(_ V, event E) {
value(event)
}
return []func(V, E){fn}, true
case func(V):
fn := func(view V, _ E) {
value(view)
}
return []func(V, E){fn}, true
case func():
fn := func(V, E) {
value()
}
return []func(V, E){fn}, true
case []func(V, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(_ V, event E) {
v(event)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(view V, _ E) {
v(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V, E) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V, E):
listeners[i] = v
case func(E):
listeners[i] = func(_ V, event E) {
v(event)
}
case func(V):
listeners[i] = func(view V, _ E) {
v(view)
}
case func():
listeners[i] = func(V, E) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func valueToEventWithOldListeners[V View, E any](value any) ([]func(V, E, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E, E):
return []func(V, E, E){value}, true
case func(V, E):
fn := func(v V, val, _ E) {
value(v, val)
}
return []func(V, E, E){fn}, true
case func(E, E):
fn := func(_ V, val, old E) {
value(val, old)
}
return []func(V, E, E){fn}, true
case func(E):
fn := func(_ V, val, _ E) {
value(val)
}
return []func(V, E, E){fn}, true
case func(V):
fn := func(v V, _, _ E) {
value(v)
}
return []func(V, E, E){fn}, true
case func():
fn := func(V, E, E) {
value()
}
return []func(V, E, E){fn}, true
case []func(V, E, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(V, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
}
return listeners, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
}
return listeners, true
case []func(E, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, _, _ E) {
fn(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(V, E, E) {
fn()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch fn := v.(type) {
case func(V, E, E):
listeners[i] = fn
case func(V, E):
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
case func(E, E):
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
case func(E):
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
case func(V):
listeners[i] = func(view V, _, _ E) {
fn(view)
}
case func():
listeners[i] = func(V, E, E) {
fn()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func getNoParamEventListeners[V View](view View, subviewID []string, tag PropertyName) []func(V) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V)); ok {
return result
}
}
}
return []func(V){}
}
func getEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []func(V, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E)); ok {
return result
}
}
}
return []func(V, E){}
}
func getEventWithOldListeners[V View, E any](view View, subviewID []string, tag PropertyName) []func(V, E, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E, E)); ok {
return result
}
}
}
return []func(V, E, E){}
}
func setNoParamEventListener[V View](properties Properties, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToNoParamListeners[V](value); ok {
if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
func setViewEventListener[V View, T any](properties Properties, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToEventListeners[V, T](value); ok {
if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
func setEventWithOldListener[V View, T any](properties Properties, tag PropertyName, value any) []PropertyName {
listeners, ok := valueToEventWithOldListeners[V, T](value)
if !ok {
notCompatibleType(tag, value)
return nil
} else if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
func viewEventsHtml[T any](view View, events []PropertyName, buffer *strings.Builder) {
for _, tag := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent} {
if value := view.getRaw(tag); value != nil {
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, T)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
}
}
}
func updateEventListenerHtml(view View, tag PropertyName) {
if js, ok := eventJsFunc[tag]; ok {
value := view.getRaw(tag)
session := view.Session()
htmlID := view.htmlID()
if value == nil {
session.removeProperty(view.htmlID(), js.jsEvent)
} else {
session.updateProperty(htmlID, js.jsEvent, js.jsFunc+"(this, event)")
}
}
}

View File

@ -25,7 +25,7 @@ const (
// `func(picker rui.FilePicker)`, // `func(picker rui.FilePicker)`,
// `func(files []rui.FileInfo)`, // `func(files []rui.FileInfo)`,
// `func()`. // `func()`.
FileSelectedEvent = "file-selected-event" FileSelectedEvent PropertyName = "file-selected-event"
// Accept is the constant for "accept" property tag. // Accept is the constant for "accept" property tag.
// //
@ -39,7 +39,7 @@ const (
// Conversion rules: // Conversion rules:
// `string` - may contain single value of multiple separated by comma(`,`). // `string` - may contain single value of multiple separated by comma(`,`).
// `[]string` - an array of acceptable file extensions or MIME types. // `[]string` - an array of acceptable file extensions or MIME types.
Accept = "accept" Accept PropertyName = "accept"
// Multiple is the constant for "multiple" property tag. // Multiple is the constant for "multiple" property tag.
// //
@ -51,7 +51,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Several files can be selected. // `true` or `1` or "true", "yes", "on", "1" - Several files can be selected.
// `false` or `0` or "false", "no", "off", "0" - Only one file can be selected. // `false` or `0` or "false", "no", "off", "0" - Only one file can be selected.
Multiple = "multiple" Multiple PropertyName = "multiple"
) )
// FileInfo describes a file which selected in the FilePicker view // FileInfo describes a file which selected in the FilePicker view
@ -82,9 +82,8 @@ type FilePicker interface {
type filePickerData struct { type filePickerData struct {
viewData viewData
files []FileInfo files []FileInfo
fileSelectedListeners []func(FilePicker, []FileInfo) loader map[int]func(FileInfo, []byte)
loader map[int]func(FileInfo, []byte)
} }
func (file *FileInfo) initBy(node DataValue) { func (file *FileInfo) initBy(node DataValue) {
@ -115,7 +114,7 @@ func NewFilePicker(session Session, params Params) FilePicker {
} }
func newFilePicker(session Session) View { func newFilePicker(session Session) View {
return NewFilePicker(session, nil) return new(filePickerData) // NewFilePicker(session, nil)
} }
func (picker *filePickerData) init(session Session) { func (picker *filePickerData) init(session Session) {
@ -124,11 +123,9 @@ func (picker *filePickerData) init(session Session) {
picker.hasHtmlDisabled = true picker.hasHtmlDisabled = true
picker.files = []FileInfo{} picker.files = []FileInfo{}
picker.loader = map[int]func(FileInfo, []byte){} picker.loader = map[int]func(FileInfo, []byte){}
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} picker.set = filePickerSet
} picker.changed = filePickerPropertyChanged
func (picker *filePickerData) String() string {
return getViewString(picker, nil)
} }
func (picker *filePickerData) Focusable() bool { func (picker *filePickerData) Focusable() bool {
@ -153,62 +150,27 @@ func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []by
} }
} }
func (picker *filePickerData) Remove(tag string) { func filePickerSet(view View, tag PropertyName, value any) []PropertyName {
picker.remove(strings.ToLower(tag))
}
func (picker *filePickerData) remove(tag string) { setAccept := func(value string) []PropertyName {
switch tag { if value != "" {
case FileSelectedEvent: view.setRaw(tag, value)
if len(picker.fileSelectedListeners) > 0 { } else if view.getRaw(tag) != nil {
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} view.setRaw(tag, nil)
picker.propertyChangedEvent(tag) } else {
return []PropertyName{}
} }
return []PropertyName{Accept}
case Accept:
delete(picker.properties, tag)
if picker.created {
picker.session.removeProperty(picker.htmlID(), "accept")
}
picker.propertyChangedEvent(tag)
default:
picker.viewData.remove(tag)
}
}
func (picker *filePickerData) Set(tag string, value any) bool {
return picker.set(strings.ToLower(tag), value)
}
func (picker *filePickerData) set(tag string, value any) bool {
if value == nil {
picker.remove(tag)
return true
} }
switch tag { switch tag {
case FileSelectedEvent: case FileSelectedEvent:
listeners, ok := valueToEventListeners[FilePicker, []FileInfo](value) return setViewEventListener[FilePicker, []FileInfo](view, tag, value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(FilePicker, []FileInfo){}
}
picker.fileSelectedListeners = listeners
picker.propertyChangedEvent(tag)
return true
case Accept: case Accept:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
value = strings.Trim(value, " \t\n") return setAccept(strings.Trim(value, " \t\n"))
if value == "" {
picker.remove(Accept)
} else {
picker.properties[Accept] = value
}
case []string: case []string:
buffer := allocStringBuilder() buffer := allocStringBuilder()
@ -222,29 +184,27 @@ func (picker *filePickerData) set(tag string, value any) bool {
buffer.WriteString(val) buffer.WriteString(val)
} }
} }
if buffer.Len() == 0 { return setAccept(buffer.String())
picker.remove(Accept)
} else {
picker.properties[Accept] = buffer.String()
}
default:
notCompatibleType(tag, value)
return false
} }
notCompatibleType(tag, value)
return nil
}
if picker.created { return viewSet(view, tag, value)
if css := picker.acceptCSS(); css != "" { }
picker.session.updateProperty(picker.htmlID(), "accept", css)
} else { func filePickerPropertyChanged(view View, tag PropertyName) {
picker.session.removeProperty(picker.htmlID(), "accept") switch tag {
} case Accept:
session := view.Session()
if css := acceptPropertyCSS(view); css != "" {
session.updateProperty(view.htmlID(), "accept", css)
} else {
session.removeProperty(view.htmlID(), "accept")
} }
picker.propertyChangedEvent(tag)
return true
default: default:
return picker.viewData.set(tag, value) viewPropertyChanged(view, tag)
} }
} }
@ -252,10 +212,10 @@ func (picker *filePickerData) htmlTag() string {
return "input" return "input"
} }
func (picker *filePickerData) acceptCSS() string { func acceptPropertyCSS(view View) string {
accept, ok := stringProperty(picker, Accept, picker.Session()) accept, ok := stringProperty(view, Accept, view.Session())
if !ok { if !ok {
if value := valueFromStyle(picker, Accept); value != nil { if value := valueFromStyle(view, Accept); value != nil {
accept, ok = value.(string) accept, ok = value.(string)
} }
} }
@ -282,7 +242,7 @@ func (picker *filePickerData) acceptCSS() string {
func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) { func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) {
picker.viewData.htmlProperties(self, buffer) picker.viewData.htmlProperties(self, buffer)
if accept := picker.acceptCSS(); accept != "" { if accept := acceptPropertyCSS(picker); accept != "" {
buffer.WriteString(` accept="`) buffer.WriteString(` accept="`)
buffer.WriteString(accept) buffer.WriteString(accept)
buffer.WriteRune('"') buffer.WriteRune('"')
@ -299,7 +259,7 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder)
} }
} }
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool { func (picker *filePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "fileSelected": case "fileSelected":
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode { if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
@ -312,7 +272,7 @@ func (picker *filePickerData) handleCommand(self View, command string, data Data
} }
picker.files = files picker.files = files
for _, listener := range picker.fileSelectedListeners { for _, listener := range GetFileSelectedListeners(picker) {
listener(picker, files) listener(picker, files)
} }
} }

View File

@ -17,7 +17,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
FocusEvent = "focus-event" FocusEvent PropertyName = "focus-event"
// LostFocusEvent is the constant for "lost-focus-event" property tag. // LostFocusEvent is the constant for "lost-focus-event" property tag.
// //
@ -32,136 +32,18 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
LostFocusEvent = "lost-focus-event" LostFocusEvent PropertyName = "lost-focus-event"
) )
func valueToNoParamListeners[V any](value any) ([]func(V), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V):
return []func(V){value}, true
case func():
fn := func(V) {
value()
}
return []func(V){fn}, true
case []func(V):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V):
listeners[i] = v
case func():
listeners[i] = func(V) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
var focusEvents = map[string]struct{ jsEvent, jsFunc string }{
FocusEvent: {jsEvent: "onfocus", jsFunc: "focusEvent"},
LostFocusEvent: {jsEvent: "onblur", jsFunc: "blurEvent"},
}
func (view *viewData) setFocusListener(tag string, value any) bool {
listeners, ok := valueToNoParamListeners[View](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removeFocusListener(tag)
} else if js, ok := focusEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
}
} else {
return false
}
return true
}
func (view *viewData) removeFocusListener(tag string) {
delete(view.properties, tag)
if view.created {
if js, ok := focusEvents[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
func getFocusListeners(view View, subviewID []string, tag string) []func(View) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(View)); ok {
return result
}
}
}
return []func(View){}
}
func focusEventsHtml(view View, buffer *strings.Builder) { func focusEventsHtml(view View, buffer *strings.Builder) {
if view.Focusable() { if view.Focusable() {
for _, js := range focusEvents { for _, tag := range []PropertyName{FocusEvent, LostFocusEvent} {
buffer.WriteString(js.jsEvent) if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
@ -169,11 +51,11 @@ func focusEventsHtml(view View, buffer *strings.Builder) {
// GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned // GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetFocusListeners(view View, subviewID ...string) []func(View) { func GetFocusListeners(view View, subviewID ...string) []func(View) {
return getFocusListeners(view, subviewID, FocusEvent) return getNoParamEventListeners[View](view, subviewID, FocusEvent)
} }
// GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned // GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetLostFocusListeners(view View, subviewID ...string) []func(View) { func GetLostFocusListeners(view View, subviewID ...string) []func(View) {
return getFocusListeners(view, subviewID, LostFocusEvent) return getNoParamEventListeners[View](view, subviewID, LostFocusEvent)
} }

View File

@ -24,7 +24,7 @@ const (
// //
// Usage in `SvgImageView`: // Usage in `SvgImageView`:
// Same as "vertical-align". // Same as "vertical-align".
CellVerticalAlign = "cell-vertical-align" CellVerticalAlign PropertyName = "cell-vertical-align"
// CellHorizontalAlign is the constant for "cell-horizontal-align" property tag. // CellHorizontalAlign is the constant for "cell-horizontal-align" property tag.
// //
@ -43,7 +43,7 @@ const (
// //
// Usage in `SvgImageView`: // Usage in `SvgImageView`:
// Same as "horizontal-align". // Same as "horizontal-align".
CellHorizontalAlign = "cell-horizontal-align" CellHorizontalAlign PropertyName = "cell-horizontal-align"
// CellVerticalSelfAlign is the constant for "cell-vertical-self-align" property tag. // CellVerticalSelfAlign is the constant for "cell-vertical-self-align" property tag.
// //
@ -58,7 +58,7 @@ const (
// `1`(`BottomAlign`) or "bottom" - Bottom alignment. // `1`(`BottomAlign`) or "bottom" - Bottom alignment.
// `2`(`CenterAlign`) or "center" - Center alignment. // `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full height stretch. // `3`(`StretchAlign`) or "stretch" - Full height stretch.
CellVerticalSelfAlign = "cell-vertical-self-align" CellVerticalSelfAlign PropertyName = "cell-vertical-self-align"
// CellHorizontalSelfAlign is the constant for "cell-horizontal-self-align" property tag. // CellHorizontalSelfAlign is the constant for "cell-horizontal-self-align" property tag.
// //
@ -73,7 +73,7 @@ const (
// `1`(`RightAlign`) or "right" - Right alignment. // `1`(`RightAlign`) or "right" - Right alignment.
// `2`(`CenterAlign`) or "center" - Center alignment. // `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full width stretch. // `3`(`StretchAlign`) or "stretch" - Full width stretch.
CellHorizontalSelfAlign = "cell-horizontal-self-align" CellHorizontalSelfAlign PropertyName = "cell-horizontal-self-align"
) )
// GridAdapter is an interface to define [GridLayout] content. [GridLayout] will query interface functions to populate // GridAdapter is an interface to define [GridLayout] content. [GridLayout] will query interface functions to populate
@ -126,7 +126,8 @@ func NewGridLayout(session Session, params Params) GridLayout {
} }
func newGridLayout(session Session) View { func newGridLayout(session Session) View {
return NewGridLayout(session, nil) //return NewGridLayout(session, nil)
return new(gridLayoutData)
} }
// Init initialize fields of GridLayout by default values // Init initialize fields of GridLayout by default values
@ -135,13 +136,14 @@ func (gridLayout *gridLayoutData) init(session Session) {
gridLayout.tag = "GridLayout" gridLayout.tag = "GridLayout"
gridLayout.systemClass = "ruiGridLayout" gridLayout.systemClass = "ruiGridLayout"
gridLayout.adapter = nil gridLayout.adapter = nil
gridLayout.normalize = normalizeGridLayoutTag
gridLayout.getFunc = gridLayout.get
gridLayout.set = gridLayout.setFunc
gridLayout.remove = gridLayout.removeFunc
} }
func (gridLayout *gridLayoutData) String() string { func setGridCellSize(properties Properties, tag PropertyName, value any) []PropertyName {
return getViewString(gridLayout, nil)
}
func (style *viewStyle) setGridCellSize(tag string, value any) bool {
setValues := func(values []string) bool { setValues := func(values []string) bool {
count := len(values) count := len(values)
if count > 1 { if count > 1 {
@ -159,11 +161,11 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
return false return false
} }
} }
style.properties[tag] = sizes properties.setRaw(tag, sizes)
} else if isConstantName(values[0]) { } else if isConstantName(values[0]) {
style.properties[tag] = values[0] properties.setRaw(tag, values[0])
} else if size, err := stringToSizeUnit(values[0]); err == nil { } else if size, err := stringToSizeUnit(values[0]); err == nil {
style.properties[tag] = size properties.setRaw(tag, size)
} else { } else {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return false
@ -175,41 +177,41 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
case CellWidth, CellHeight: case CellWidth, CellHeight:
switch value := value.(type) { switch value := value.(type) {
case SizeUnit, []SizeUnit: case SizeUnit, []SizeUnit:
style.properties[tag] = value properties.setRaw(tag, value)
case string: case string:
if !setValues(strings.Split(value, ",")) { if !setValues(strings.Split(value, ",")) {
return false return nil
} }
case []string: case []string:
if !setValues(value) { if !setValues(value) {
return false return nil
} }
case []DataValue: case []DataValue:
count := len(value) count := len(value)
if count == 0 { if count == 0 {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
values := make([]string, count) values := make([]string, count)
for i, val := range value { for i, val := range value {
if val.IsObject() { if val.IsObject() {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
values[i] = val.Value() values[i] = val.Value()
} }
if !setValues(values) { if !setValues(values) {
return false return nil
} }
case []any: case []any:
count := len(value) count := len(value)
if count == 0 { if count == 0 {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
sizes := make([]any, count) sizes := make([]any, count)
for i, val := range value { for i, val := range value {
@ -224,29 +226,29 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
sizes[i] = size sizes[i] = size
} else { } else {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
default: default:
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
} }
style.properties[tag] = sizes properties.setRaw(tag, sizes)
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
return true return []PropertyName{tag}
} }
return false return nil
} }
func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string { func gridCellSizesCSS(properties Properties, tag PropertyName, session Session) string {
switch cellSize := gridCellSizes(style, tag, session); len(cellSize) { switch cellSize := gridCellSizes(properties, tag, session); len(cellSize) {
case 0: case 0:
case 1: case 1:
@ -283,8 +285,8 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string {
return "" return ""
} }
func (gridLayout *gridLayoutData) normalizeTag(tag string) string { func normalizeGridLayoutTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case VerticalAlign: case VerticalAlign:
return CellVerticalAlign return CellVerticalAlign
@ -301,162 +303,167 @@ func (gridLayout *gridLayoutData) normalizeTag(tag string) string {
return tag return tag
} }
func (gridLayout *gridLayoutData) Get(tag string) any { func (gridLayout *gridLayoutData) get(self View, tag PropertyName) any {
return gridLayout.get(gridLayout.normalizeTag(tag)) switch tag {
} case Gap:
func (gridLayout *gridLayoutData) get(tag string) any {
if tag == Gap {
rowGap := GetGridRowGap(gridLayout) rowGap := GetGridRowGap(gridLayout)
columnGap := GetGridColumnGap(gridLayout) columnGap := GetGridColumnGap(gridLayout)
if rowGap.Equal(columnGap) { if rowGap.Equal(columnGap) {
return rowGap return rowGap
} }
return AutoSize() return AutoSize()
}
return gridLayout.viewsContainerData.get(tag)
}
func (gridLayout *gridLayoutData) Remove(tag string) {
gridLayout.remove(gridLayout.normalizeTag(tag))
}
func (gridLayout *gridLayoutData) remove(tag string) {
switch tag {
case Gap:
gridLayout.remove(GridRowGap)
gridLayout.remove(GridColumnGap)
gridLayout.propertyChangedEvent(Gap)
return
case Content: case Content:
gridLayout.adapter = nil if gridLayout.adapter != nil {
} return gridLayout.adapter
gridLayout.viewsContainerData.remove(tag)
if gridLayout.created {
switch tag {
case CellWidth:
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-columns`,
gridLayout.gridCellSizesCSS(CellWidth, gridLayout.session))
case CellHeight:
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-rows`,
gridLayout.gridCellSizesCSS(CellHeight, gridLayout.session))
} }
} }
return gridLayout.viewsContainerData.get(gridLayout, tag)
} }
func (gridLayout *gridLayoutData) Set(tag string, value any) bool { func (gridLayout *gridLayoutData) removeFunc(self View, tag PropertyName) []PropertyName {
return gridLayout.set(gridLayout.normalizeTag(tag), value)
}
func (gridLayout *gridLayoutData) set(tag string, value any) bool {
if value == nil {
gridLayout.remove(tag)
return true
}
switch tag { switch tag {
case Gap: case Gap:
return gridLayout.set(GridRowGap, value) && gridLayout.set(GridColumnGap, value) result := []PropertyName{}
for _, tag := range []PropertyName{GridRowGap, GridColumnGap} {
if gridLayout.getRaw(tag) != nil {
gridLayout.setRaw(tag, nil)
result = append(result, tag)
}
}
return result
case Content:
if len(gridLayout.views) > 0 || gridLayout.adapter != nil {
gridLayout.views = []View{}
gridLayout.adapter = nil
return []PropertyName{Content}
}
return []PropertyName{}
}
return gridLayout.viewsContainerData.removeFunc(gridLayout, tag)
}
func (gridLayout *gridLayoutData) setFunc(self View, tag PropertyName, value any) []PropertyName {
switch tag {
case Gap:
result := gridLayout.setFunc(gridLayout, GridRowGap, value)
if result != nil {
if gap := gridLayout.getRaw(GridRowGap); gap != nil {
gridLayout.setRaw(GridColumnGap, gap)
result = append(result, GridColumnGap)
}
}
return result
case Content: case Content:
if adapter, ok := value.(GridAdapter); ok { if adapter, ok := value.(GridAdapter); ok {
gridLayout.adapter = adapter gridLayout.adapter = adapter
gridLayout.UpdateGridContent() gridLayout.createGridContent()
return true } else if gridLayout.setContent(value) {
gridLayout.adapter = nil
} else {
return nil
} }
gridLayout.adapter = nil return []PropertyName{Content}
} }
if gridLayout.viewsContainerData.set(tag, value) { return gridLayout.viewsContainerData.setFunc(gridLayout, tag, value)
if gridLayout.created { }
switch tag {
case CellWidth:
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-columns`,
gridLayout.gridCellSizesCSS(CellWidth, gridLayout.session))
case CellHeight: func gridLayoutPropertyChanged(view View, tag PropertyName) {
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-rows`, switch tag {
gridLayout.gridCellSizesCSS(CellHeight, gridLayout.session)) case CellWidth:
view.Session().updateCSSProperty(view.htmlID(), `grid-template-columns`,
gridCellSizesCSS(view, CellWidth, view.Session()))
case CellHeight:
view.Session().updateCSSProperty(view.htmlID(), `grid-template-rows`,
gridCellSizesCSS(view, CellHeight, view.Session()))
default:
viewsContainerPropertyChanged(view, tag)
}
}
func (gridLayout *gridLayoutData) createGridContent() bool {
if gridLayout.adapter == nil {
return false
}
adapter := gridLayout.adapter
gridLayout.views = []View{}
session := gridLayout.session
htmlID := gridLayout.htmlID()
isDisabled := IsDisabled(gridLayout)
var columnSpan GridCellColumnSpanAdapter = nil
if span, ok := adapter.(GridCellColumnSpanAdapter); ok {
columnSpan = span
}
var rowSpan GridCellRowSpanAdapter = nil
if span, ok := adapter.(GridCellRowSpanAdapter); ok {
rowSpan = span
}
width := adapter.GridColumnCount()
height := adapter.GridRowCount()
for column := 0; column < width; column++ {
for row := 0; row < height; row++ {
if view := adapter.GridCellContent(row, column, session); view != nil {
view.setParentID(htmlID)
columnCount := 1
if columnSpan != nil {
columnCount = columnSpan.GridCellColumnSpan(row, column)
}
if columnCount > 1 {
view.Set(Column, Range{First: column, Last: column + columnCount - 1})
} else {
view.Set(Column, column)
}
rowCount := 1
if rowSpan != nil {
rowCount = rowSpan.GridCellRowSpan(row, column)
}
if rowCount > 1 {
view.Set(Row, Range{First: row, Last: row + rowCount - 1})
} else {
view.Set(Row, row)
}
if isDisabled {
view.Set(Disabled, true)
}
gridLayout.views = append(gridLayout.views, view)
} }
} }
return true
} }
return false return true
} }
func (gridLayout *gridLayoutData) UpdateGridContent() { func (gridLayout *gridLayoutData) UpdateGridContent() {
if adapter := gridLayout.adapter; adapter != nil { if gridLayout.createGridContent() {
gridLayout.views = []View{}
session := gridLayout.session
htmlID := gridLayout.htmlID()
isDisabled := IsDisabled(gridLayout)
var columnSpan GridCellColumnSpanAdapter = nil
if span, ok := adapter.(GridCellColumnSpanAdapter); ok {
columnSpan = span
}
var rowSpan GridCellRowSpanAdapter = nil
if span, ok := adapter.(GridCellRowSpanAdapter); ok {
rowSpan = span
}
width := adapter.GridColumnCount()
height := adapter.GridRowCount()
for column := 0; column < width; column++ {
for row := 0; row < height; row++ {
if view := adapter.GridCellContent(row, column, session); view != nil {
view.setParentID(htmlID)
columnCount := 1
if columnSpan != nil {
columnCount = columnSpan.GridCellColumnSpan(row, column)
}
if columnCount > 1 {
view.Set(Column, Range{First: column, Last: column + columnCount - 1})
} else {
view.Set(Column, column)
}
rowCount := 1
if rowSpan != nil {
rowCount = rowSpan.GridCellRowSpan(row, column)
}
if rowCount > 1 {
view.Set(Row, Range{First: row, Last: row + rowCount - 1})
} else {
view.Set(Row, row)
}
if isDisabled {
view.Set(Disabled, true)
}
gridLayout.views = append(gridLayout.views, view)
}
}
}
if gridLayout.created { if gridLayout.created {
updateInnerHTML(htmlID, session) updateInnerHTML(gridLayout.htmlID(), gridLayout.session)
} }
gridLayout.propertyChangedEvent(Content) if listener, ok := gridLayout.changeListener[Content]; ok {
listener(gridLayout, Content)
}
} }
} }
func gridCellSizes(properties Properties, tag string, session Session) []SizeUnit { func gridCellSizes(properties Properties, tag PropertyName, session Session) []SizeUnit {
if value := properties.Get(tag); value != nil { if value := properties.Get(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case []SizeUnit: case []SizeUnit:

View File

@ -20,7 +20,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
LoadedEvent = "loaded-event" LoadedEvent PropertyName = "loaded-event"
// ErrorEvent is the constant for "error-event" property tag. // ErrorEvent is the constant for "error-event" property tag.
// //
@ -35,7 +35,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
ErrorEvent = "error-event" ErrorEvent PropertyName = "error-event"
// NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized // NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized
NoneFit = 0 NoneFit = 0
@ -88,7 +88,7 @@ func NewImageView(session Session, params Params) ImageView {
} }
func newImageView(session Session) View { func newImageView(session Session) View {
return NewImageView(session, nil) return new(imageViewData)
} }
// Init initialize fields of imageView by default values // Init initialize fields of imageView by default values
@ -96,14 +96,13 @@ func (imageView *imageViewData) init(session Session) {
imageView.viewData.init(session) imageView.viewData.init(session)
imageView.tag = "ImageView" imageView.tag = "ImageView"
imageView.systemClass = "ruiImageView" imageView.systemClass = "ruiImageView"
imageView.normalize = normalizeImageViewTag
imageView.set = imageViewSet
imageView.changed = imageViewPropertyChanged
} }
func (imageView *imageViewData) String() string { func normalizeImageViewTag(tag PropertyName) PropertyName {
return getViewString(imageView, nil) tag = defaultNormalize(tag)
}
func (imageView *imageViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case "source": case "source":
tag = Source tag = Source
@ -123,127 +122,58 @@ func (imageView *imageViewData) normalizeTag(tag string) string {
return tag return tag
} }
func (imageView *imageViewData) Remove(tag string) { func imageViewSet(view View, tag PropertyName, value any) []PropertyName {
imageView.remove(imageView.normalizeTag(tag))
}
func (imageView *imageViewData) remove(tag string) { switch tag {
imageView.viewData.remove(tag) case Source, SrcSet, AltText:
if imageView.created { if text, ok := value.(string); ok {
switch tag { return setStringPropertyValue(view, tag, text)
case Source:
imageView.session.updateProperty(imageView.htmlID(), "src", "")
imageView.session.removeProperty(imageView.htmlID(), "srcset")
case AltText:
updateInnerHTML(imageView.htmlID(), imageView.session)
case ImageVerticalAlign, ImageHorizontalAlign:
updateCSSStyle(imageView.htmlID(), imageView.session)
} }
notCompatibleType(tag, value)
return nil
case LoadedEvent, ErrorEvent:
return setNoParamEventListener[ImageView](view, tag, value)
} }
return viewSet(view, tag, value)
} }
func (imageView *imageViewData) Set(tag string, value any) bool { func imageViewPropertyChanged(view View, tag PropertyName) {
return imageView.set(imageView.normalizeTag(tag), value) session := view.Session()
} htmlID := view.htmlID()
func (imageView *imageViewData) set(tag string, value any) bool {
if value == nil {
imageView.remove(tag)
return true
}
switch tag { switch tag {
case Source: case Source:
if text, ok := value.(string); ok { src, srcset := imageViewSrc(view, GetImageViewSource(view))
imageView.properties[tag] = text session.updateProperty(htmlID, "src", src)
if imageView.created { if srcset != "" {
src, srcset := imageView.src(text) session.updateProperty(htmlID, "srcset", srcset)
imageView.session.updateProperty(imageView.htmlID(), "src", src) } else {
session.removeProperty(htmlID, "srcset")
if srcset != "" {
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
} else {
imageView.session.removeProperty(imageView.htmlID(), "srcset")
}
}
imageView.propertyChangedEvent(Source)
return true
} }
notCompatibleType(Source, value)
case SrcSet: case SrcSet:
if text, ok := value.(string); ok { _, srcset := imageViewSrc(view, GetImageViewSource(view))
if text == "" { if srcset != "" {
delete(imageView.properties, tag) session.updateProperty(htmlID, "srcset", srcset)
} else { } else {
imageView.properties[tag] = text session.removeProperty(htmlID, "srcset")
}
if imageView.created {
_, srcset := imageView.src(text)
if srcset != "" {
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
} else {
imageView.session.removeProperty(imageView.htmlID(), "srcset")
}
}
imageView.propertyChangedEvent(Source)
return true
} }
notCompatibleType(Source, value)
case AltText: case AltText:
if text, ok := value.(string); ok { updateInnerHTML(htmlID, session)
imageView.properties[AltText] = text
if imageView.created {
updateInnerHTML(imageView.htmlID(), imageView.session)
}
imageView.propertyChangedEvent(Source)
return true
}
notCompatibleType(tag, value)
case LoadedEvent, ErrorEvent: case ImageVerticalAlign, ImageHorizontalAlign:
if listeners, ok := valueToNoParamListeners[ImageView](value); ok { updateCSSStyle(htmlID, session)
if listeners == nil {
delete(imageView.properties, tag)
} else {
imageView.properties[tag] = listeners
}
return true
}
default: default:
if imageView.viewData.set(tag, value) { viewPropertyChanged(view, tag)
if imageView.created {
switch tag {
case ImageVerticalAlign, ImageHorizontalAlign:
updateCSSStyle(imageView.htmlID(), imageView.session)
}
}
return true
}
} }
return false
} }
func (imageView *imageViewData) Get(tag string) any { func imageViewSrcSet(view View, path string) string {
return imageView.viewData.get(imageView.normalizeTag(tag)) if value := view.getRaw(SrcSet); value != nil {
}
func (imageView *imageViewData) imageListeners(tag string) []func(ImageView) {
if value := imageView.getRaw(tag); value != nil {
if listeners, ok := value.([]func(ImageView)); ok {
return listeners
}
}
return []func(ImageView){}
}
func (imageView *imageViewData) srcSet(path string) string {
if value := imageView.getRaw(SrcSet); value != nil {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
srcset := strings.Split(text, ",") srcset := strings.Split(text, ",")
buffer := allocStringBuilder() buffer := allocStringBuilder()
@ -286,9 +216,9 @@ func (imageView *imageViewData) htmlTag() string {
return "img" return "img"
} }
func (imageView *imageViewData) src(src string) (string, string) { func imageViewSrc(view View, src string) (string, string) {
if src != "" && src[0] == '@' { if src != "" && src[0] == '@' {
if image, ok := imageView.Session().ImageConstant(src[1:]); ok { if image, ok := view.Session().ImageConstant(src[1:]); ok {
src = image src = image
} else { } else {
src = "" src = ""
@ -296,7 +226,7 @@ func (imageView *imageViewData) src(src string) (string, string) {
} }
if src != "" { if src != "" {
return src, imageView.srcSet(src) return src, imageViewSrcSet(view, src)
} }
return "", "" return "", ""
} }
@ -306,7 +236,7 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde
imageView.viewData.htmlProperties(self, buffer) imageView.viewData.htmlProperties(self, buffer)
if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" { if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" {
if src, srcset := imageView.src(imageResource); src != "" { if src, srcset := imageViewSrc(imageView, imageResource); src != "" {
buffer.WriteString(` src="`) buffer.WriteString(` src="`)
buffer.WriteString(src) buffer.WriteString(src)
buffer.WriteString(`"`) buffer.WriteString(`"`)
@ -326,7 +256,7 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde
buffer.WriteString(` onload="imageLoaded(this, event)"`) buffer.WriteString(` onload="imageLoaded(this, event)"`)
if len(imageView.imageListeners(ErrorEvent)) > 0 { if len(getNoParamEventListeners[ImageView](imageView, nil, ErrorEvent)) > 0 {
buffer.WriteString(` onerror="imageError(this, event)"`) buffer.WriteString(` onerror="imageError(this, event)"`)
} }
} }
@ -366,10 +296,10 @@ func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) {
} }
} }
func (imageView *imageViewData) handleCommand(self View, command string, data DataObject) bool { func (imageView *imageViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "imageViewError": case "imageViewError":
for _, listener := range imageView.imageListeners(ErrorEvent) { for _, listener := range getNoParamEventListeners[ImageView](imageView, nil, ErrorEvent) {
listener(imageView) listener(imageView)
} }
@ -378,7 +308,7 @@ func (imageView *imageViewData) handleCommand(self View, command string, data Da
imageView.naturalHeight = dataFloatProperty(data, "natural-height") imageView.naturalHeight = dataFloatProperty(data, "natural-height")
imageView.currentSrc, _ = data.PropertyValue("current-src") imageView.currentSrc, _ = data.PropertyValue("current-src")
for _, listener := range imageView.imageListeners(LoadedEvent) { for _, listener := range getNoParamEventListeners[ImageView](imageView, nil, LoadedEvent) {
listener(imageView) listener(imageView)
} }

View File

@ -20,7 +20,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.KeyEvent)`, // `func(event rui.KeyEvent)`,
// `func()`. // `func()`.
KeyDownEvent = "key-down-event" KeyDownEvent PropertyName = "key-down-event"
// KeyUpEvent is the constant for "key-up-event" property tag. // KeyUpEvent is the constant for "key-up-event" property tag.
// //
@ -38,7 +38,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.KeyEvent)`, // `func(event rui.KeyEvent)`,
// `func()`. // `func()`.
KeyUpEvent = "key-up-event" KeyUpEvent PropertyName = "key-up-event"
) )
// ControlKeyMask represent ORed state of keyboard's control keys like [AltKey], [CtrlKey], [ShiftKey] and [MetaKey] // ControlKeyMask represent ORed state of keyboard's control keys like [AltKey], [CtrlKey], [ShiftKey] and [MetaKey]
@ -429,342 +429,21 @@ func (event *KeyEvent) init(data DataObject) {
event.MetaKey = getBool("metaKey") event.MetaKey = getBool("metaKey")
} }
func valueToEventListeners[V View, E any](value any) ([]func(V, E), bool) { /*
if value == nil { func setKeyListener(properties Properties, tag PropertyName, value any) bool {
return nil, true if listeners, ok := valueToEventListeners[View, KeyEvent](value); ok {
if len(listeners) == 0 {
properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
return true
} }
notCompatibleType(tag, value)
switch value := value.(type) { return false
case func(V, E):
return []func(V, E){value}, true
case func(E):
fn := func(_ V, event E) {
value(event)
}
return []func(V, E){fn}, true
case func(V):
fn := func(view V, _ E) {
value(view)
}
return []func(V, E){fn}, true
case func():
fn := func(V, E) {
value()
}
return []func(V, E){fn}, true
case []func(V, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(_ V, event E) {
v(event)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(view V, _ E) {
v(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V, E) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V, E):
listeners[i] = v
case func(E):
listeners[i] = func(_ V, event E) {
v(event)
}
case func(V):
listeners[i] = func(view V, _ E) {
v(view)
}
case func():
listeners[i] = func(V, E) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
} }
func valueToEventWithOldListeners[V View, E any](value any) ([]func(V, E, E), bool) { func (view *viewData) removeKeyListener(tag PropertyName) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E, E):
return []func(V, E, E){value}, true
case func(V, E):
fn := func(v V, val, _ E) {
value(v, val)
}
return []func(V, E, E){fn}, true
case func(E, E):
fn := func(_ V, val, old E) {
value(val, old)
}
return []func(V, E, E){fn}, true
case func(E):
fn := func(_ V, val, _ E) {
value(val)
}
return []func(V, E, E){fn}, true
case func(V):
fn := func(v V, _, _ E) {
value(v)
}
return []func(V, E, E){fn}, true
case func():
fn := func(V, E, E) {
value()
}
return []func(V, E, E){fn}, true
case []func(V, E, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(V, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
}
return listeners, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
}
return listeners, true
case []func(E, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, _, _ E) {
fn(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(V, E, E) {
fn()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch fn := v.(type) {
case func(V, E, E):
listeners[i] = fn
case func(V, E):
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
case func(E, E):
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
case func(E):
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
case func(V):
listeners[i] = func(view V, _, _ E) {
fn(view)
}
case func():
listeners[i] = func(V, E, E) {
fn()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func (view *viewData) setKeyListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, KeyEvent](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removeKeyListener(tag)
} else {
switch tag {
case KeyDownEvent:
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), "onkeydown", "keyDownEvent(this, event)")
}
case KeyUpEvent:
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), "onkeyup", "keyUpEvent(this, event)")
}
default:
return false
}
}
return true
}
func (view *viewData) removeKeyListener(tag string) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
switch tag { switch tag {
@ -778,34 +457,7 @@ func (view *viewData) removeKeyListener(tag string) {
} }
} }
} }
*/
func getEventWithOldListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E, E)); ok {
return result
}
}
}
return []func(V, E, E){}
}
func getEventListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E)); ok {
return result
}
}
}
return []func(V, E){}
}
func keyEventsHtml(view View, buffer *strings.Builder) { func keyEventsHtml(view View, buffer *strings.Builder) {
if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 { if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
@ -821,7 +473,7 @@ func keyEventsHtml(view View, buffer *strings.Builder) {
} }
} }
func handleKeyEvents(view View, tag string, data DataObject) { func handleKeyEvents(view View, tag PropertyName, data DataObject) {
var event KeyEvent var event KeyEvent
event.init(data) event.init(data)
listeners := getEventListeners[View, KeyEvent](view, nil, tag) listeners := getEventListeners[View, KeyEvent](view, nil, tag)

View File

@ -37,6 +37,7 @@ type ListLayout interface {
// UpdateContent updates child Views if the "content" property value is set to ListAdapter, // UpdateContent updates child Views if the "content" property value is set to ListAdapter,
// otherwise does nothing // otherwise does nothing
UpdateContent() UpdateContent()
setAdapter(ListAdapter)
} }
type listLayoutData struct { type listLayoutData struct {
@ -53,7 +54,8 @@ func NewListLayout(session Session, params Params) ListLayout {
} }
func newListLayout(session Session) View { func newListLayout(session Session) View {
return NewListLayout(session, nil) //return NewListLayout(session, nil)
return new(listLayoutData)
} }
// Init initialize fields of ViewsAlignContainer by default values // Init initialize fields of ViewsAlignContainer by default values
@ -61,14 +63,16 @@ func (listLayout *listLayoutData) init(session Session) {
listLayout.viewsContainerData.init(session) listLayout.viewsContainerData.init(session)
listLayout.tag = "ListLayout" listLayout.tag = "ListLayout"
listLayout.systemClass = "ruiListLayout" listLayout.systemClass = "ruiListLayout"
listLayout.normalize = normalizeListLayoutTag
listLayout.getFunc = listLayout.get
listLayout.set = listLayout.setFunc
listLayout.remove = listLayout.removeFunc
listLayout.changed = listLayoutPropertyChanged
} }
func (listLayout *listLayoutData) String() string { func normalizeListLayoutTag(tag PropertyName) PropertyName {
return getViewString(listLayout, nil) tag = defaultNormalize(tag)
}
func (listLayout *listLayoutData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case "wrap": case "wrap":
tag = ListWrap tag = ListWrap
@ -82,79 +86,78 @@ func (listLayout *listLayoutData) normalizeTag(tag string) string {
return tag return tag
} }
func (listLayout *listLayoutData) Get(tag string) any { func (listLayout *listLayoutData) get(self View, tag PropertyName) any {
return listLayout.get(listLayout.normalizeTag(tag)) switch tag {
} case Gap:
func (listLayout *listLayoutData) get(tag string) any {
if tag == Gap {
if rowGap := GetListRowGap(listLayout); rowGap.Equal(GetListColumnGap(listLayout)) { if rowGap := GetListRowGap(listLayout); rowGap.Equal(GetListColumnGap(listLayout)) {
return rowGap return rowGap
} }
return AutoSize() return AutoSize()
}
return listLayout.viewsContainerData.get(tag)
}
func (listLayout *listLayoutData) Remove(tag string) {
listLayout.remove(listLayout.normalizeTag(tag))
}
func (listLayout *listLayoutData) remove(tag string) {
switch tag {
case Gap:
listLayout.remove(ListRowGap)
listLayout.remove(ListColumnGap)
return
case Content: case Content:
listLayout.adapter = nil if listLayout.adapter != nil {
} return listLayout.adapter
listLayout.viewsContainerData.remove(tag)
if listLayout.created {
switch tag {
case Orientation, ListWrap, HorizontalAlign, VerticalAlign:
updateCSSStyle(listLayout.htmlID(), listLayout.session)
} }
} }
return listLayout.viewsContainerData.get(listLayout, tag)
} }
func (listLayout *listLayoutData) Set(tag string, value any) bool { func (listLayout *listLayoutData) removeFunc(self View, tag PropertyName) []PropertyName {
return listLayout.set(listLayout.normalizeTag(tag), value)
}
func (listLayout *listLayoutData) set(tag string, value any) bool {
if value == nil {
listLayout.remove(tag)
return true
}
switch tag { switch tag {
case Gap: case Gap:
return listLayout.set(ListRowGap, value) && listLayout.set(ListColumnGap, value) result := []PropertyName{}
for _, tag := range []PropertyName{ListRowGap, ListColumnGap} {
if listLayout.getRaw(tag) != nil {
listLayout.setRaw(tag, nil)
result = append(result, tag)
}
}
return result
case Content:
listLayout.viewsContainerData.removeFunc(listLayout, Content)
listLayout.adapter = nil
return []PropertyName{Content}
}
return listLayout.viewsContainerData.removeFunc(listLayout, tag)
}
func (listLayout *listLayoutData) setFunc(self View, tag PropertyName, value any) []PropertyName {
switch tag {
case Gap:
result := listLayout.setFunc(listLayout, ListRowGap, value)
if result != nil {
if gap := listLayout.getRaw(ListRowGap); gap != nil {
listLayout.setRaw(ListColumnGap, gap)
result = append(result, ListColumnGap)
}
}
return result
case Content: case Content:
if adapter, ok := value.(ListAdapter); ok { if adapter, ok := value.(ListAdapter); ok {
listLayout.adapter = adapter listLayout.adapter = adapter
listLayout.UpdateContent() listLayout.createContent()
// TODO } else if listLayout.setContent(value) {
return true listLayout.adapter = nil
} else {
return nil
} }
listLayout.adapter = nil return []PropertyName{Content}
} }
return listLayout.viewsContainerData.setFunc(listLayout, tag, value)
}
if listLayout.viewsContainerData.set(tag, value) { func listLayoutPropertyChanged(view View, tag PropertyName) {
if listLayout.created { switch tag {
switch tag { case Orientation, ListWrap, HorizontalAlign, VerticalAlign:
case Orientation, ListWrap, HorizontalAlign, VerticalAlign: updateCSSStyle(view.htmlID(), view.Session())
updateCSSStyle(listLayout.htmlID(), listLayout.session)
} default:
} viewsContainerPropertyChanged(view, tag)
return true
} }
return false
} }
func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builder) { func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
@ -166,7 +169,14 @@ func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builde
} }
} }
func (listLayout *listLayoutData) UpdateContent() { func (listLayout *listLayoutData) setAdapter(adapter ListAdapter) {
listLayout.adapter = adapter
if adapter != nil {
listLayout.createContent()
}
}
func (listLayout *listLayoutData) createContent() bool {
if adapter := listLayout.adapter; adapter != nil { if adapter := listLayout.adapter; adapter != nil {
listLayout.views = []View{} listLayout.views = []View{}
@ -185,11 +195,20 @@ func (listLayout *listLayoutData) UpdateContent() {
} }
} }
return true
}
return false
}
func (listLayout *listLayoutData) UpdateContent() {
if listLayout.createContent() {
if listLayout.created { if listLayout.created {
updateInnerHTML(htmlID, session) updateInnerHTML(listLayout.htmlID(), listLayout.session)
} }
listLayout.propertyChangedEvent(Content) if listener, ok := listLayout.changeListener[Content]; ok {
listener(listLayout, Content)
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - The browser will offer controls to allow the user to control video playback, volume, seeking and pause/resume playback. // `true` or `1` or "true", "yes", "on", "1" - The browser will offer controls to allow the user to control video playback, volume, seeking and pause/resume playback.
// `false` or `0` or "false", "no", "off", "0" - No controls will be visible to the end user. // `false` or `0` or "false", "no", "off", "0" - No controls will be visible to the end user.
Controls = "controls" Controls PropertyName = "controls"
// Loop is the constant for "loop" property tag. // Loop is the constant for "loop" property tag.
// //
@ -55,7 +55,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - The video player will automatically seek back to the start upon reaching the end of the video. // `true` or `1` or "true", "yes", "on", "1" - The video player will automatically seek back to the start upon reaching the end of the video.
// `false` or `0` or "false", "no", "off", "0" - Video player will stop playing when the end of the media file has been reached. // `false` or `0` or "false", "no", "off", "0" - Video player will stop playing when the end of the media file has been reached.
Loop = "loop" Loop PropertyName = "loop"
// Muted is the constant for "muted" property tag. // Muted is the constant for "muted" property tag.
// //
@ -78,7 +78,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Video will be muted. // `true` or `1` or "true", "yes", "on", "1" - Video will be muted.
// `false` or `0` or "false", "no", "off", "0" - Video playing normally. // `false` or `0` or "false", "no", "off", "0" - Video playing normally.
Muted = "muted" Muted PropertyName = "muted"
// Preload is the constant for "preload" property tag. // Preload is the constant for "preload" property tag.
// //
@ -105,7 +105,7 @@ const (
// `0`(`PreloadNone`) or "none" - Media file must not be pre-loaded. // `0`(`PreloadNone`) or "none" - Media file must not be pre-loaded.
// `1`(`PreloadMetadata`) or "metadata" - Only metadata is preloaded. // `1`(`PreloadMetadata`) or "metadata" - Only metadata is preloaded.
// `2`(`PreloadAuto`) or "auto" - The entire media file can be downloaded even if the user doesn't have to use it. // `2`(`PreloadAuto`) or "auto" - The entire media file can be downloaded even if the user doesn't have to use it.
Preload = "preload" Preload PropertyName = "preload"
// AbortEvent is the constant for "abort-event" property tag. // AbortEvent is the constant for "abort-event" property tag.
// //
@ -134,7 +134,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
AbortEvent = "abort-event" AbortEvent PropertyName = "abort-event"
// CanPlayEvent is the constant for "can-play-event" property tag. // CanPlayEvent is the constant for "can-play-event" property tag.
// //
@ -165,7 +165,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
CanPlayEvent = "can-play-event" CanPlayEvent PropertyName = "can-play-event"
// CanPlayThroughEvent is the constant for "can-play-through-event" property tag. // CanPlayThroughEvent is the constant for "can-play-through-event" property tag.
// //
@ -194,7 +194,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
CanPlayThroughEvent = "can-play-through-event" CanPlayThroughEvent PropertyName = "can-play-through-event"
// CompleteEvent is the constant for "complete-event" property tag. // CompleteEvent is the constant for "complete-event" property tag.
// //
@ -223,7 +223,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
CompleteEvent = "complete-event" CompleteEvent PropertyName = "complete-event"
// DurationChangedEvent is the constant for "duration-changed-event" property tag. // DurationChangedEvent is the constant for "duration-changed-event" property tag.
// //
@ -258,7 +258,7 @@ const (
// `func(player rui.MediaPlayer)`, // `func(player rui.MediaPlayer)`,
// `func(duration float64)`, // `func(duration float64)`,
// `func()`. // `func()`.
DurationChangedEvent = "duration-changed-event" DurationChangedEvent PropertyName = "duration-changed-event"
// EmptiedEvent is the constant for "emptied-event" property tag. // EmptiedEvent is the constant for "emptied-event" property tag.
// //
@ -289,7 +289,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
EmptiedEvent = "emptied-event" EmptiedEvent PropertyName = "emptied-event"
// EndedEvent is the constant for "ended-event" property tag. // EndedEvent is the constant for "ended-event" property tag.
// //
@ -318,7 +318,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
EndedEvent = "ended-event" EndedEvent PropertyName = "ended-event"
// LoadedDataEvent is the constant for "loaded-data-event" property tag. // LoadedDataEvent is the constant for "loaded-data-event" property tag.
// //
@ -347,7 +347,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
LoadedDataEvent = "loaded-data-event" LoadedDataEvent PropertyName = "loaded-data-event"
// LoadedMetadataEvent is the constant for "loaded-metadata-event" property tag. // LoadedMetadataEvent is the constant for "loaded-metadata-event" property tag.
// //
@ -376,7 +376,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
LoadedMetadataEvent = "loaded-metadata-event" LoadedMetadataEvent PropertyName = "loaded-metadata-event"
// LoadStartEvent is the constant for "load-start-event" property tag. // LoadStartEvent is the constant for "load-start-event" property tag.
// //
@ -405,7 +405,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
LoadStartEvent = "load-start-event" LoadStartEvent PropertyName = "load-start-event"
// PauseEvent is the constant for "pause-event" property tag. // PauseEvent is the constant for "pause-event" property tag.
// //
@ -434,7 +434,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
PauseEvent = "pause-event" PauseEvent PropertyName = "pause-event"
// PlayEvent is the constant for "play-event" property tag. // PlayEvent is the constant for "play-event" property tag.
// //
@ -463,7 +463,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
PlayEvent = "play-event" PlayEvent PropertyName = "play-event"
// PlayingEvent is the constant for "playing-event" property tag. // PlayingEvent is the constant for "playing-event" property tag.
// //
@ -492,7 +492,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
PlayingEvent = "playing-event" PlayingEvent PropertyName = "playing-event"
// ProgressEvent is the constant for "progress-event" property tag. // ProgressEvent is the constant for "progress-event" property tag.
// //
@ -521,7 +521,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
ProgressEvent = "progress-event" ProgressEvent PropertyName = "progress-event"
// RateChangedEvent is the constant for "rate-changed-event" property tag. // RateChangedEvent is the constant for "rate-changed-event" property tag.
// //
@ -556,7 +556,7 @@ const (
// `func(player rui.MediaPlayer)`, // `func(player rui.MediaPlayer)`,
// `func(rate float64)`, // `func(rate float64)`,
// `func()`. // `func()`.
RateChangedEvent = "rate-changed-event" RateChangedEvent PropertyName = "rate-changed-event"
// SeekedEvent is the constant for "seeked-event" property tag. // SeekedEvent is the constant for "seeked-event" property tag.
// //
@ -585,7 +585,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
SeekedEvent = "seeked-event" SeekedEvent PropertyName = "seeked-event"
// SeekingEvent is the constant for "seeking-event" property tag. // SeekingEvent is the constant for "seeking-event" property tag.
// //
@ -614,7 +614,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
SeekingEvent = "seeking-event" SeekingEvent PropertyName = "seeking-event"
// StalledEvent is the constant for "stalled-event" property tag. // StalledEvent is the constant for "stalled-event" property tag.
// //
@ -643,7 +643,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
StalledEvent = "stalled-event" StalledEvent PropertyName = "stalled-event"
// SuspendEvent is the constant for "suspend-event" property tag. // SuspendEvent is the constant for "suspend-event" property tag.
// //
@ -672,7 +672,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
SuspendEvent = "suspend-event" SuspendEvent PropertyName = "suspend-event"
// TimeUpdateEvent is the constant for "time-update-event" property tag. // TimeUpdateEvent is the constant for "time-update-event" property tag.
// //
@ -707,7 +707,7 @@ const (
// `func(player rui.MediaPlayer)`, // `func(player rui.MediaPlayer)`,
// `func(time float64)`, // `func(time float64)`,
// `func()`. // `func()`.
TimeUpdateEvent = "time-update-event" TimeUpdateEvent PropertyName = "time-update-event"
// VolumeChangedEvent is the constant for "volume-changed-event" property tag. // VolumeChangedEvent is the constant for "volume-changed-event" property tag.
// //
@ -742,7 +742,7 @@ const (
// `func(player rui.MediaPlayer)`, // `func(player rui.MediaPlayer)`,
// `func(volume float64)`, // `func(volume float64)`,
// `func()`. // `func()`.
VolumeChangedEvent = "volume-changed-event" VolumeChangedEvent PropertyName = "volume-changed-event"
// WaitingEvent is the constant for "waiting-event" property tag. // WaitingEvent is the constant for "waiting-event" property tag.
// //
@ -771,7 +771,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
WaitingEvent = "waiting-event" WaitingEvent PropertyName = "waiting-event"
// PlayerErrorEvent is the constant for "player-error-event" property tag. // PlayerErrorEvent is the constant for "player-error-event" property tag.
// //
@ -820,7 +820,7 @@ const (
// `func(code int, message string)`, // `func(code int, message string)`,
// `func(player rui.MediaPlayer)`, // `func(player rui.MediaPlayer)`,
// `func()`. // `func()`.
PlayerErrorEvent = "player-error-event" PlayerErrorEvent PropertyName = "player-error-event"
// PreloadNone - value of the view "preload" property: indicates that the audio/video should not be preloaded. // PreloadNone - value of the view "preload" property: indicates that the audio/video should not be preloaded.
PreloadNone = 0 PreloadNone = 0
@ -907,119 +907,70 @@ type MediaSource struct {
func (player *mediaPlayerData) init(session Session) { func (player *mediaPlayerData) init(session Session) {
player.viewData.init(session) player.viewData.init(session)
player.tag = "MediaPlayer" player.tag = "MediaPlayer"
} player.set = mediaPlayerSet
player.changed = mediaPlayerPropertyChanged
func (player *mediaPlayerData) String() string {
return getViewString(player, nil)
} }
func (player *mediaPlayerData) Focusable() bool { func (player *mediaPlayerData) Focusable() bool {
return true return true
} }
func (player *mediaPlayerData) Remove(tag string) { func mediaPlayerSet(view View, tag PropertyName, value any) []PropertyName {
player.remove(strings.ToLower(tag))
}
func (player *mediaPlayerData) remove(tag string) {
player.viewData.remove(tag)
player.propertyChanged(tag)
}
func (player *mediaPlayerData) Set(tag string, value any) bool {
return player.set(strings.ToLower(tag), value)
}
func (player *mediaPlayerData) set(tag string, value any) bool {
if value == nil {
player.remove(tag)
return true
}
switch tag { switch tag {
case Controls, Loop, Muted, Preload:
if player.viewData.set(tag, value) {
player.propertyChanged(tag)
return true
}
case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent, LoadStartEvent, case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent, LoadStartEvent,
EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent,
ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent: ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent:
if listeners, ok := valueToNoParamListeners[MediaPlayer](value); ok {
if listeners == nil { return setNoParamEventListener[MediaPlayer](view, tag, value)
delete(player.properties, tag)
} else {
player.properties[tag] = listeners
}
player.propertyChanged(tag)
player.propertyChangedEvent(tag)
return true
}
notCompatibleType(tag, value)
case DurationChangedEvent, RateChangedEvent, TimeUpdateEvent, VolumeChangedEvent: case DurationChangedEvent, RateChangedEvent, TimeUpdateEvent, VolumeChangedEvent:
if listeners, ok := valueToEventListeners[MediaPlayer, float64](value); ok {
if listeners == nil { return setViewEventListener[MediaPlayer, float64](view, tag, value)
delete(player.properties, tag)
} else {
player.properties[tag] = listeners
}
player.propertyChanged(tag)
player.propertyChangedEvent(tag)
return true
}
notCompatibleType(tag, value)
case PlayerErrorEvent: case PlayerErrorEvent:
if listeners, ok := valueToPlayerErrorListeners(value); ok { if listeners, ok := valueToPlayerErrorListeners(value); ok {
if listeners == nil { if len(listeners) > 0 {
delete(player.properties, tag) view.setRaw(tag, listeners)
} else if view.getRaw(tag) != nil {
view.setRaw(tag, nil)
} else { } else {
player.properties[tag] = listeners return []PropertyName{}
} }
player.propertyChanged(tag) return []PropertyName{tag}
player.propertyChangedEvent(tag)
return true
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return nil
case Source: case Source:
if player.setSource(value) { return setMediaPlayerSource(view, value)
player.propertyChanged(tag)
player.propertyChangedEvent(tag)
return true
}
default:
return player.viewData.set(tag, value)
} }
return false return viewSet(view, tag, value)
} }
func (player *mediaPlayerData) setSource(value any) bool { func setMediaPlayerSource(properties Properties, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
src := MediaSource{Url: value, MimeType: ""} src := MediaSource{Url: value, MimeType: ""}
player.properties[Source] = []MediaSource{src} properties.setRaw(Source, []MediaSource{src})
case MediaSource: case MediaSource:
player.properties[Source] = []MediaSource{value} properties.setRaw(Source, []MediaSource{value})
case []MediaSource: case []MediaSource:
player.properties[Source] = value properties.setRaw(Source, value)
case DataObject: case DataObject:
url, ok := value.PropertyValue("src") url, ok := value.PropertyValue("src")
if !ok || url == "" { if !ok || url == "" {
invalidPropertyValue(Source, value) invalidPropertyValue(Source, value)
return false return nil
} }
mimeType, _ := value.PropertyValue("mime-type") mimeType, _ := value.PropertyValue("mime-type")
src := MediaSource{Url: url, MimeType: mimeType} src := MediaSource{Url: url, MimeType: mimeType}
player.properties[Source] = []MediaSource{src} properties.setRaw(Source, []MediaSource{src})
case []DataValue: case []DataValue:
src := []MediaSource{} src := []MediaSource{}
@ -1031,7 +982,7 @@ func (player *mediaPlayerData) setSource(value any) bool {
src = append(src, MediaSource{Url: url, MimeType: mimeType}) src = append(src, MediaSource{Url: url, MimeType: mimeType})
} else { } else {
invalidPropertyValue(Source, value) invalidPropertyValue(Source, value)
return false return nil
} }
} else { } else {
src = append(src, MediaSource{Url: val.Value(), MimeType: ""}) src = append(src, MediaSource{Url: val.Value(), MimeType: ""})
@ -1040,16 +991,16 @@ func (player *mediaPlayerData) setSource(value any) bool {
if len(src) == 0 { if len(src) == 0 {
invalidPropertyValue(Source, value) invalidPropertyValue(Source, value)
return false return nil
} }
player.properties[Source] = src properties.setRaw(Source, src)
default: default:
notCompatibleType(Source, value) notCompatibleType(Source, value)
return false return nil
} }
return true return []PropertyName{Source}
} }
func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), bool) { func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), bool) {
@ -1177,109 +1128,106 @@ func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), b
return nil, false return nil, false
} }
func playerEvents() []struct{ tag, cssTag string } { func mediaPlayerEvents() map[PropertyName]string {
return []struct{ tag, cssTag string }{ return map[PropertyName]string{
{AbortEvent, "onabort"}, AbortEvent: "onabort",
{CanPlayEvent, "oncanplay"}, CanPlayEvent: "oncanplay",
{CanPlayThroughEvent, "oncanplaythrough"}, CanPlayThroughEvent: "oncanplaythrough",
{CompleteEvent, "oncomplete"}, CompleteEvent: "oncomplete",
{EmptiedEvent, "onemptied"}, EmptiedEvent: "onemptied",
{EndedEvent, "ended"}, EndedEvent: "ended",
{LoadedDataEvent, "onloadeddata"}, LoadedDataEvent: "onloadeddata",
{LoadedMetadataEvent, "onloadedmetadata"}, LoadedMetadataEvent: "onloadedmetadata",
{LoadStartEvent, "onloadstart"}, LoadStartEvent: "onloadstart",
{PauseEvent, "onpause"}, PauseEvent: "onpause",
{PlayEvent, "onplay"}, PlayEvent: "onplay",
{PlayingEvent, "onplaying"}, PlayingEvent: "onplaying",
{ProgressEvent, "onprogress"}, ProgressEvent: "onprogress",
{SeekedEvent, "onseeked"}, SeekedEvent: "onseeked",
{SeekingEvent, "onseeking"}, SeekingEvent: "onseeking",
{StalledEvent, "onstalled"}, StalledEvent: "onstalled",
{SuspendEvent, "onsuspend"}, SuspendEvent: "onsuspend",
{WaitingEvent, "onwaiting"}, WaitingEvent: "onwaiting",
} }
} }
func (player *mediaPlayerData) propertyChanged(tag string) { func mediaPlayerPropertyChanged(view View, tag PropertyName) {
if player.created { session := view.Session()
switch tag {
case Controls, Loop:
value, _ := boolProperty(player, tag, player.session)
if value {
player.session.updateProperty(player.htmlID(), tag, value)
} else {
player.session.removeProperty(player.htmlID(), tag)
}
case Muted: switch tag {
value, _ := boolProperty(player, tag, player.session) case Controls, Loop:
player.session.callFunc("setMediaMuted", player.htmlID(), value) value, _ := boolProperty(view, tag, session)
if value {
case Preload: session.updateProperty(view.htmlID(), string(tag), value)
value, _ := enumProperty(player, tag, player.session, 0) } else {
values := enumProperties[Preload].values session.removeProperty(view.htmlID(), string(tag))
player.session.updateProperty(player.htmlID(), tag, values[value])
case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent,
EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent,
LoadStartEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent:
for _, event := range playerEvents() {
if event.tag == tag {
if value := player.getRaw(event.tag); value != nil {
switch value := value.(type) {
case []func(MediaPlayer):
if len(value) > 0 {
fn := fmt.Sprintf(`playerEvent(this, "%s")`, event.tag)
player.session.updateProperty(player.htmlID(), event.cssTag, fn)
return
}
}
}
player.session.updateProperty(player.htmlID(), tag, "")
break
}
}
case TimeUpdateEvent:
if value := player.getRaw(tag); value != nil {
player.session.updateProperty(player.htmlID(), "ontimeupdate", "playerTimeUpdatedEvent(this)")
} else {
player.session.updateProperty(player.htmlID(), "ontimeupdate", "")
}
case VolumeChangedEvent:
if value := player.getRaw(tag); value != nil {
player.session.updateProperty(player.htmlID(), "onvolumechange", "playerVolumeChangedEvent(this)")
} else {
player.session.updateProperty(player.htmlID(), "onvolumechange", "")
}
case DurationChangedEvent:
if value := player.getRaw(tag); value != nil {
player.session.updateProperty(player.htmlID(), "ondurationchange", "playerDurationChangedEvent(this)")
} else {
player.session.updateProperty(player.htmlID(), "ondurationchange", "")
}
case RateChangedEvent:
if value := player.getRaw(tag); value != nil {
player.session.updateProperty(player.htmlID(), "onratechange", "playerRateChangedEvent(this)")
} else {
player.session.updateProperty(player.htmlID(), "onratechange", "")
}
case PlayerErrorEvent:
if value := player.getRaw(tag); value != nil {
player.session.updateProperty(player.htmlID(), "onerror", "playerErrorEvent(this)")
} else {
player.session.updateProperty(player.htmlID(), "onerror", "")
}
case Source:
updateInnerHTML(player.htmlID(), player.session)
} }
case Muted:
value, _ := boolProperty(view, Muted, session)
session.callFunc("setMediaMuted", view.htmlID(), value)
case Preload:
value, _ := enumProperty(view, Preload, session, 0)
values := enumProperties[Preload].values
session.updateProperty(view.htmlID(), string(Preload), values[value])
case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent,
EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent,
LoadStartEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent:
if cssTag, ok := mediaPlayerEvents()[tag]; ok {
fn := ""
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(MediaPlayer)); ok && len(listeners) > 0 {
fn = fmt.Sprintf(`viewEvent(this, "%s")`, string(tag))
}
}
session.updateProperty(view.htmlID(), cssTag, fn)
}
case TimeUpdateEvent:
if value := view.getRaw(tag); value != nil {
session.updateProperty(view.htmlID(), "ontimeupdate", "viewTimeUpdatedEvent(this)")
} else {
session.updateProperty(view.htmlID(), "ontimeupdate", "")
}
case VolumeChangedEvent:
if value := view.getRaw(tag); value != nil {
session.updateProperty(view.htmlID(), "onvolumechange", "viewVolumeChangedEvent(this)")
} else {
session.updateProperty(view.htmlID(), "onvolumechange", "")
}
case DurationChangedEvent:
if value := view.getRaw(tag); value != nil {
session.updateProperty(view.htmlID(), "ondurationchange", "viewDurationChangedEvent(this)")
} else {
session.updateProperty(view.htmlID(), "ondurationchange", "")
}
case RateChangedEvent:
if value := view.getRaw(tag); value != nil {
session.updateProperty(view.htmlID(), "onratechange", "viewRateChangedEvent(this)")
} else {
session.updateProperty(view.htmlID(), "onratechange", "")
}
case PlayerErrorEvent:
if value := view.getRaw(tag); value != nil {
session.updateProperty(view.htmlID(), "onerror", "viewErrorEvent(this)")
} else {
session.updateProperty(view.htmlID(), "onerror", "")
}
case Source:
updateInnerHTML(view.htmlID(), session)
default:
viewPropertyChanged(view, tag)
} }
} }
func (player *mediaPlayerData) htmlSubviews(self View, buffer *strings.Builder) { func (player *mediaPlayerData) htmlSubviews(self View, buffer *strings.Builder) {
@ -1305,10 +1253,10 @@ func (player *mediaPlayerData) htmlSubviews(self View, buffer *strings.Builder)
func (player *mediaPlayerData) htmlProperties(self View, buffer *strings.Builder) { func (player *mediaPlayerData) htmlProperties(self View, buffer *strings.Builder) {
player.viewData.htmlProperties(self, buffer) player.viewData.htmlProperties(self, buffer)
for _, tag := range []string{Controls, Loop, Muted, Preload} { for _, tag := range []PropertyName{Controls, Loop, Muted, Preload} {
if value, _ := boolProperty(player, tag, player.session); value { if value, _ := boolProperty(player, tag, player.session); value {
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(tag) buffer.WriteString(string(tag))
} }
} }
@ -1319,17 +1267,14 @@ func (player *mediaPlayerData) htmlProperties(self View, buffer *strings.Builder
buffer.WriteRune('"') buffer.WriteRune('"')
} }
for _, event := range playerEvents() { for tag, cssTag := range mediaPlayerEvents() {
if value := player.getRaw(event.tag); value != nil { if value := player.getRaw(tag); value != nil {
switch value := value.(type) { if listeners, ok := value.([]func(MediaPlayer)); ok && len(listeners) > 0 {
case []func(MediaPlayer): buffer.WriteString(` `)
if len(value) > 0 { buffer.WriteString(cssTag)
buffer.WriteString(` `) buffer.WriteString(`="playerEvent(this, '`)
buffer.WriteString(event.cssTag) buffer.WriteString(string(tag))
buffer.WriteString(`="playerEvent(this, '`) buffer.WriteString(`')"`)
buffer.WriteString(event.tag)
buffer.WriteString(`')"`)
}
} }
} }
} }
@ -1355,7 +1300,7 @@ func (player *mediaPlayerData) htmlProperties(self View, buffer *strings.Builder
} }
} }
func (player *mediaPlayerData) handleCommand(self View, command string, data DataObject) bool { func (player *mediaPlayerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, LoadStartEvent, case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, LoadStartEvent,
EmptiedEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, EmptiedEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent,

View File

@ -23,7 +23,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
ClickEvent = "click-event" ClickEvent PropertyName = "click-event"
// DoubleClickEvent is the constant for "double-click-event" property tag. // DoubleClickEvent is the constant for "double-click-event" property tag.
// //
@ -41,7 +41,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
DoubleClickEvent = "double-click-event" DoubleClickEvent PropertyName = "double-click-event"
// MouseDown is the constant for "mouse-down" property tag. // MouseDown is the constant for "mouse-down" property tag.
// //
@ -59,7 +59,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
MouseDown = "mouse-down" MouseDown PropertyName = "mouse-down"
// MouseUp is the constant for "mouse-up" property tag. // MouseUp is the constant for "mouse-up" property tag.
// //
@ -78,7 +78,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
MouseUp = "mouse-up" MouseUp PropertyName = "mouse-up"
// MouseMove is the constant for "mouse-move" property tag. // MouseMove is the constant for "mouse-move" property tag.
// //
@ -96,7 +96,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
MouseMove = "mouse-move" MouseMove PropertyName = "mouse-move"
// MouseOut is the constant for "mouse-out" property tag. // MouseOut is the constant for "mouse-out" property tag.
// //
@ -116,7 +116,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
MouseOut = "mouse-out" MouseOut PropertyName = "mouse-out"
// MouseOver is the constant for "mouse-over" property tag. // MouseOver is the constant for "mouse-over" property tag.
// //
@ -135,7 +135,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
MouseOver = "mouse-over" MouseOver PropertyName = "mouse-over"
// ContextMenuEvent is the constant for "context-menu-event" property tag. // ContextMenuEvent is the constant for "context-menu-event" property tag.
// //
@ -153,7 +153,7 @@ const (
// `func(view rui.View)`, // `func(view rui.View)`,
// `func(event rui.MouseEvent)`, // `func(event rui.MouseEvent)`,
// `func()`. // `func()`.
ContextMenuEvent = "context-menu-event" ContextMenuEvent PropertyName = "context-menu-event"
// PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state // PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state
PrimaryMouseButton = 0 PrimaryMouseButton = 0
@ -228,54 +228,39 @@ type MouseEvent struct {
MetaKey bool MetaKey bool
} }
var mouseEvents = map[string]struct{ jsEvent, jsFunc string }{ /*
ClickEvent: {jsEvent: "onclick", jsFunc: "clickEvent"}, func setMouseListener(properties Properties, tag PropertyName, value any) bool {
DoubleClickEvent: {jsEvent: "ondblclick", jsFunc: "doubleClickEvent"}, if listeners, ok := valueToEventListeners[View, MouseEvent](value); ok {
MouseDown: {jsEvent: "onmousedown", jsFunc: "mouseDownEvent"}, if len(listeners) == 0 {
MouseUp: {jsEvent: "onmouseup", jsFunc: "mouseUpEvent"}, properties.setRaw(tag, nil)
MouseMove: {jsEvent: "onmousemove", jsFunc: "mouseMoveEvent"}, } else {
MouseOut: {jsEvent: "onmouseout", jsFunc: "mouseOutEvent"}, properties.setRaw(tag, listeners)
MouseOver: {jsEvent: "onmouseover", jsFunc: "mouseOverEvent"},
ContextMenuEvent: {jsEvent: "oncontextmenu", jsFunc: "contextMenuEvent"},
}
func (view *viewData) setMouseListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, MouseEvent](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removeMouseListener(tag)
} else if js, ok := mouseEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
} }
} else { return true
return false
} }
return true notCompatibleType(tag, value)
return false
} }
func (view *viewData) removeMouseListener(tag string) { func (view *viewData) removeMouseListener(tag PropertyName) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
if js, ok := mouseEvents[tag]; ok { if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent) view.session.removeProperty(view.htmlID(), js.jsEvent)
} }
} }
} }
func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) { func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
for tag, js := range mouseEvents { for _, tag := range []PropertyName{ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent} {
if value := view.getRaw(tag); value != nil { if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 { if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(js.jsEvent) if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
@ -285,6 +270,7 @@ func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `) buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `)
} }
} }
*/
func getTimeStamp(data DataObject) uint64 { func getTimeStamp(data DataObject) uint64 {
if value, ok := data.PropertyValue("timeStamp"); ok { if value, ok := data.PropertyValue("timeStamp"); ok {
@ -315,7 +301,7 @@ func (event *MouseEvent) init(data DataObject) {
event.MetaKey = dataBoolProperty(data, "metaKey") event.MetaKey = dataBoolProperty(data, "metaKey")
} }
func handleMouseEvents(view View, tag string, data DataObject) { func handleMouseEvents(view View, tag PropertyName, data DataObject) {
listeners := getEventListeners[View, MouseEvent](view, nil, tag) listeners := getEventListeners[View, MouseEvent](view, nil, tag)
if len(listeners) > 0 { if len(listeners) > 0 {
var event MouseEvent var event MouseEvent

View File

@ -26,7 +26,7 @@ const (
// `func(newValue, oldValue float64)`, // `func(newValue, oldValue float64)`,
// `func(newValue float64)`, // `func(newValue float64)`,
// `func()`. // `func()`.
NumberChangedEvent = "number-changed" NumberChangedEvent PropertyName = "number-changed"
// NumberPickerType is the constant for "number-picker-type" property tag. // NumberPickerType is the constant for "number-picker-type" property tag.
// //
@ -38,7 +38,7 @@ const (
// Values: // Values:
// `0`(`NumberEditor`) or "editor" - Displayed as an editor. // `0`(`NumberEditor`) or "editor" - Displayed as an editor.
// `1`(`NumberSlider`) or "slider" - Displayed as a slider. // `1`(`NumberSlider`) or "slider" - Displayed as a slider.
NumberPickerType = "number-picker-type" NumberPickerType PropertyName = "number-picker-type"
// NumberPickerMin is the constant for "number-picker-min" property tag. // NumberPickerMin is the constant for "number-picker-min" property tag.
// //
@ -48,7 +48,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
NumberPickerMin = "number-picker-min" NumberPickerMin PropertyName = "number-picker-min"
// NumberPickerMax is the constant for "number-picker-max" property tag. // NumberPickerMax is the constant for "number-picker-max" property tag.
// //
@ -58,7 +58,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
NumberPickerMax = "number-picker-max" NumberPickerMax PropertyName = "number-picker-max"
// NumberPickerStep is the constant for "number-picker-step" property tag. // NumberPickerStep is the constant for "number-picker-step" property tag.
// //
@ -68,7 +68,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
NumberPickerStep = "number-picker-step" NumberPickerStep PropertyName = "number-picker-step"
// NumberPickerValue is the constant for "number-picker-value" property tag. // NumberPickerValue is the constant for "number-picker-value" property tag.
// //
@ -78,7 +78,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
NumberPickerValue = "number-picker-value" NumberPickerValue PropertyName = "number-picker-value"
) )
// Constants which describe values of the "number-picker-type" property of a [NumberPicker] // Constants which describe values of the "number-picker-type" property of a [NumberPicker]
@ -97,8 +97,6 @@ type NumberPicker interface {
type numberPickerData struct { type numberPickerData struct {
viewData viewData
dataList
numberChangedListeners []func(NumberPicker, float64, float64)
} }
// NewNumberPicker create new NumberPicker object and return it // NewNumberPicker create new NumberPicker object and return it
@ -110,165 +108,92 @@ func NewNumberPicker(session Session, params Params) NumberPicker {
} }
func newNumberPicker(session Session) View { func newNumberPicker(session Session) View {
return NewNumberPicker(session, nil) return new(numberPickerData)
} }
func (picker *numberPickerData) init(session Session) { func (picker *numberPickerData) init(session Session) {
picker.viewData.init(session) picker.viewData.init(session)
picker.tag = "NumberPicker" picker.tag = "NumberPicker"
picker.hasHtmlDisabled = true picker.hasHtmlDisabled = true
picker.numberChangedListeners = []func(NumberPicker, float64, float64){} picker.normalize = normalizeNumberPickerTag
picker.dataListInit() picker.set = numberPickerSet
} picker.changed = numberPickerPropertyChanged
func (picker *numberPickerData) String() string {
return getViewString(picker, nil)
} }
func (picker *numberPickerData) Focusable() bool { func (picker *numberPickerData) Focusable() bool {
return true return true
} }
func (picker *numberPickerData) normalizeTag(tag string) string { func normalizeNumberPickerTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case Type, Min, Max, Step, Value: case Type, Min, Max, Step, Value:
return "number-picker-" + tag return "number-picker-" + tag
} }
return picker.normalizeDataListTag(tag) return normalizeDataListTag(tag)
} }
func (picker *numberPickerData) Remove(tag string) { func numberPickerSet(view View, tag PropertyName, value any) []PropertyName {
picker.remove(picker.normalizeTag(tag))
}
func (picker *numberPickerData) remove(tag string) {
switch tag { switch tag {
case NumberChangedEvent: case NumberChangedEvent:
if len(picker.numberChangedListeners) > 0 { return setEventWithOldListener[NumberPicker, float64](view, tag, value)
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
picker.propertyChangedEvent(tag)
}
case NumberPickerValue: case NumberPickerValue:
oldValue := GetNumberPickerValue(picker) view.setRaw("old-number", GetNumberPickerValue(view))
picker.viewData.remove(tag) min, max := GetNumberPickerMinMax(view)
if oldValue != 0 {
if picker.created { return setFloatProperty(view, NumberPickerValue, value, min, max)
picker.session.callFunc("setInputValue", picker.htmlID(), 0)
}
for _, listener := range picker.numberChangedListeners {
listener(picker, 0, oldValue)
}
picker.propertyChangedEvent(tag)
}
case DataList: case DataList:
if len(picker.dataList.dataList) > 0 { return setDataList(view, value, "")
picker.setDataList(picker, []string{}, true)
}
default:
picker.viewData.remove(tag)
picker.propertyChanged(tag)
}
}
func (picker *numberPickerData) Set(tag string, value any) bool {
return picker.set(picker.normalizeTag(tag), value)
}
func (picker *numberPickerData) set(tag string, value any) bool {
if value == nil {
picker.remove(tag)
return true
} }
return viewSet(view, tag, value)
}
func numberPickerPropertyChanged(view View, tag PropertyName) {
switch tag { switch tag {
case NumberChangedEvent: case NumberPickerType:
listeners, ok := valueToEventWithOldListeners[NumberPicker, float64](value) if GetNumberPickerType(view) == NumberSlider {
if !ok { view.Session().updateProperty(view.htmlID(), "type", "range")
notCompatibleType(tag, value) } else {
return false view.Session().updateProperty(view.htmlID(), "type", "number")
} else if listeners == nil {
listeners = []func(NumberPicker, float64, float64){}
} }
picker.numberChangedListeners = listeners
picker.propertyChangedEvent(tag)
return true
case NumberPickerValue: case NumberPickerMin:
oldValue := GetNumberPickerValue(picker) min, _ := GetNumberPickerMinMax(view)
min, max := GetNumberPickerMinMax(picker) view.Session().updateProperty(view.htmlID(), "min", strconv.FormatFloat(min, 'f', -1, 32))
if picker.setFloatProperty(NumberPickerValue, value, min, max) {
if f, ok := floatProperty(picker, NumberPickerValue, picker.Session(), min); ok && f != oldValue { case NumberPickerMax:
newValue, _ := floatTextProperty(picker, NumberPickerValue, picker.Session(), min) _, max := GetNumberPickerMinMax(view)
if picker.created { view.Session().updateProperty(view.htmlID(), "max", strconv.FormatFloat(max, 'f', -1, 32))
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
case NumberPickerStep:
if step := GetNumberPickerStep(view); step > 0 {
view.Session().updateProperty(view.htmlID(), "step", strconv.FormatFloat(step, 'f', -1, 32))
} else {
view.Session().updateProperty(view.htmlID(), "step", "any")
}
case TimePickerValue:
value := GetNumberPickerValue(view)
view.Session().callFunc("setInputValue", view.htmlID(), value)
if listeners := GetNumberChangedListeners(view); len(listeners) > 0 {
old := 0.0
if val := view.getRaw("old-number"); val != nil {
if n, ok := val.(float64); ok {
old = n
} }
for _, listener := range picker.numberChangedListeners {
listener(picker, f, oldValue)
}
picker.propertyChangedEvent(tag)
} }
return true for _, listener := range listeners {
listener(view, value, old)
}
} }
case DataList:
return picker.setDataList(picker, value, picker.created)
default: default:
if picker.viewData.set(tag, value) { viewPropertyChanged(view, tag)
picker.propertyChanged(tag)
return true
}
}
return false
}
func (picker *numberPickerData) propertyChanged(tag string) {
if picker.created {
switch tag {
case NumberPickerType:
if GetNumberPickerType(picker) == NumberSlider {
picker.session.updateProperty(picker.htmlID(), "type", "range")
} else {
picker.session.updateProperty(picker.htmlID(), "type", "number")
}
case NumberPickerMin:
min, _ := GetNumberPickerMinMax(picker)
picker.session.updateProperty(picker.htmlID(), Min, strconv.FormatFloat(min, 'f', -1, 32))
case NumberPickerMax:
_, max := GetNumberPickerMinMax(picker)
picker.session.updateProperty(picker.htmlID(), Max, strconv.FormatFloat(max, 'f', -1, 32))
case NumberPickerStep:
if step := GetNumberPickerStep(picker); step > 0 {
picker.session.updateProperty(picker.htmlID(), Step, strconv.FormatFloat(step, 'f', -1, 32))
} else {
picker.session.updateProperty(picker.htmlID(), Step, "any")
}
}
}
}
func (picker *numberPickerData) Get(tag string) any {
return picker.get(picker.normalizeTag(tag))
}
func (picker *numberPickerData) get(tag string) any {
switch tag {
case NumberChangedEvent:
return picker.numberChangedListeners
case DataList:
return picker.dataList.dataList
default:
return picker.viewData.get(tag)
} }
} }
@ -277,7 +202,10 @@ func (picker *numberPickerData) htmlTag() string {
} }
func (picker *numberPickerData) htmlSubviews(self View, buffer *strings.Builder) { func (picker *numberPickerData) htmlSubviews(self View, buffer *strings.Builder) {
picker.dataListHtmlSubviews(self, buffer) dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
text, _ = session.resolveConstants(text)
return text
})
} }
func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) { func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) {
@ -317,10 +245,10 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde
buffer.WriteString(` oninput="editViewInputEvent(this)"`) buffer.WriteString(` oninput="editViewInputEvent(this)"`)
picker.dataListHtmlProperties(picker, buffer) dataListHtmlProperties(picker, buffer)
} }
func (picker *numberPickerData) handleCommand(self View, command string, data DataObject) bool { func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyValue("text"); ok {
@ -328,9 +256,12 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da
oldValue := GetNumberPickerValue(picker) oldValue := GetNumberPickerValue(picker)
picker.properties[NumberPickerValue] = text picker.properties[NumberPickerValue] = text
if value != oldValue { if value != oldValue {
for _, listener := range picker.numberChangedListeners { for _, listener := range GetNumberChangedListeners(picker) {
listener(picker, value, oldValue) listener(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[NumberPickerValue]; ok {
listener(picker, NumberPickerValue)
}
} }
} }
} }

View File

@ -16,7 +16,7 @@ type OutlineProperty interface {
} }
type outlinePropertyData struct { type outlinePropertyData struct {
propertyList dataProperty
} }
// NewOutlineProperty creates the new OutlineProperty. // NewOutlineProperty creates the new OutlineProperty.
@ -27,22 +27,29 @@ type outlinePropertyData struct {
// "width" (Width). Determines the line thickness (SizeUnit). // "width" (Width). Determines the line thickness (SizeUnit).
func NewOutlineProperty(params Params) OutlineProperty { func NewOutlineProperty(params Params) OutlineProperty {
outline := new(outlinePropertyData) outline := new(outlinePropertyData)
outline.properties = map[string]any{} outline.init()
for tag, value := range params { for tag, value := range params {
outline.Set(tag, value) outline.Set(tag, value)
} }
return outline return outline
} }
func (outline *outlinePropertyData) init() {
outline.propertyList.init()
outline.normalize = normalizeOutlineTag
outline.set = outlineSet
outline.supportedProperties = []PropertyName{Style, Width, ColorTag}
}
func (outline *outlinePropertyData) writeString(buffer *strings.Builder, indent string) { func (outline *outlinePropertyData) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ") buffer.WriteString("_{ ")
comma := false comma := false
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := outline.properties[tag]; ok { if value, ok := outline.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, BorderStyle, value, indent) writePropertyValue(buffer, BorderStyle, value, indent)
comma = true comma = true
@ -56,46 +63,33 @@ func (outline *outlinePropertyData) String() string {
return runStringWriter(outline) return runStringWriter(outline)
} }
func (outline *outlinePropertyData) normalizeTag(tag string) string { func normalizeOutlineTag(tag PropertyName) PropertyName {
return strings.TrimPrefix(strings.ToLower(tag), "outline-") tag = defaultNormalize(tag)
return PropertyName(strings.TrimPrefix(string(tag), "outline-"))
} }
func (outline *outlinePropertyData) Remove(tag string) { func outlineSet(properties Properties, tag PropertyName, value any) []PropertyName {
delete(outline.properties, outline.normalizeTag(tag))
}
func (outline *outlinePropertyData) Set(tag string, value any) bool {
if value == nil {
outline.Remove(tag)
return true
}
tag = outline.normalizeTag(tag)
switch tag { switch tag {
case Style: case Style:
return outline.setEnumProperty(Style, value, enumProperties[BorderStyle].values) return setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values)
case Width: case Width:
if width, ok := value.(SizeUnit); ok { if width, ok := value.(SizeUnit); ok {
switch width.Type { switch width.Type {
case SizeInFraction, SizeInPercent: case SizeInFraction, SizeInPercent:
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
return outline.setSizeProperty(Width, value) return setSizeProperty(properties, Width, value)
case ColorTag: case ColorTag:
return outline.setColorProperty(ColorTag, value) return setColorProperty(properties, ColorTag, value)
default: default:
ErrorLogF(`"%s" property is not compatible with the OutlineProperty`, tag) ErrorLogF(`"%s" property is not compatible with the OutlineProperty`, tag)
} }
return false return nil
}
func (outline *outlinePropertyData) Get(tag string) any {
return outline.propertyList.Get(outline.normalizeTag(tag))
} }
func (outline *outlinePropertyData) ViewOutline(session Session) ViewOutline { func (outline *outlinePropertyData) ViewOutline(session Session) ViewOutline {
@ -132,7 +126,7 @@ func (outline ViewOutline) cssString(session Session) string {
return builder.finish() return builder.finish()
} }
func getOutline(properties Properties) OutlineProperty { func getOutlineProperty(properties Properties) OutlineProperty {
if value := properties.Get(Outline); value != nil { if value := properties.Get(Outline); value != nil {
if outline, ok := value.(OutlineProperty); ok { if outline, ok := value.(OutlineProperty); ok {
return outline return outline
@ -142,30 +136,30 @@ func getOutline(properties Properties) OutlineProperty {
return nil return nil
} }
func (style *viewStyle) setOutline(value any) bool { func setOutlineProperty(properties Properties, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case OutlineProperty: case OutlineProperty:
style.properties[Outline] = value properties.setRaw(Outline, value)
case ViewOutline: case ViewOutline:
style.properties[Outline] = NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}) properties.setRaw(Outline, NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}))
case ViewBorder: case ViewBorder:
style.properties[Outline] = NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}) properties.setRaw(Outline, NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}))
case DataObject: case DataObject:
outline := NewOutlineProperty(nil) outline := NewOutlineProperty(nil)
for _, tag := range []string{Style, Width, ColorTag} { for _, tag := range []PropertyName{Style, Width, ColorTag} {
if text, ok := value.PropertyValue(tag); ok && text != "" { if text, ok := value.PropertyValue(string(tag)); ok && text != "" {
outline.Set(tag, text) outline.Set(tag, text)
} }
} }
style.properties[Outline] = outline properties.setRaw(Outline, outline)
default: default:
notCompatibleType(Outline, value) notCompatibleType(Outline, value)
return false return nil
} }
return true return []PropertyName{Outline}
} }

View File

@ -3,15 +3,15 @@ package rui
import "sort" import "sort"
// Params defines a type of a parameters list // Params defines a type of a parameters list
type Params map[string]any type Params map[PropertyName]any
// Get returns a value of the property with name defined by the argument. The type of return value depends // Get returns a value of the property with name defined by the argument. The type of return value depends
// on the property. If the property is not set then nil is returned. // on the property. If the property is not set then nil is returned.
func (params Params) Get(tag string) any { func (params Params) Get(tag PropertyName) any {
return params.getRaw(tag) return params.getRaw(tag)
} }
func (params Params) getRaw(tag string) any { func (params Params) getRaw(tag PropertyName) any {
if value, ok := params[tag]; ok { if value, ok := params[tag]; ok {
return value return value
} }
@ -20,12 +20,12 @@ func (params Params) getRaw(tag string) any {
// Set sets the value (second argument) of the property with name defined by the first argument. // Set 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" is returned and a description of an error is written to the log // Return "true" if the value has been set, in the opposite case "false" is returned and a description of an error is written to the log
func (params Params) Set(tag string, value any) bool { func (params Params) Set(tag PropertyName, value any) bool {
params.setRaw(tag, value) params.setRaw(tag, value)
return true return true
} }
func (params Params) setRaw(tag string, value any) { func (params Params) setRaw(tag PropertyName, value any) {
if value != nil { if value != nil {
params[tag] = value params[tag] = value
} else { } else {
@ -34,7 +34,7 @@ func (params Params) setRaw(tag string, value any) {
} }
// Remove removes the property with name defined by the argument from a map. // Remove removes the property with name defined by the argument from a map.
func (params Params) Remove(tag string) { func (params Params) Remove(tag PropertyName) {
delete(params, tag) delete(params, tag)
} }
@ -46,11 +46,17 @@ func (params Params) Clear() {
} }
// AllTags returns a sorted slice of all properties. // AllTags returns a sorted slice of all properties.
func (params Params) AllTags() []string { func (params Params) AllTags() []PropertyName {
tags := make([]string, 0, len(params)) tags := make([]PropertyName, 0, len(params))
for t := range params { for t := range params {
tags = append(tags, t) tags = append(tags, t)
} }
sort.Strings(tags) sort.Slice(tags, func(i, j int) bool {
return tags[i] < tags[j]
})
return tags return tags
} }
func (params Params) empty() bool {
return len(params) == 0
}

View File

@ -55,7 +55,6 @@ type Path interface {
// If the shape has already been closed or has only one point, this function does nothing. // If the shape has already been closed or has only one point, this function does nothing.
Close() Close()
//create(session Session)
obj() any obj() any
} }

View File

@ -1,9 +1,5 @@
package rui package rui
import (
"strings"
)
// Constants for [View] specific pointer events properties // Constants for [View] specific pointer events properties
const ( const (
// PointerDown is the constant for "pointer-down" property tag. // PointerDown is the constant for "pointer-down" property tag.
@ -24,7 +20,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerDown = "pointer-down" PointerDown PropertyName = "pointer-down"
// PointerUp is the constant for "pointer-up" property tag. // PointerUp is the constant for "pointer-up" property tag.
// //
@ -42,7 +38,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerUp = "pointer-up" PointerUp PropertyName = "pointer-up"
// PointerMove is the constant for "pointer-move" property tag. // PointerMove is the constant for "pointer-move" property tag.
// //
@ -60,7 +56,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerMove = "pointer-move" PointerMove PropertyName = "pointer-move"
// PointerCancel is the constant for "pointer-cancel" property tag. // PointerCancel is the constant for "pointer-cancel" property tag.
// //
@ -78,7 +74,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerCancel = "pointer-cancel" PointerCancel PropertyName = "pointer-cancel"
// PointerOut is the constant for "pointer-out" property tag. // PointerOut is the constant for "pointer-out" property tag.
// //
@ -98,7 +94,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerOut = "pointer-out" PointerOut PropertyName = "pointer-out"
// PointerOver is the constant for "pointer-over" property tag. // PointerOver is the constant for "pointer-over" property tag.
// //
@ -116,7 +112,7 @@ const (
// `func(event rui.PointerEvent)`, // `func(event rui.PointerEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
PointerOver = "pointer-over" PointerOver PropertyName = "pointer-over"
) )
// PointerEvent represent a stylus events. Also inherit [MouseEvent] attributes // PointerEvent represent a stylus events. Also inherit [MouseEvent] attributes
@ -158,56 +154,44 @@ type PointerEvent struct {
IsPrimary bool IsPrimary bool
} }
var pointerEvents = map[string]struct{ jsEvent, jsFunc string }{ /*
PointerDown: {jsEvent: "onpointerdown", jsFunc: "pointerDownEvent"}, func setPointerListener(properties Properties, tag PropertyName, value any) bool {
PointerUp: {jsEvent: "onpointerup", jsFunc: "pointerUpEvent"}, if listeners, ok := valueToEventListeners[View, PointerEvent](value); ok {
PointerMove: {jsEvent: "onpointermove", jsFunc: "pointerMoveEvent"}, if len(listeners) == 0 {
PointerCancel: {jsEvent: "onpointercancel", jsFunc: "pointerCancelEvent"}, properties.setRaw(tag, nil)
PointerOut: {jsEvent: "onpointerout", jsFunc: "pointerOutEvent"}, } else {
PointerOver: {jsEvent: "onpointerover", jsFunc: "pointerOverEvent"}, properties.setRaw(tag, listeners)
}
func (view *viewData) setPointerListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, PointerEvent](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removePointerListener(tag)
} else if js, ok := pointerEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
} }
} else { return true
return false
} }
return true notCompatibleType(tag, value)
return false
} }
func (view *viewData) removePointerListener(tag string) { func (view *viewData) removePointerListener(tag PropertyName) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
if js, ok := pointerEvents[tag]; ok { if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent) view.session.removeProperty(view.htmlID(), js.jsEvent)
} }
} }
} }
func pointerEventsHtml(view View, buffer *strings.Builder) { func pointerEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range pointerEvents { for _, tag := range []PropertyName{PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel} {
if value := view.getRaw(tag); value != nil { if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 { if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(js.jsEvent) if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
} }
*/
func (event *PointerEvent) init(data DataObject) { func (event *PointerEvent) init(data DataObject) {
event.MouseEvent.init(data) event.MouseEvent.init(data)
@ -225,7 +209,7 @@ func (event *PointerEvent) init(data DataObject) {
event.IsPrimary = dataBoolProperty(data, "isPrimary") event.IsPrimary = dataBoolProperty(data, "isPrimary")
} }
func handlePointerEvents(view View, tag string, data DataObject) { func handlePointerEvents(view View, tag PropertyName, data DataObject) {
listeners := getEventListeners[View, PointerEvent](view, nil, tag) listeners := getEventListeners[View, PointerEvent](view, nil, tag)
if len(listeners) == 0 { if len(listeners) == 0 {
return return

View File

@ -27,7 +27,7 @@ const (
// Set popup title style. Default title style is "ruiPopupTitle". // Set popup title style. Default title style is "ruiPopupTitle".
// //
// Supported types: `string`. // Supported types: `string`.
TitleStyle = "title-style" TitleStyle PropertyName = "title-style"
// CloseButton is the constant for "close-button" property tag. // CloseButton is the constant for "close-button" property tag.
// //
@ -39,7 +39,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Close button will be added to a title bar of a window. // `true` or `1` or "true", "yes", "on", "1" - Close button will be added to a title bar of a window.
// `false` or `0` or "false", "no", "off", "0" - Popup without a close button. // `false` or `0` or "false", "no", "off", "0" - Popup without a close button.
CloseButton = "close-button" CloseButton PropertyName = "close-button"
// OutsideClose is the constant for "outside-close" property tag. // OutsideClose is the constant for "outside-close" property tag.
// //
@ -51,7 +51,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Clicking outside the popup window will automatically call the `Dismiss()` method. // `true` or `1` or "true", "yes", "on", "1" - Clicking outside the popup window will automatically call the `Dismiss()` method.
// `false` or `0` or "false", "no", "off", "0" - Clicking outside the popup window has no effect. // `false` or `0` or "false", "no", "off", "0" - Clicking outside the popup window has no effect.
OutsideClose = "outside-close" OutsideClose PropertyName = "outside-close"
// Buttons is the constant for "buttons" property tag. // Buttons is the constant for "buttons" property tag.
// //
@ -62,7 +62,7 @@ const (
// //
// Internal type is `[]PopupButton`, other types converted to it during assignment. // Internal type is `[]PopupButton`, other types converted to it during assignment.
// See `PopupButton` description for more details. // See `PopupButton` description for more details.
Buttons = "buttons" Buttons PropertyName = "buttons"
// ButtonsAlign is the constant for "buttons-align" property tag. // ButtonsAlign is the constant for "buttons-align" property tag.
// //
@ -76,7 +76,7 @@ const (
// `1`(`RightAlign`) or "right" - Right alignment. // `1`(`RightAlign`) or "right" - Right alignment.
// `2`(`CenterAlign`) or "center" - Center alignment. // `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Width alignment. // `3`(`StretchAlign`) or "stretch" - Width alignment.
ButtonsAlign = "buttons-align" ButtonsAlign PropertyName = "buttons-align"
// DismissEvent is the constant for "dismiss-event" property tag. // DismissEvent is the constant for "dismiss-event" property tag.
// //
@ -91,7 +91,7 @@ const (
// //
// Allowed listener formats: // Allowed listener formats:
// `func()`. // `func()`.
DismissEvent = "dismiss-event" DismissEvent PropertyName = "dismiss-event"
// Arrow is the constant for "arrow" property tag. // Arrow is the constant for "arrow" property tag.
// //
@ -106,7 +106,7 @@ const (
// `2`(`RightArrow`) or "right" - Arrow on the right side of the pop-up window. // `2`(`RightArrow`) or "right" - Arrow on the right side of the pop-up window.
// `3`(`BottomArrow`) or "bottom" - Arrow at the bottom of the pop-up window. // `3`(`BottomArrow`) or "bottom" - Arrow at the bottom of the pop-up window.
// `4`(`LeftArrow`) or "left" - Arrow on the left side of the pop-up window. // `4`(`LeftArrow`) or "left" - Arrow on the left side of the pop-up window.
Arrow = "arrow" Arrow PropertyName = "arrow"
// ArrowAlign is the constant for "arrow-align" property tag. // ArrowAlign is the constant for "arrow-align" property tag.
// //
@ -119,7 +119,7 @@ const (
// `0`(`TopAlign`/`LeftAlign`) or "top" - Top/left alignment. // `0`(`TopAlign`/`LeftAlign`) or "top" - Top/left alignment.
// `1`(`BottomAlign`/`RightAlign`) or "bottom" - Bottom/right alignment. // `1`(`BottomAlign`/`RightAlign`) or "bottom" - Bottom/right alignment.
// `2`(`CenterAlign`) or "center" - Center alignment. // `2`(`CenterAlign`) or "center" - Center alignment.
ArrowAlign = "arrow-align" ArrowAlign PropertyName = "arrow-align"
// ArrowSize is the constant for "arrow-size" property tag. // ArrowSize is the constant for "arrow-size" property tag.
// //
@ -130,7 +130,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ArrowSize = "arrow-size" ArrowSize PropertyName = "arrow-size"
// ArrowWidth is the constant for "arrow-width" property tag. // ArrowWidth is the constant for "arrow-width" property tag.
// //
@ -141,7 +141,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ArrowWidth = "arrow-width" ArrowWidth PropertyName = "arrow-width"
// ArrowOffset is the constant for "arrow-offset" property tag. // ArrowOffset is the constant for "arrow-offset" property tag.
// //
@ -152,7 +152,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
ArrowOffset = "arrow-offset" ArrowOffset PropertyName = "arrow-offset"
// NoneArrow is value of the popup "arrow" property: no arrow // NoneArrow is value of the popup "arrow" property: no arrow
NoneArrow = 0 NoneArrow = 0

View File

@ -15,7 +15,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
ProgressBarMax = "progress-max" ProgressBarMax PropertyName = "progress-max"
// ProgressBarValue is the constant for "progress-value" property tag. // ProgressBarValue is the constant for "progress-value" property tag.
// //
@ -25,7 +25,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
ProgressBarValue = "progress-value" ProgressBarValue PropertyName = "progress-value"
) )
// ProgressBar represents a ProgressBar view // ProgressBar represents a ProgressBar view
@ -46,20 +46,18 @@ func NewProgressBar(session Session, params Params) ProgressBar {
} }
func newProgressBar(session Session) View { func newProgressBar(session Session) View {
return NewProgressBar(session, nil) return new(progressBarData)
} }
func (progress *progressBarData) init(session Session) { func (progress *progressBarData) init(session Session) {
progress.viewData.init(session) progress.viewData.init(session)
progress.tag = "ProgressBar" progress.tag = "ProgressBar"
progress.normalize = normalizeProgressBarTag
progress.changed = progressBarPropertyChanged
} }
func (progress *progressBarData) String() string { func normalizeProgressBarTag(tag PropertyName) PropertyName {
return getViewString(progress, nil) tag = defaultNormalize(tag)
}
func (progress *progressBarData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case Max, "progress-bar-max", "progressbar-max": case Max, "progress-bar-max", "progressbar-max":
return ProgressBarMax return ProgressBarMax
@ -70,45 +68,22 @@ func (progress *progressBarData) normalizeTag(tag string) string {
return tag return tag
} }
func (progress *progressBarData) Remove(tag string) { func progressBarPropertyChanged(view View, tag PropertyName) {
progress.remove(progress.normalizeTag(tag))
}
func (progress *progressBarData) remove(tag string) { switch tag {
progress.viewData.remove(tag) case ProgressBarMax:
progress.propertyChanged(tag) view.Session().updateProperty(view.htmlID(), "max",
} strconv.FormatFloat(GetProgressBarMax(view), 'f', -1, 32))
func (progress *progressBarData) propertyChanged(tag string) { case ProgressBarValue:
if progress.created { view.Session().updateProperty(view.htmlID(), "value",
switch tag { strconv.FormatFloat(GetProgressBarValue(view), 'f', -1, 32))
case ProgressBarMax:
progress.session.updateProperty(progress.htmlID(), Max,
strconv.FormatFloat(GetProgressBarMax(progress), 'f', -1, 32))
case ProgressBarValue: default:
progress.session.updateProperty(progress.htmlID(), Value, viewPropertyChanged(view, tag)
strconv.FormatFloat(GetProgressBarValue(progress), 'f', -1, 32))
}
} }
} }
func (progress *progressBarData) Set(tag string, value any) bool {
return progress.set(progress.normalizeTag(tag), value)
}
func (progress *progressBarData) set(tag string, value any) bool {
if progress.viewData.set(tag, value) {
progress.propertyChanged(tag)
return true
}
return false
}
func (progress *progressBarData) Get(tag string) any {
return progress.get(progress.normalizeTag(tag))
}
func (progress *progressBarData) htmlTag() string { func (progress *progressBarData) htmlTag() string {
return "progress" return "progress"
} }

View File

@ -9,71 +9,96 @@ import (
type Properties interface { type Properties interface {
// Get returns a value of the property with name defined by the argument. // Get returns a value of the property with name defined by the argument.
// The type of return value depends on the property. If the property is not set then nil is returned. // The type of return value depends on the property. If the property is not set then nil is returned.
Get(tag string) any Get(tag PropertyName) any
getRaw(tag string) any getRaw(tag PropertyName) any
// Set sets the value (second argument) of the property with name defined by the first argument. // Set 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 // 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 // a description of the error is written to the log
Set(tag string, value any) bool Set(tag PropertyName, value any) bool
setRaw(tag string, value any) setRaw(tag PropertyName, value any)
// Remove removes the property with name defined by the argument // Remove removes the property with name defined by the argument
Remove(tag string) Remove(tag PropertyName)
// Clear removes all properties // Clear removes all properties
Clear() Clear()
// AllTags returns an array of the set properties // AllTags returns an array of the set properties
AllTags() []string AllTags() []PropertyName
empty() bool
} }
type propertyList struct { type propertyList struct {
properties map[string]any properties map[PropertyName]any
normalize func(PropertyName) PropertyName
//getFunc func(PropertyName) any
//set func(Properties, PropertyName, any) []PropertyName
//remove func(Properties, PropertyName) []PropertyName
}
type dataProperty struct {
propertyList
supportedProperties []PropertyName
get func(Properties, PropertyName) any
set func(Properties, PropertyName, any) []PropertyName
remove func(Properties, PropertyName) []PropertyName
}
func defaultNormalize(tag PropertyName) PropertyName {
return PropertyName(strings.ToLower(strings.Trim(string(tag), " \t")))
} }
func (properties *propertyList) init() { func (properties *propertyList) init() {
properties.properties = map[string]any{} properties.properties = map[PropertyName]any{}
properties.normalize = defaultNormalize
//properties.getFunc = properties.getRaw
//properties.set = propertiesSet
//properties.remove = propertiesRemove
} }
func (properties *propertyList) Get(tag string) any { func (properties *propertyList) empty() bool {
return properties.getRaw(strings.ToLower(tag)) return len(properties.properties) == 0
} }
func (properties *propertyList) getRaw(tag string) any { func (properties *propertyList) getRaw(tag PropertyName) any {
if value, ok := properties.properties[tag]; ok { if value, ok := properties.properties[tag]; ok {
return value return value
} }
return nil return nil
} }
func (properties *propertyList) setRaw(tag string, value any) { func (properties *propertyList) setRaw(tag PropertyName, value any) {
properties.properties[tag] = value if value == nil {
} delete(properties.properties, tag)
} else {
func (properties *propertyList) Remove(tag string) { properties.properties[tag] = value
delete(properties.properties, strings.ToLower(tag))
}
func (properties *propertyList) remove(tag string) {
delete(properties.properties, tag)
}
func (properties *propertyList) Clear() {
properties.properties = map[string]any{}
}
func (properties *propertyList) AllTags() []string {
tags := make([]string, 0, len(properties.properties))
for t := range properties.properties {
tags = append(tags, t)
} }
sort.Strings(tags) }
/*
func (properties *propertyList) Remove(tag PropertyName) {
properties.remove(properties, properties.normalize(tag))
}
*/
func (properties *propertyList) Clear() {
properties.properties = map[PropertyName]any{}
}
func (properties *propertyList) AllTags() []PropertyName {
tags := make([]PropertyName, 0, len(properties.properties))
for tag := range properties.properties {
tags = append(tags, tag)
}
sort.Slice(tags, func(i, j int) bool {
return tags[i] < tags[j]
})
return tags return tags
} }
func (properties *propertyList) writeToBuffer(buffer *strings.Builder, func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
indent string, objectTag string, tags []string) { indent string, objectTag string, tags []PropertyName) {
buffer.WriteString(objectTag) buffer.WriteString(objectTag)
buffer.WriteString(" {\n") buffer.WriteString(" {\n")
@ -83,7 +108,7 @@ func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
for _, tag := range tags { for _, tag := range tags {
if value, ok := properties.properties[tag]; ok { if value, ok := properties.properties[tag]; ok {
buffer.WriteString(indent2) buffer.WriteString(indent2)
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent2) writePropertyValue(buffer, tag, value, indent2)
buffer.WriteString(",\n") buffer.WriteString(",\n")
@ -100,14 +125,41 @@ func parseProperties(properties Properties, object DataObject) {
if node := object.Property(i); node != nil { if node := object.Property(i); node != nil {
switch node.Type() { switch node.Type() {
case TextNode: case TextNode:
properties.Set(node.Tag(), node.Text()) properties.Set(PropertyName(node.Tag()), node.Text())
case ObjectNode: case ObjectNode:
properties.Set(node.Tag(), node.Object()) properties.Set(PropertyName(node.Tag()), node.Object())
case ArrayNode: case ArrayNode:
properties.Set(node.Tag(), node.ArrayElements()) properties.Set(PropertyName(node.Tag()), node.ArrayElements())
} }
} }
} }
} }
func propertiesGet(properties Properties, tag PropertyName) any {
return properties.getRaw(tag)
}
func propertiesRemove(properties Properties, tag PropertyName) []PropertyName {
if properties.getRaw(tag) == nil {
return []PropertyName{}
}
properties.setRaw(tag, nil)
return []PropertyName{tag}
}
func (data *dataProperty) init() {
data.propertyList.init()
data.get = propertiesGet
data.set = propertiesSet
data.remove = propertiesRemove
}
func (data *dataProperty) Get(tag PropertyName) any {
return propertiesGet(data, data.normalize(tag))
}
func (data *dataProperty) Remove(tag PropertyName) {
data.remove(data, data.normalize(tag))
}

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
func stringProperty(properties Properties, tag string, session Session) (string, bool) { func stringProperty(properties Properties, tag PropertyName, session Session) (string, bool) {
if value := properties.getRaw(tag); value != nil { if value := properties.getRaw(tag); value != nil {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
return session.resolveConstants(text) return session.resolveConstants(text)
@ -15,7 +15,7 @@ func stringProperty(properties Properties, tag string, session Session) (string,
return "", false return "", false
} }
func imageProperty(properties Properties, tag string, session Session) (string, bool) { func imageProperty(properties Properties, tag PropertyName, session Session) (string, bool) {
if value := properties.getRaw(tag); value != nil { if value := properties.getRaw(tag); value != nil {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
if text != "" && text[0] == '@' { if text != "" && text[0] == '@' {
@ -61,11 +61,11 @@ func valueToSizeUnit(value any, session Session) (SizeUnit, bool) {
return AutoSize(), false return AutoSize(), false
} }
func sizeProperty(properties Properties, tag string, session Session) (SizeUnit, bool) { func sizeProperty(properties Properties, tag PropertyName, session Session) (SizeUnit, bool) {
return valueToSizeUnit(properties.getRaw(tag), session) return valueToSizeUnit(properties.getRaw(tag), session)
} }
func angleProperty(properties Properties, tag string, session Session) (AngleUnit, bool) { func angleProperty(properties Properties, tag PropertyName, session Session) (AngleUnit, bool) {
if value := properties.getRaw(tag); value != nil { if value := properties.getRaw(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case AngleUnit: case AngleUnit:
@ -98,11 +98,11 @@ func valueToColor(value any, session Session) (Color, bool) {
return Color(0), false return Color(0), false
} }
func colorProperty(properties Properties, tag string, session Session) (Color, bool) { func colorProperty(properties Properties, tag PropertyName, session Session) (Color, bool) {
return valueToColor(properties.getRaw(tag), session) return valueToColor(properties.getRaw(tag), session)
} }
func valueToEnum(value any, tag string, session Session, defaultValue int) (int, bool) { func valueToEnum(value any, tag PropertyName, session Session, defaultValue int) (int, bool) {
if value != nil { if value != nil {
values := enumProperties[tag].values values := enumProperties[tag].values
switch value := value.(type) { switch value := value.(type) {
@ -165,7 +165,7 @@ func enumStringToInt(value string, enumValues []string, logError bool) (int, boo
return 0, false return 0, false
} }
func enumProperty(properties Properties, tag string, session Session, defaultValue int) (int, bool) { func enumProperty(properties Properties, tag PropertyName, session Session, defaultValue int) (int, bool) {
return valueToEnum(properties.getRaw(tag), tag, session, defaultValue) return valueToEnum(properties.getRaw(tag), tag, session, defaultValue)
} }
@ -194,7 +194,7 @@ func valueToBool(value any, session Session) (bool, bool) {
return false, false return false, false
} }
func boolProperty(properties Properties, tag string, session Session) (bool, bool) { func boolProperty(properties Properties, tag PropertyName, session Session) (bool, bool) {
return valueToBool(properties.getRaw(tag), session) return valueToBool(properties.getRaw(tag), session)
} }
@ -224,7 +224,7 @@ func valueToInt(value any, session Session, defaultValue int) (int, bool) {
return defaultValue, false return defaultValue, false
} }
func intProperty(properties Properties, tag string, session Session, defaultValue int) (int, bool) { func intProperty(properties Properties, tag PropertyName, session Session, defaultValue int) (int, bool) {
return valueToInt(properties.getRaw(tag), session, defaultValue) return valueToInt(properties.getRaw(tag), session, defaultValue)
} }
@ -248,7 +248,7 @@ func valueToFloat(value any, session Session, defaultValue float64) (float64, bo
return defaultValue, false return defaultValue, false
} }
func floatProperty(properties Properties, tag string, session Session, defaultValue float64) (float64, bool) { func floatProperty(properties Properties, tag PropertyName, session Session, defaultValue float64) (float64, bool) {
return valueToFloat(properties.getRaw(tag), session, defaultValue) return valueToFloat(properties.getRaw(tag), session, defaultValue)
} }
@ -272,7 +272,7 @@ func valueToFloatText(value any, session Session, defaultValue float64) (string,
return fmt.Sprintf("%g", defaultValue), false return fmt.Sprintf("%g", defaultValue), false
} }
func floatTextProperty(properties Properties, tag string, session Session, defaultValue float64) (string, bool) { func floatTextProperty(properties Properties, tag PropertyName, session Session, defaultValue float64) (string, bool) {
return valueToFloatText(properties.getRaw(tag), session, defaultValue) return valueToFloatText(properties.getRaw(tag), session, defaultValue)
} }
@ -297,6 +297,6 @@ func valueToRange(value any, session Session) (Range, bool) {
return Range{}, false return Range{}, false
} }
func rangeProperty(properties Properties, tag string, session Session) (Range, bool) { func rangeProperty(properties Properties, tag PropertyName, session Session) (Range, bool) {
return valueToRange(properties.getRaw(tag), session) return valueToRange(properties.getRaw(tag), session)
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
var colorProperties = []string{ var colorProperties = []PropertyName{
ColorTag, ColorTag,
BackgroundColor, BackgroundColor,
TextColor, TextColor,
@ -21,7 +21,7 @@ var colorProperties = []string{
ColorPickerValue, ColorPickerValue,
} }
func isPropertyInList(tag string, list []string) bool { func isPropertyInList(tag PropertyName, list []PropertyName) bool {
for _, prop := range list { for _, prop := range list {
if prop == tag { if prop == tag {
return true return true
@ -30,11 +30,11 @@ func isPropertyInList(tag string, list []string) bool {
return false return false
} }
var angleProperties = []string{ var angleProperties = []PropertyName{
From, From,
} }
var boolProperties = []string{ var boolProperties = []PropertyName{
Disabled, Disabled,
Focusable, Focusable,
Inset, Inset,
@ -63,7 +63,7 @@ var boolProperties = []string{
ColumnSpanAll, ColumnSpanAll,
} }
var intProperties = []string{ var intProperties = []PropertyName{
ZIndex, ZIndex,
TabSize, TabSize,
HeadHeight, HeadHeight,
@ -73,9 +73,10 @@ var intProperties = []string{
ColumnCount, ColumnCount,
Order, Order,
TabIndex, TabIndex,
MaxLength,
} }
var floatProperties = map[string]struct{ min, max float64 }{ var floatProperties = map[PropertyName]struct{ min, max float64 }{
Opacity: {min: 0, max: 1}, Opacity: {min: 0, max: 1},
NumberPickerMax: {min: -math.MaxFloat64, max: math.MaxFloat64}, NumberPickerMax: {min: -math.MaxFloat64, max: math.MaxFloat64},
NumberPickerMin: {min: -math.MaxFloat64, max: math.MaxFloat64}, NumberPickerMin: {min: -math.MaxFloat64, max: math.MaxFloat64},
@ -87,79 +88,79 @@ var floatProperties = map[string]struct{ min, max float64 }{
VideoHeight: {min: 0, max: 10000}, VideoHeight: {min: 0, max: 10000},
} }
var sizeProperties = map[string]string{ var sizeProperties = map[PropertyName]string{
Width: Width, Width: string(Width),
Height: Height, Height: string(Height),
MinWidth: MinWidth, MinWidth: string(MinWidth),
MinHeight: MinHeight, MinHeight: string(MinHeight),
MaxWidth: MaxWidth, MaxWidth: string(MaxWidth),
MaxHeight: MaxHeight, MaxHeight: string(MaxHeight),
Left: Left, Left: string(Left),
Right: Right, Right: string(Right),
Top: Top, Top: string(Top),
Bottom: Bottom, Bottom: string(Bottom),
TextSize: "font-size", TextSize: "font-size",
TextIndent: TextIndent, TextIndent: string(TextIndent),
LetterSpacing: LetterSpacing, LetterSpacing: string(LetterSpacing),
WordSpacing: WordSpacing, WordSpacing: string(WordSpacing),
LineHeight: LineHeight, LineHeight: string(LineHeight),
TextLineThickness: "text-decoration-thickness", TextLineThickness: "text-decoration-thickness",
ListRowGap: "row-gap", ListRowGap: "row-gap",
ListColumnGap: "column-gap", ListColumnGap: "column-gap",
GridRowGap: GridRowGap, GridRowGap: string(GridRowGap),
GridColumnGap: GridColumnGap, GridColumnGap: string(GridColumnGap),
ColumnWidth: ColumnWidth, ColumnWidth: string(ColumnWidth),
ColumnGap: ColumnGap, ColumnGap: string(ColumnGap),
Gap: Gap, Gap: string(Gap),
Margin: Margin, Margin: string(Margin),
MarginLeft: MarginLeft, MarginLeft: string(MarginLeft),
MarginRight: MarginRight, MarginRight: string(MarginRight),
MarginTop: MarginTop, MarginTop: string(MarginTop),
MarginBottom: MarginBottom, MarginBottom: string(MarginBottom),
Padding: Padding, Padding: string(Padding),
PaddingLeft: PaddingLeft, PaddingLeft: string(PaddingLeft),
PaddingRight: PaddingRight, PaddingRight: string(PaddingRight),
PaddingTop: PaddingTop, PaddingTop: string(PaddingTop),
PaddingBottom: PaddingBottom, PaddingBottom: string(PaddingBottom),
BorderWidth: BorderWidth, BorderWidth: string(BorderWidth),
BorderLeftWidth: BorderLeftWidth, BorderLeftWidth: string(BorderLeftWidth),
BorderRightWidth: BorderRightWidth, BorderRightWidth: string(BorderRightWidth),
BorderTopWidth: BorderTopWidth, BorderTopWidth: string(BorderTopWidth),
BorderBottomWidth: BorderBottomWidth, BorderBottomWidth: string(BorderBottomWidth),
OutlineWidth: OutlineWidth, OutlineWidth: string(OutlineWidth),
OutlineOffset: OutlineOffset, OutlineOffset: string(OutlineOffset),
XOffset: XOffset, XOffset: string(XOffset),
YOffset: YOffset, YOffset: string(YOffset),
BlurRadius: BlurRadius, BlurRadius: string(BlurRadius),
SpreadRadius: SpreadRadius, SpreadRadius: string(SpreadRadius),
Perspective: Perspective, Perspective: string(Perspective),
PerspectiveOriginX: PerspectiveOriginX, PerspectiveOriginX: string(PerspectiveOriginX),
PerspectiveOriginY: PerspectiveOriginY, PerspectiveOriginY: string(PerspectiveOriginY),
OriginX: OriginX, OriginX: string(OriginX),
OriginY: OriginY, OriginY: string(OriginY),
OriginZ: OriginZ, OriginZ: string(OriginZ),
Radius: Radius, Radius: string(Radius),
RadiusX: RadiusX, RadiusX: string(RadiusX),
RadiusY: RadiusY, RadiusY: string(RadiusY),
RadiusTopLeft: RadiusTopLeft, RadiusTopLeft: string(RadiusTopLeft),
RadiusTopLeftX: RadiusTopLeftX, RadiusTopLeftX: string(RadiusTopLeftX),
RadiusTopLeftY: RadiusTopLeftY, RadiusTopLeftY: string(RadiusTopLeftY),
RadiusTopRight: RadiusTopRight, RadiusTopRight: string(RadiusTopRight),
RadiusTopRightX: RadiusTopRightX, RadiusTopRightX: string(RadiusTopRightX),
RadiusTopRightY: RadiusTopRightY, RadiusTopRightY: string(RadiusTopRightY),
RadiusBottomLeft: RadiusBottomLeft, RadiusBottomLeft: string(RadiusBottomLeft),
RadiusBottomLeftX: RadiusBottomLeftX, RadiusBottomLeftX: string(RadiusBottomLeftX),
RadiusBottomLeftY: RadiusBottomLeftY, RadiusBottomLeftY: string(RadiusBottomLeftY),
RadiusBottomRight: RadiusBottomRight, RadiusBottomRight: string(RadiusBottomRight),
RadiusBottomRightX: RadiusBottomRightX, RadiusBottomRightX: string(RadiusBottomRightX),
RadiusBottomRightY: RadiusBottomRightY, RadiusBottomRightY: string(RadiusBottomRightY),
ItemWidth: ItemWidth, ItemWidth: string(ItemWidth),
ItemHeight: ItemHeight, ItemHeight: string(ItemHeight),
CenterX: CenterX, CenterX: string(CenterX),
CenterY: CenterX, CenterY: string(CenterX),
} }
var enumProperties = map[string]struct { var enumProperties = map[PropertyName]struct {
values []string values []string
cssTag string cssTag string
cssValues []string cssValues []string
@ -176,17 +177,17 @@ var enumProperties = map[string]struct {
}, },
Overflow: { Overflow: {
[]string{"hidden", "visible", "scroll", "auto"}, []string{"hidden", "visible", "scroll", "auto"},
Overflow, string(Overflow),
[]string{"hidden", "visible", "scroll", "auto"}, []string{"hidden", "visible", "scroll", "auto"},
}, },
TextAlign: { TextAlign: {
[]string{"left", "right", "center", "justify"}, []string{"left", "right", "center", "justify"},
TextAlign, string(TextAlign),
[]string{"left", "right", "center", "justify"}, []string{"left", "right", "center", "justify"},
}, },
TextTransform: { TextTransform: {
[]string{"none", "capitalize", "lowercase", "uppercase"}, []string{"none", "capitalize", "lowercase", "uppercase"},
TextTransform, string(TextTransform),
[]string{"none", "capitalize", "lowercase", "uppercase"}, []string{"none", "capitalize", "lowercase", "uppercase"},
}, },
TextWeight: { TextWeight: {
@ -196,27 +197,27 @@ var enumProperties = map[string]struct {
}, },
WhiteSpace: { WhiteSpace: {
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"}, []string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
WhiteSpace, string(WhiteSpace),
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"}, []string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
}, },
WordBreak: { WordBreak: {
[]string{"normal", "break-all", "keep-all", "break-word"}, []string{"normal", "break-all", "keep-all", "break-word"},
WordBreak, string(WordBreak),
[]string{"normal", "break-all", "keep-all", "break-word"}, []string{"normal", "break-all", "keep-all", "break-word"},
}, },
TextOverflow: { TextOverflow: {
[]string{"clip", "ellipsis"}, []string{"clip", "ellipsis"},
TextOverflow, string(TextOverflow),
[]string{"clip", "ellipsis"}, []string{"clip", "ellipsis"},
}, },
TextWrap: { TextWrap: {
[]string{"wrap", "nowrap", "balance"}, []string{"wrap", "nowrap", "balance"},
TextWrap, string(TextWrap),
[]string{"wrap", "nowrap", "balance"}, []string{"wrap", "nowrap", "balance"},
}, },
WritingMode: { WritingMode: {
[]string{"horizontal-top-to-bottom", "horizontal-bottom-to-top", "vertical-right-to-left", "vertical-left-to-right"}, []string{"horizontal-top-to-bottom", "horizontal-bottom-to-top", "vertical-right-to-left", "vertical-left-to-right"},
WritingMode, string(WritingMode),
[]string{"horizontal-tb", "horizontal-bt", "vertical-rl", "vertical-lr"}, []string{"horizontal-tb", "horizontal-bt", "vertical-rl", "vertical-lr"},
}, },
TextDirection: { TextDirection: {
@ -236,7 +237,7 @@ var enumProperties = map[string]struct {
}, },
BorderStyle: { BorderStyle: {
[]string{"none", "solid", "dashed", "dotted", "double"}, []string{"none", "solid", "dashed", "dotted", "double"},
BorderStyle, string(BorderStyle),
[]string{"none", "solid", "dashed", "dotted", "double"}, []string{"none", "solid", "dashed", "dotted", "double"},
}, },
TopStyle: { TopStyle: {
@ -261,7 +262,7 @@ var enumProperties = map[string]struct {
}, },
OutlineStyle: { OutlineStyle: {
[]string{"none", "solid", "dashed", "dotted", "double"}, []string{"none", "solid", "dashed", "dotted", "double"},
OutlineStyle, string(OutlineStyle),
[]string{"none", "solid", "dashed", "dotted", "double"}, []string{"none", "solid", "dashed", "dotted", "double"},
}, },
Tabs: { Tabs: {
@ -336,7 +337,7 @@ var enumProperties = map[string]struct {
}, },
GridAutoFlow: { GridAutoFlow: {
[]string{"row", "column", "row-dense", "column-dense"}, []string{"row", "column", "row-dense", "column-dense"},
GridAutoFlow, string(GridAutoFlow),
[]string{"row", "column", "row dense", "column dense"}, []string{"row", "column", "row dense", "column dense"},
}, },
ImageVerticalAlign: { ImageVerticalAlign: {
@ -376,7 +377,7 @@ var enumProperties = map[string]struct {
}, },
Cursor: { Cursor: {
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"}, []string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
Cursor, string(Cursor),
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"}, []string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
}, },
Fit: { Fit: {
@ -456,27 +457,27 @@ var enumProperties = map[string]struct {
}, },
MixBlendMode: { MixBlendMode: {
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}, []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
MixBlendMode, string(MixBlendMode),
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}, []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
}, },
BackgroundBlendMode: { BackgroundBlendMode: {
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}, []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
BackgroundBlendMode, string(BackgroundBlendMode),
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}, []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
}, },
ColumnFill: { ColumnFill: {
[]string{"balance", "auto"}, []string{"balance", "auto"},
ColumnFill, string(ColumnFill),
[]string{"balance", "auto"}, []string{"balance", "auto"},
}, },
} }
func notCompatibleType(tag string, value any) { func notCompatibleType(tag PropertyName, value any) {
ErrorLogF(`"%T" type not compatible with "%s" property`, value, tag) ErrorLogF(`"%T" type not compatible with "%s" property`, value, string(tag))
} }
func invalidPropertyValue(tag string, value any) { func invalidPropertyValue(tag PropertyName, value any) {
ErrorLogF(`Invalid value "%v" of "%s" property`, value, tag) ErrorLogF(`Invalid value "%v" of "%s" property`, value, string(tag))
} }
func isConstantName(text string) bool { func isConstantName(text string) bool {
@ -537,26 +538,48 @@ func isInt(value any) (int, bool) {
return n, true return n, true
} }
func (properties *propertyList) setSimpleProperty(tag string, value any) bool { func setSimpleProperty(properties Properties, tag PropertyName, value any) bool {
if value == nil { if value == nil {
delete(properties.properties, tag) properties.setRaw(tag, nil)
return true return true
} else if text, ok := value.(string); ok { } else if text, ok := value.(string); ok {
text = strings.Trim(text, " \t\n\r") text = strings.Trim(text, " \t\n\r")
if text == "" { if text == "" {
delete(properties.properties, tag) properties.setRaw(tag, nil)
return true return true
} }
if isConstantName(text) { if isConstantName(text) {
properties.properties[tag] = text properties.setRaw(tag, text)
return true return true
} }
} }
return false return false
} }
func (properties *propertyList) setSizeProperty(tag string, value any) bool { func setStringPropertyValue(properties Properties, tag PropertyName, text any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if text != "" {
properties.setRaw(tag, text)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
func setArrayPropertyValue[T any](properties Properties, tag PropertyName, value []T) []PropertyName {
if len(value) > 0 {
properties.setRaw(tag, value)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
func setSizeProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !setSimpleProperty(properties, tag, value) {
var size SizeUnit var size SizeUnit
switch value := value.(type) { switch value := value.(type) {
case string: case string:
@ -566,7 +589,7 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
size.Function = fn size.Function = fn
} else if size, ok = StringToSizeUnit(value); !ok { } else if size, ok = StringToSizeUnit(value); !ok {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
case SizeUnit: case SizeUnit:
size = value size = value
@ -589,29 +612,29 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
size.Value = float64(n) size.Value = float64(n)
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
if size.Type == Auto { if size.Type == Auto {
delete(properties.properties, tag) properties.setRaw(tag, nil)
} else { } else {
properties.properties[tag] = size properties.setRaw(tag, size)
} }
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setAngleProperty(tag string, value any) bool { func setAngleProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
var angle AngleUnit var angle AngleUnit
switch value := value.(type) { switch value := value.(type) {
case string: case string:
var ok bool var ok bool
if angle, ok = StringToAngleUnit(value); !ok { if angle, ok = StringToAngleUnit(value); !ok {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
case AngleUnit: case AngleUnit:
angle = value angle = value
@ -627,24 +650,24 @@ func (properties *propertyList) setAngleProperty(tag string, value any) bool {
angle = Rad(float64(n)) angle = Rad(float64(n))
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
properties.properties[tag] = angle properties.setRaw(tag, angle)
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setColorProperty(tag string, value any) bool { func setColorProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
var result Color var result Color
switch value := value.(type) { switch value := value.(type) {
case string: case string:
var err error var err error
if result, err = stringToColor(value); err != nil { if result, err = stringToColor(value); err != nil {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
case Color: case Color:
result = value result = value
@ -654,105 +677,101 @@ func (properties *propertyList) setColorProperty(tag string, value any) bool {
result = Color(color) result = Color(color)
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
if result == 0 { properties.setRaw(tag, result)
delete(properties.properties, tag)
} else {
properties.properties[tag] = result
}
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setEnumProperty(tag string, value any, values []string) bool { func setEnumProperty(properties Properties, tag PropertyName, value any, values []string) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
var n int var n int
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
if n, ok = enumStringToInt(text, values, false); !ok { if n, ok = enumStringToInt(text, values, false); !ok {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
} else if i, ok := isInt(value); ok { } else if i, ok := isInt(value); ok {
if i < 0 || i >= len(values) { if i < 0 || i >= len(values) {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
n = i n = i
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
properties.properties[tag] = n properties.setRaw(tag, n)
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setBoolProperty(tag string, value any) bool { func setBoolProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
switch strings.ToLower(strings.Trim(text, " \t")) { switch strings.ToLower(strings.Trim(text, " \t")) {
case "true", "yes", "on", "1": case "true", "yes", "on", "1":
properties.properties[tag] = true properties.setRaw(tag, true)
case "false", "no", "off", "0": case "false", "no", "off", "0":
properties.properties[tag] = false properties.setRaw(tag, false)
default: default:
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
} else if n, ok := isInt(value); ok { } else if n, ok := isInt(value); ok {
switch n { switch n {
case 1: case 1:
properties.properties[tag] = true properties.setRaw(tag, true)
case 0: case 0:
properties.properties[tag] = false properties.setRaw(tag, false)
default: default:
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
} else if b, ok := value.(bool); ok { } else if b, ok := value.(bool); ok {
properties.properties[tag] = b properties.setRaw(tag, b)
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setIntProperty(tag string, value any) bool { func setIntProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
n, err := strconv.Atoi(strings.Trim(text, " \t")) n, err := strconv.Atoi(strings.Trim(text, " \t"))
if err != nil { if err != nil {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
ErrorLog(err.Error()) ErrorLog(err.Error())
return false return nil
} }
properties.properties[tag] = n properties.setRaw(tag, n)
} else if n, ok := isInt(value); ok { } else if n, ok := isInt(value); ok {
properties.properties[tag] = n properties.setRaw(tag, n)
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) setFloatProperty(tag string, value any, min, max float64) bool { func setFloatProperty(properties Properties, tag PropertyName, value any, min, max float64) []PropertyName {
if !properties.setSimpleProperty(tag, value) { if !setSimpleProperty(properties, tag, value) {
f := float64(0) f := float64(0)
switch value := value.(type) { switch value := value.(type) {
case string: case string:
@ -760,14 +779,14 @@ func (properties *propertyList) setFloatProperty(tag string, value any, min, max
if f, err = strconv.ParseFloat(strings.Trim(value, " \t"), 64); err != nil { if f, err = strconv.ParseFloat(strings.Trim(value, " \t"), 64); err != nil {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
ErrorLog(err.Error()) ErrorLog(err.Error())
return false return nil
} }
if f < min || f > max { if f < min || f > max {
ErrorLogF(`"%T" out of range of "%s" property`, value, tag) ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
return false return nil
} }
properties.properties[tag] = value properties.setRaw(tag, value)
return true return nil
case float32: case float32:
f = float64(value) f = float64(value)
@ -780,64 +799,84 @@ func (properties *propertyList) setFloatProperty(tag string, value any, min, max
f = float64(n) f = float64(n)
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
if f >= min && f <= max { if f >= min && f <= max {
properties.properties[tag] = f properties.setRaw(tag, f)
} else { } else {
ErrorLogF(`"%T" out of range of "%s" property`, value, tag) ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
return false return nil
} }
} }
return true return []PropertyName{tag}
} }
func (properties *propertyList) Set(tag string, value any) bool { func propertiesSet(properties Properties, tag PropertyName, value any) []PropertyName {
return properties.set(strings.ToLower(tag), value)
}
func (properties *propertyList) set(tag string, value any) bool {
if value == nil {
delete(properties.properties, tag)
return true
}
if _, ok := sizeProperties[tag]; ok { if _, ok := sizeProperties[tag]; ok {
return properties.setSizeProperty(tag, value) return setSizeProperty(properties, tag, value)
} }
if valuesData, ok := enumProperties[tag]; ok { if valuesData, ok := enumProperties[tag]; ok {
return properties.setEnumProperty(tag, value, valuesData.values) return setEnumProperty(properties, tag, value, valuesData.values)
} }
if limits, ok := floatProperties[tag]; ok { if limits, ok := floatProperties[tag]; ok {
return properties.setFloatProperty(tag, value, limits.min, limits.max) return setFloatProperty(properties, tag, value, limits.min, limits.max)
} }
if isPropertyInList(tag, colorProperties) { if isPropertyInList(tag, colorProperties) {
return properties.setColorProperty(tag, value) return setColorProperty(properties, tag, value)
} }
if isPropertyInList(tag, angleProperties) { if isPropertyInList(tag, angleProperties) {
return properties.setAngleProperty(tag, value) return setAngleProperty(properties, tag, value)
} }
if isPropertyInList(tag, boolProperties) { if isPropertyInList(tag, boolProperties) {
return properties.setBoolProperty(tag, value) return setBoolProperty(properties, tag, value)
} }
if isPropertyInList(tag, intProperties) { if isPropertyInList(tag, intProperties) {
return properties.setIntProperty(tag, value) return setIntProperty(properties, tag, value)
} }
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
properties.properties[tag] = text properties.setRaw(tag, text)
return true return []PropertyName{tag}
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return nil
}
/*
func (properties *propertyList) Set(tag PropertyName, value any) bool {
tag = properties.normalize(tag)
if value == nil {
properties.remove(properties, tag)
return true
}
return properties.set(properties, tag, value) != nil
}
*/
func (data *dataProperty) Set(tag PropertyName, value any) bool {
if value == nil {
data.Remove(tag)
return true
}
tag = data.normalize(tag)
for _, supported := range data.supportedProperties {
if tag == supported {
return data.set(data, tag, value) != nil
}
}
ErrorLogF(`"%s" property is not supported`, string(tag))
return false return false
} }

383
radius.go
View File

@ -37,7 +37,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
Radius = "radius" Radius PropertyName = "radius"
// RadiusX is the constant for "radius-x" property tag. // RadiusX is the constant for "radius-x" property tag.
// //
@ -58,7 +58,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusX = "radius-x" RadiusX PropertyName = "radius-x"
// RadiusY is the constant for "radius-y" property tag. // RadiusY is the constant for "radius-y" property tag.
// //
@ -79,7 +79,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusY = "radius-y" RadiusY PropertyName = "radius-y"
// RadiusTopLeft is the constant for "radius-top-left" property tag. // RadiusTopLeft is the constant for "radius-top-left" property tag.
// //
@ -90,7 +90,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopLeft = "radius-top-left" RadiusTopLeft PropertyName = "radius-top-left"
// RadiusTopLeftX is the constant for "radius-top-left-x" property tag. // RadiusTopLeftX is the constant for "radius-top-left-x" property tag.
// //
@ -101,7 +101,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopLeftX = "radius-top-left-x" RadiusTopLeftX PropertyName = "radius-top-left-x"
// RadiusTopLeftY is the constant for "radius-top-left-y" property tag. // RadiusTopLeftY is the constant for "radius-top-left-y" property tag.
// //
@ -112,7 +112,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopLeftY = "radius-top-left-y" RadiusTopLeftY PropertyName = "radius-top-left-y"
// RadiusTopRight is the constant for "radius-top-right" property tag. // RadiusTopRight is the constant for "radius-top-right" property tag.
// //
@ -123,7 +123,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopRight = "radius-top-right" RadiusTopRight PropertyName = "radius-top-right"
// RadiusTopRightX is the constant for "radius-top-right-x" property tag. // RadiusTopRightX is the constant for "radius-top-right-x" property tag.
// //
@ -134,7 +134,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopRightX = "radius-top-right-x" RadiusTopRightX PropertyName = "radius-top-right-x"
// RadiusTopRightY is the constant for "radius-top-right-y" property tag. // RadiusTopRightY is the constant for "radius-top-right-y" property tag.
// //
@ -145,7 +145,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusTopRightY = "radius-top-right-y" RadiusTopRightY PropertyName = "radius-top-right-y"
// RadiusBottomLeft is the constant for "radius-bottom-left" property tag. // RadiusBottomLeft is the constant for "radius-bottom-left" property tag.
// //
@ -156,7 +156,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomLeft = "radius-bottom-left" RadiusBottomLeft PropertyName = "radius-bottom-left"
// RadiusBottomLeftX is the constant for "radius-bottom-left-x" property tag. // RadiusBottomLeftX is the constant for "radius-bottom-left-x" property tag.
// //
@ -167,7 +167,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomLeftX = "radius-bottom-left-x" RadiusBottomLeftX PropertyName = "radius-bottom-left-x"
// RadiusBottomLeftY is the constant for "radius-bottom-left-y" property tag. // RadiusBottomLeftY is the constant for "radius-bottom-left-y" property tag.
// //
@ -178,7 +178,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomLeftY = "radius-bottom-left-y" RadiusBottomLeftY PropertyName = "radius-bottom-left-y"
// RadiusBottomRight is the constant for "radius-bottom-right" property tag. // RadiusBottomRight is the constant for "radius-bottom-right" property tag.
// //
@ -189,7 +189,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomRight = "radius-bottom-right" RadiusBottomRight PropertyName = "radius-bottom-right"
// RadiusBottomRightX is the constant for "radius-bottom-right-x" property tag. // RadiusBottomRightX is the constant for "radius-bottom-right-x" property tag.
// //
@ -200,7 +200,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomRightX = "radius-bottom-right-x" RadiusBottomRightX PropertyName = "radius-bottom-right-x"
// RadiusBottomRightY is the constant for "radius-bottom-right-y" property tag. // RadiusBottomRightY is the constant for "radius-bottom-right-y" property tag.
// //
@ -211,7 +211,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
RadiusBottomRightY = "radius-bottom-right-y" RadiusBottomRightY PropertyName = "radius-bottom-right-y"
// X is the constant for "x" property tag. // X is the constant for "x" property tag.
// //
@ -232,7 +232,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
X = "x" X PropertyName = "x"
// Y is the constant for "y" property tag. // Y is the constant for "y" property tag.
// //
@ -253,7 +253,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
Y = "y" Y PropertyName = "y"
// TopLeft is the constant for "top-left" property tag. // TopLeft is the constant for "top-left" property tag.
// //
@ -264,7 +264,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopLeft = "top-left" TopLeft PropertyName = "top-left"
// TopLeftX is the constant for "top-left-x" property tag. // TopLeftX is the constant for "top-left-x" property tag.
// //
@ -275,7 +275,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopLeftX = "top-left-x" TopLeftX PropertyName = "top-left-x"
// TopLeftY is the constant for "top-left-y" property tag. // TopLeftY is the constant for "top-left-y" property tag.
// //
@ -286,7 +286,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopLeftY = "top-left-y" TopLeftY PropertyName = "top-left-y"
// TopRight is the constant for "top-right" property tag. // TopRight is the constant for "top-right" property tag.
// //
@ -297,7 +297,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopRight = "top-right" TopRight PropertyName = "top-right"
// TopRightX is the constant for "top-right-x" property tag. // TopRightX is the constant for "top-right-x" property tag.
// //
@ -308,7 +308,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopRightX = "top-right-x" TopRightX PropertyName = "top-right-x"
// TopRightY is the constant for "top-right-y" property tag. // TopRightY is the constant for "top-right-y" property tag.
// //
@ -319,7 +319,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TopRightY = "top-right-y" TopRightY PropertyName = "top-right-y"
// BottomLeft is the constant for "bottom-left" property tag. // BottomLeft is the constant for "bottom-left" property tag.
// //
@ -330,7 +330,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomLeft = "bottom-left" BottomLeft PropertyName = "bottom-left"
// BottomLeftX is the constant for "bottom-left-x" property tag. // BottomLeftX is the constant for "bottom-left-x" property tag.
// //
@ -341,7 +341,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomLeftX = "bottom-left-x" BottomLeftX PropertyName = "bottom-left-x"
// BottomLeftY is the constant for "bottom-left-y" property tag. // BottomLeftY is the constant for "bottom-left-y" property tag.
// //
@ -352,7 +352,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomLeftY = "bottom-left-y" BottomLeftY PropertyName = "bottom-left-y"
// BottomRight is the constant for "bottom-right" property tag. // BottomRight is the constant for "bottom-right" property tag.
// //
@ -363,7 +363,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomRight = "bottom-right" BottomRight PropertyName = "bottom-right"
// BottomRightX is the constant for "bottom-right-x" property tag. // BottomRightX is the constant for "bottom-right-x" property tag.
// //
@ -374,7 +374,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomRightX = "bottom-right-x" BottomRightX PropertyName = "bottom-right-x"
// BottomRightY is the constant for "bottom-right-y" property tag. // BottomRightY is the constant for "bottom-right-y" property tag.
// //
@ -385,7 +385,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BottomRightY = "bottom-right-y" BottomRightY PropertyName = "bottom-right-y"
) )
// RadiusProperty is a description of the [View] (shape) elliptical corner radius. // RadiusProperty is a description of the [View] (shape) elliptical corner radius.
@ -399,38 +399,46 @@ type RadiusProperty interface {
} }
type radiusPropertyData struct { type radiusPropertyData struct {
propertyList dataProperty
} }
// NewRadiusProperty creates the new RadiusProperty // NewRadiusProperty creates the new RadiusProperty
func NewRadiusProperty(params Params) RadiusProperty { func NewRadiusProperty(params Params) RadiusProperty {
result := new(radiusPropertyData) result := new(radiusPropertyData)
result.properties = map[string]any{} result.dataProperty.init()
result.normalize = radiusPropertyNormalize
result.get = radiusPropertyGet
result.remove = radiusPropertyRemove
result.set = radiusPropertySet
result.supportedProperties = []PropertyName{
X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY,
TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY,
}
if params != nil { if params != nil {
for _, tag := range []string{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY, for _, tag := range result.supportedProperties {
TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} {
if value, ok := params[tag]; ok { if value, ok := params[tag]; ok {
result.Set(tag, value) radiusPropertySet(result, tag, value)
} }
} }
} }
return result return result
} }
func (radius *radiusPropertyData) normalizeTag(tag string) string { func radiusPropertyNormalize(tag PropertyName) PropertyName {
return strings.TrimPrefix(strings.ToLower(tag), "radius-") name := strings.TrimPrefix(strings.ToLower(string(tag)), "radius-")
return PropertyName(name)
} }
func (radius *radiusPropertyData) writeString(buffer *strings.Builder, indent string) { func (radius *radiusPropertyData) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ") buffer.WriteString("_{ ")
comma := false comma := false
for _, tag := range []string{X, Y, TopLeft, TopLeftX, TopLeftY, TopRight, TopRightX, TopRightY, for _, tag := range radius.supportedProperties {
BottomLeft, BottomLeftX, BottomLeftY, BottomRight, BottomRightX, BottomRightY} {
if value, ok := radius.properties[tag]; ok { if value, ok := radius.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -444,26 +452,54 @@ func (radius *radiusPropertyData) String() string {
return runStringWriter(radius) return runStringWriter(radius)
} }
func (radius *radiusPropertyData) delete(tags []string) { func radiusPropertyRemove(properties Properties, tag PropertyName) []PropertyName {
for _, tag := range tags { result := []PropertyName{}
delete(radius.properties, tag) removeTag := func(tag PropertyName) {
if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
result = append(result, tag)
}
} }
switch tag {
case X, Y:
if properties.getRaw(tag) == nil {
for _, prefix := range []PropertyName{TopLeft, TopRight, BottomLeft, BottomRight} {
removeTag(prefix + "-" + tag)
}
} else {
removeTag(tag)
}
case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY:
removeTag(tag)
case TopLeft, TopRight, BottomLeft, BottomRight:
for _, tag := range []PropertyName{tag, tag + "-x", tag + "-y"} {
removeTag(tag)
}
default:
ErrorLogF(`"%s" property is not compatible with the RadiusProperty`, tag)
}
return result
} }
func (radius *radiusPropertyData) deleteUnusedTags() { func deleteRadiusUnusedTags(radius Properties, result []PropertyName) {
for _, tag := range []string{X, Y} {
if _, ok := radius.properties[tag]; ok { for _, tag := range []PropertyName{X, Y} {
if radius.getRaw(tag) != nil {
unused := true unused := true
for _, t := range []string{TopLeft, TopRight, BottomLeft, BottomRight} { for _, t := range []PropertyName{TopLeft, TopRight, BottomLeft, BottomRight} {
if _, ok := radius.properties[t+"-"+tag]; !ok { if radius.getRaw(t+"-"+tag) == nil && radius.getRaw(t) == nil {
if _, ok := radius.properties[t]; !ok { unused = false
unused = false break
break
}
} }
} }
if unused { if unused {
delete(radius.properties, tag) radius.setRaw(tag, nil)
result = append(result, tag)
} }
} }
} }
@ -485,124 +521,122 @@ func (radius *radiusPropertyData) deleteUnusedTags() {
return false return false
} }
for _, tag := range []string{TopLeft, TopRight, BottomLeft, BottomRight} { for _, tag := range []PropertyName{TopLeft, TopRight, BottomLeft, BottomRight} {
tagX := tag + "-x" tagX := tag + "-x"
tagY := tag + "-y" tagY := tag + "-y"
valueX, okX := radius.properties[tagX] valueX := radius.getRaw(tagX)
valueY, okY := radius.properties[tagY] valueY := radius.getRaw(tagY)
if value, ok := radius.properties[tag]; ok { if value := radius.getRaw(tag); value != nil {
if okX && okY { if valueX != nil && valueY != nil {
delete(radius.properties, tag) radius.setRaw(tag, nil)
} else if okX && !okY { result = append(result, tag)
} else if valueX != nil && valueY == nil {
if equalValue(value, valueX) { if equalValue(value, valueX) {
delete(radius.properties, tagX) radius.setRaw(tagX, nil)
result = append(result, tagX)
} else { } else {
radius.properties[tagY] = value radius.setRaw(tagY, value)
delete(radius.properties, tag) result = append(result, tagY)
radius.setRaw(tag, nil)
result = append(result, tag)
} }
} else if !okX && okY { } else if valueX == nil && valueY != nil {
if equalValue(value, valueY) { if equalValue(value, valueY) {
delete(radius.properties, tagY) radius.setRaw(tagY, nil)
result = append(result, tagY)
} else { } else {
radius.properties[tagX] = value radius.setRaw(tagX, value)
delete(radius.properties, tag) result = append(result, tagX)
radius.setRaw(tag, nil)
result = append(result, tag)
} }
} }
} else if okX && okY && equalValue(valueX, valueY) { } else if valueX != nil && valueY != nil && equalValue(valueX, valueY) {
radius.properties[tag] = valueX radius.setRaw(tag, valueX)
delete(radius.properties, tagX) result = append(result, tag)
delete(radius.properties, tagY) radius.setRaw(tagX, nil)
result = append(result, tagX)
radius.setRaw(tagY, nil)
result = append(result, tagY)
} }
} }
} }
func (radius *radiusPropertyData) Remove(tag string) { func radiusPropertySet(radius Properties, tag PropertyName, value any) []PropertyName {
tag = radius.normalizeTag(tag) var result []PropertyName = nil
switch tag { deleteTags := func(tags []PropertyName) {
case X, Y: for _, tag := range tags {
if _, ok := radius.properties[tag]; ok { if radius.getRaw(tag) != nil {
radius.Set(tag, AutoSize()) radius.setRaw(tag, nil)
delete(radius.properties, tag) result = append(result, tag)
}
} }
case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY:
delete(radius.properties, tag)
case TopLeft, TopRight, BottomLeft, BottomRight:
radius.delete([]string{tag, tag + "-x", tag + "-y"})
default:
ErrorLogF(`"%s" property is not compatible with the RadiusProperty`, tag)
} }
}
func (radius *radiusPropertyData) Set(tag string, value any) bool {
if value == nil {
radius.Remove(tag)
return true
}
tag = radius.normalizeTag(tag)
switch tag { switch tag {
case X: case X:
if radius.setSizeProperty(tag, value) { if result = setSizeProperty(radius, tag, value); result != nil {
radius.delete([]string{TopLeftX, TopRightX, BottomLeftX, BottomRightX}) deleteTags([]PropertyName{TopLeftX, TopRightX, BottomLeftX, BottomRightX})
for _, t := range []string{TopLeft, TopRight, BottomLeft, BottomRight} { for _, t := range []PropertyName{TopLeft, TopRight, BottomLeft, BottomRight} {
if val, ok := radius.properties[t]; ok { if val := radius.getRaw(t); val != nil {
if _, ok := radius.properties[t+"-y"]; !ok { t2 := t + "-y"
radius.properties[t+"-y"] = val if radius.getRaw(t2) != nil {
radius.setRaw(t2, val)
result = append(result, t2)
} }
delete(radius.properties, t) radius.setRaw(t, nil)
result = append(result, t)
} }
} }
return true
} }
case Y: case Y:
if radius.setSizeProperty(tag, value) { if result = setSizeProperty(radius, tag, value); result != nil {
radius.delete([]string{TopLeftY, TopRightY, BottomLeftY, BottomRightY}) deleteTags([]PropertyName{TopLeftY, TopRightY, BottomLeftY, BottomRightY})
for _, t := range []string{TopLeft, TopRight, BottomLeft, BottomRight} { for _, t := range []PropertyName{TopLeft, TopRight, BottomLeft, BottomRight} {
if val, ok := radius.properties[t]; ok { if val := radius.getRaw(t); val != nil {
if _, ok := radius.properties[t+"-x"]; !ok { t2 := t + "-x"
radius.properties[t+"-x"] = val if radius.getRaw(t2) != nil {
radius.setRaw(t2, val)
result = append(result, t2)
} }
delete(radius.properties, t) radius.setRaw(t, nil)
result = append(result, t)
} }
} }
return true
} }
case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY: case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY:
if radius.setSizeProperty(tag, value) { if result = setSizeProperty(radius, tag, value); result != nil {
radius.deleteUnusedTags() deleteRadiusUnusedTags(radius, result)
return true
} }
case TopLeft, TopRight, BottomLeft, BottomRight: case TopLeft, TopRight, BottomLeft, BottomRight:
switch value := value.(type) { switch value := value.(type) {
case SizeUnit: case SizeUnit:
radius.properties[tag] = value radius.setRaw(tag, value)
radius.delete([]string{tag + "-x", tag + "-y"}) result = []PropertyName{tag}
radius.deleteUnusedTags() deleteTags([]PropertyName{tag + "-x", tag + "-y"})
return true deleteRadiusUnusedTags(radius, result)
case string: case string:
if strings.Contains(value, "/") { if strings.Contains(value, "/") {
if values := strings.Split(value, "/"); len(values) == 2 { if values := strings.Split(value, "/"); len(values) == 2 {
xOK := radius.Set(tag+"-x", value[0]) if result = radiusPropertySet(radius, tag+"-x", value[0]); result != nil {
yOK := radius.Set(tag+"-y", value[1]) if resultY := radiusPropertySet(radius, tag+"-y", value[1]); resultY != nil {
return xOK && yOK result = append(result, resultY...)
}
}
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
} }
} else { } else {
if radius.setSizeProperty(tag, value) { if result = setSizeProperty(radius, tag, value); result != nil {
radius.delete([]string{tag + "-x", tag + "-y"}) deleteTags([]PropertyName{tag + "-x", tag + "-y"})
radius.deleteUnusedTags() deleteRadiusUnusedTags(radius, result)
return true
} }
} }
} }
@ -611,33 +645,32 @@ func (radius *radiusPropertyData) Set(tag string, value any) bool {
ErrorLogF(`"%s" property is not compatible with the RadiusProperty`, tag) ErrorLogF(`"%s" property is not compatible with the RadiusProperty`, tag)
} }
return false return result
} }
func (radius *radiusPropertyData) Get(tag string) any { func radiusPropertyGet(properties Properties, tag PropertyName) any {
tag = radius.normalizeTag(tag) if value := properties.getRaw(tag); value != nil {
if value, ok := radius.properties[tag]; ok {
return value return value
} }
switch tag { switch tag {
case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY: case TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY:
tagLen := len(tag) tagLen := len(tag)
if value, ok := radius.properties[tag[:tagLen-2]]; ok { if value := properties.getRaw(tag[:tagLen-2]); value != nil {
return value return value
} }
if value, ok := radius.properties[tag[tagLen-1:]]; ok { if value := properties.getRaw(tag[tagLen-1:]); value != nil {
return value return value
} }
} }
switch tag { switch tag {
case TopLeftX, TopRightX, BottomLeftX, BottomRightX: case TopLeftX, TopRightX, BottomLeftX, BottomRightX:
if value, ok := radius.properties[X]; ok { if value := properties.getRaw(X); value != nil {
return value return value
} }
case TopLeftY, TopRightY, BottomLeftY, BottomRightY: case TopLeftY, TopRightY, BottomLeftY, BottomRightY:
if value, ok := radius.properties[Y]; ok { if value := properties.getRaw(Y); value != nil {
return value return value
} }
} }
@ -649,7 +682,7 @@ func (radius *radiusPropertyData) BoxRadius(session Session) BoxRadius {
x, _ := sizeProperty(radius, X, session) x, _ := sizeProperty(radius, X, session)
y, _ := sizeProperty(radius, Y, session) y, _ := sizeProperty(radius, Y, session)
getRadius := func(tag string) (SizeUnit, SizeUnit) { getRadius := func(tag PropertyName) (SizeUnit, SizeUnit) {
rx := x rx := x
ry := y ry := y
if r, ok := sizeProperty(radius, tag, session); ok { if r, ok := sizeProperty(radius, tag, session); ok {
@ -866,21 +899,18 @@ func getRadiusProperty(style Properties) RadiusProperty {
return NewRadiusProperty(nil) return NewRadiusProperty(nil)
} }
func (properties *propertyList) setRadius(value any) bool { func setRadiusProperty(properties Properties, value any) []PropertyName {
if value == nil { if value == nil {
delete(properties.properties, Radius) return propertiesRemove(properties, Radius)
return true
} }
switch value := value.(type) { switch value := value.(type) {
case RadiusProperty: case RadiusProperty:
properties.properties[Radius] = value properties.setRaw(Radius, value)
return true
case SizeUnit: case SizeUnit:
properties.properties[Radius] = value properties.setRaw(Radius, value)
return true
case BoxRadius: case BoxRadius:
radius := NewRadiusProperty(nil) radius := NewRadiusProperty(nil)
@ -913,78 +943,85 @@ func (properties *propertyList) setRadius(value any) bool {
radius.Set(BottomRightY, value.BottomRightY) radius.Set(BottomRightY, value.BottomRightY)
} }
} }
properties.properties[Radius] = radius properties.setRaw(Radius, radius)
return true
case string: case string:
if strings.Contains(value, "/") { if strings.Contains(value, "/") {
values := strings.Split(value, "/") values := strings.Split(value, "/")
if len(values) == 2 { if len(values) == 2 {
okX := properties.setRadiusElement(RadiusX, values[0]) if setRadiusPropertyElement(properties, RadiusX, values[0]) {
okY := properties.setRadiusElement(RadiusY, values[1]) result := []PropertyName{Radius, RadiusX}
return okX && okY if setRadiusPropertyElement(properties, RadiusY, values[1]) {
} else { result = append(result, RadiusY)
notCompatibleType(Radius, value) }
return result
}
} }
notCompatibleType(Radius, value)
return nil
} else { } else {
return properties.setSizeProperty(Radius, value) return setSizeProperty(properties, Radius, value)
} }
case DataObject: case DataObject:
radius := NewRadiusProperty(nil) radius := NewRadiusProperty(nil)
for _, tag := range []string{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY, for _, tag := range []PropertyName{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY,
TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} { TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} {
if value, ok := value.PropertyValue(tag); ok { if value, ok := value.PropertyValue(string(tag)); ok {
radius.Set(tag, value) radius.Set(tag, value)
} }
} }
properties.properties[Radius] = radius properties.setRaw(Radius, radius)
return true
case float32: case float32:
return properties.setRadius(Px(float64(value))) properties.setRaw(Radius, Px(float64(value)))
case float64: case float64:
return properties.setRadius(Px(value)) properties.setRaw(Radius, Px(value))
default: default:
if n, ok := isInt(value); ok { if n, ok := isInt(value); ok {
return properties.setRadius(Px(float64(n))) properties.setRaw(Radius, Px(float64(n)))
} else {
notCompatibleType(Radius, value)
return nil
} }
notCompatibleType(Radius, value)
} }
return []PropertyName{Radius}
}
func removeRadiusPropertyElement(properties Properties, tag PropertyName) bool {
if value := properties.getRaw(Radius); value != nil {
radius := getRadiusProperty(properties)
radius.Remove(tag)
if radius.empty() {
properties.setRaw(Radius, nil)
} else {
properties.setRaw(Radius, radius)
}
return true
}
return false return false
} }
func (properties *propertyList) removeRadiusElement(tag string) { func setRadiusPropertyElement(properties Properties, tag PropertyName, value any) bool {
if value, ok := properties.properties[Radius]; ok && value != nil {
radius := getRadiusProperty(properties)
radius.Remove(tag)
if len(radius.AllTags()) == 0 {
delete(properties.properties, Radius)
} else {
properties.properties[Radius] = radius
}
}
}
func (properties *propertyList) setRadiusElement(tag string, value any) bool {
if value == nil { if value == nil {
properties.removeRadiusElement(tag) removeRadiusPropertyElement(properties, tag)
return true return true
} }
radius := getRadiusProperty(properties) radius := getRadiusProperty(properties)
if radius.Set(tag, value) { if radius.Set(tag, value) {
properties.properties[Radius] = radius properties.setRaw(Radius, radius)
return true return true
} }
return false return false
} }
func getRadiusElement(style Properties, tag string) any { func getRadiusElement(style Properties, tag PropertyName) any {
value := style.Get(Radius) value := style.Get(Radius)
if value != nil { if value != nil {
switch value := value.(type) { switch value := value.(type) {

75
range.go Normal file
View File

@ -0,0 +1,75 @@
package rui
import (
"fmt"
"strconv"
"strings"
)
// Range defines range limits. The First and Last value are included in the range
type Range struct {
First, Last int
}
// String returns a string representation of the Range struct
func (r Range) String() string {
if r.First == r.Last {
return fmt.Sprintf("%d", r.First)
}
return fmt.Sprintf("%d:%d", r.First, r.Last)
}
func (r *Range) setValue(value string) bool {
var err error
if strings.Contains(value, ":") {
values := strings.Split(value, ":")
if len(values) != 2 {
ErrorLog("Invalid range value: " + value)
return false
}
if r.First, err = strconv.Atoi(strings.Trim(values[0], " \t\n\r")); err != nil {
ErrorLog(`Invalid first range value "` + value + `" (` + err.Error() + ")")
return false
}
if r.Last, err = strconv.Atoi(strings.Trim(values[1], " \t\n\r")); err != nil {
ErrorLog(`Invalid last range value "` + value + `" (` + err.Error() + ")")
return false
}
return true
}
if r.First, err = strconv.Atoi(value); err != nil {
ErrorLog(`Invalid range value "` + value + `" (` + err.Error() + ")")
return false
}
r.Last = r.First
return true
}
func setRangeProperty(properties Properties, tag PropertyName, value any) []PropertyName {
switch value := value.(type) {
case string:
if setSimpleProperty(properties, tag, value) {
return []PropertyName{tag}
}
var r Range
if !r.setValue(value) {
invalidPropertyValue(tag, value)
return nil
}
properties.setRaw(tag, r)
case Range:
properties.setRaw(tag, value)
default:
if n, ok := isInt(value); ok {
properties.setRaw(tag, Range{First: n, Last: n})
} else {
notCompatibleType(tag, value)
return nil
}
}
return []PropertyName{tag}
}

View File

@ -62,7 +62,6 @@ type Resizable interface {
type resizableData struct { type resizableData struct {
viewData viewData
content []View
} }
// NewResizable create new Resizable object and return it // NewResizable create new Resizable object and return it
@ -74,147 +73,99 @@ func NewResizable(session Session, params Params) Resizable {
} }
func newResizable(session Session) View { func newResizable(session Session) View {
return NewResizable(session, nil) return new(resizableData)
} }
func (resizable *resizableData) init(session Session) { func (resizable *resizableData) init(session Session) {
resizable.viewData.init(session) resizable.viewData.init(session)
resizable.tag = "Resizable" resizable.tag = "Resizable"
resizable.systemClass = "ruiGridLayout" resizable.systemClass = "ruiGridLayout"
resizable.content = []View{} resizable.set = resizableSet
} resizable.changed = resizablePropertyChanged
func (resizable *resizableData) String() string {
return getViewString(resizable, nil)
} }
func (resizable *resizableData) Views() []View { func (resizable *resizableData) Views() []View {
return resizable.content if view := resizable.content(); view != nil {
return []View{view}
}
return []View{}
} }
func (resizable *resizableData) Remove(tag string) { func (resizable *resizableData) content() View {
resizable.remove(strings.ToLower(tag)) if value := resizable.getRaw(Content); value != nil {
if content, ok := value.(View); ok {
return content
}
}
return nil
} }
func (resizable *resizableData) remove(tag string) { func resizableSet(view View, tag PropertyName, value any) []PropertyName {
switch tag { switch tag {
case Side: case Side:
oldSide := resizable.getSide() return resizableSetSide(view, value)
delete(resizable.properties, Side)
if oldSide != resizable.getSide() {
if resizable.created {
updateInnerHTML(resizable.htmlID(), resizable.Session())
resizable.updateResizeBorderWidth()
}
resizable.propertyChangedEvent(tag)
}
case ResizeBorderWidth: case ResizeBorderWidth:
w := resizable.resizeBorderWidth() return setSizeProperty(view, tag, value)
delete(resizable.properties, ResizeBorderWidth)
if !w.Equal(resizable.resizeBorderWidth()) {
resizable.updateResizeBorderWidth()
resizable.propertyChangedEvent(tag)
}
case Content:
if len(resizable.content) > 0 {
resizable.content = []View{}
if resizable.created {
updateInnerHTML(resizable.htmlID(), resizable.Session())
}
resizable.propertyChangedEvent(tag)
}
default:
resizable.viewData.remove(tag)
}
}
func (resizable *resizableData) Set(tag string, value any) bool {
return resizable.set(strings.ToLower(tag), value)
}
func (resizable *resizableData) set(tag string, value any) bool {
if value == nil {
resizable.remove(tag)
return true
}
switch tag {
case Side:
oldSide := resizable.getSide()
if !resizable.setSide(value) {
notCompatibleType(tag, value)
return false
}
if oldSide != resizable.getSide() {
if resizable.created {
updateInnerHTML(resizable.htmlID(), resizable.Session())
resizable.updateResizeBorderWidth()
}
resizable.propertyChangedEvent(tag)
}
return true
case ResizeBorderWidth:
w := resizable.resizeBorderWidth()
ok := resizable.setSizeProperty(tag, value)
if ok && !w.Equal(resizable.resizeBorderWidth()) {
resizable.updateResizeBorderWidth()
resizable.propertyChangedEvent(tag)
}
return ok
case Content: case Content:
var newContent View = nil var newContent View = nil
switch value := value.(type) { switch value := value.(type) {
case string: case string:
newContent = NewTextView(resizable.Session(), Params{Text: value}) newContent = NewTextView(view.Session(), Params{Text: value})
case View: case View:
newContent = value newContent = value
case DataObject: case DataObject:
if view := CreateViewFromObject(resizable.Session(), value); view != nil { if newContent = CreateViewFromObject(view.Session(), value); newContent == nil {
newContent = view return nil
} else {
return false
} }
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
if len(resizable.content) == 0 { view.setRaw(Content, newContent)
resizable.content = []View{newContent} return []PropertyName{}
} else {
resizable.content[0] = newContent
}
if resizable.created {
updateInnerHTML(resizable.htmlID(), resizable.Session())
}
resizable.propertyChangedEvent(tag)
return true
case CellWidth, CellHeight, GridRowGap, GridColumnGap, CellVerticalAlign, CellHorizontalAlign: case CellWidth, CellHeight, GridRowGap, GridColumnGap, CellVerticalAlign, CellHorizontalAlign:
ErrorLogF(`Not supported "%s" property`, tag) ErrorLogF(`Not supported "%s" property`, string(tag))
return false return nil
} }
return resizable.viewData.set(tag, value) return viewSet(view, tag, value)
} }
func (resizable *resizableData) Get(tag string) any { func resizablePropertyChanged(view View, tag PropertyName) {
return resizable.get(strings.ToLower(tag)) switch tag {
case Side:
updateInnerHTML(view.htmlID(), view.Session())
fallthrough
case ResizeBorderWidth:
htmlID := view.htmlID()
session := view.Session()
column, row := resizableCellSizeCSS(view)
session.updateCSSProperty(htmlID, "grid-template-columns", column)
session.updateCSSProperty(htmlID, "grid-template-rows", row)
case Content:
updateInnerHTML(view.htmlID(), view.Session())
default:
viewPropertyChanged(view, tag)
}
} }
func (resizable *resizableData) getSide() int { func resizableSide(view View) int {
if value := resizable.getRaw(Side); value != nil { if value := view.getRaw(Side); value != nil {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if value, ok := resizable.session.resolveConstants(value); ok { if value, ok := view.Session().resolveConstants(value); ok {
validValues := map[string]int{ validValues := map[string]int{
"top": TopSide, "top": TopSide,
"right": RightSide, "right": RightSide,
@ -258,15 +209,15 @@ func (resizable *resizableData) getSide() int {
return AllSides return AllSides
} }
func (resizable *resizableData) setSide(value any) bool { func resizableSetSide(properties Properties, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if n, err := strconv.Atoi(value); err == nil { if n, err := strconv.Atoi(value); err == nil {
if n >= 1 && n <= AllSides { if n >= 1 && n <= AllSides {
resizable.properties[Side] = n properties.setRaw(Side, n)
return true return []PropertyName{Side}
} }
return false return nil
} }
validValues := map[string]int{ validValues := map[string]int{
"top": TopSide, "top": TopSide,
@ -287,13 +238,13 @@ func (resizable *resizableData) setSide(value any) bool {
hasConst = true hasConst = true
} else if n, err := strconv.Atoi(val); err == nil { } else if n, err := strconv.Atoi(val); err == nil {
if n < 1 || n > AllSides { if n < 1 || n > AllSides {
return false return nil
} }
sides |= n sides |= n
} else if n, ok := validValues[val]; ok { } else if n, ok := validValues[val]; ok {
sides |= n sides |= n
} else { } else {
return false return nil
} }
} }
@ -302,69 +253,58 @@ func (resizable *resizableData) setSide(value any) bool {
for i := 1; i < len(values); i++ { for i := 1; i < len(values); i++ {
value += "|" + values[i] value += "|" + values[i]
} }
resizable.properties[Side] = value properties.setRaw(Side, value)
return true return []PropertyName{Side}
} }
if sides >= 1 && sides <= AllSides { if sides >= 1 && sides <= AllSides {
resizable.properties[Side] = sides properties.setRaw(Side, sides)
return true return []PropertyName{Side}
} }
} else if value[0] == '@' { } else if value[0] == '@' {
resizable.properties[Side] = value properties.setRaw(Side, value)
return true return []PropertyName{Side}
} else if n, ok := validValues[value]; ok { } else if n, ok := validValues[value]; ok {
resizable.properties[Side] = n properties.setRaw(Side, n)
return true return []PropertyName{Side}
} }
case int: case int:
if value >= 1 && value <= AllSides { if value >= 1 && value <= AllSides {
resizable.properties[Side] = value properties.setRaw(Side, value)
return true return []PropertyName{Side}
} else { } else {
ErrorLogF(`Invalid value %d of "side" property`, value) ErrorLogF(`Invalid value %d of "side" property`, value)
return false return nil
} }
default: default:
if n, ok := isInt(value); ok { if n, ok := isInt(value); ok {
if n >= 1 && n <= AllSides { if n >= 1 && n <= AllSides {
resizable.properties[Side] = n properties.setRaw(Side, n)
return true return []PropertyName{Side}
} else { } else {
ErrorLogF(`Invalid value %d of "side" property`, n) ErrorLogF(`Invalid value %d of "side" property`, n)
return false return nil
} }
} }
} }
return false return nil
} }
func (resizable *resizableData) resizeBorderWidth() SizeUnit { func resizableBorderWidth(view View) SizeUnit {
result, _ := sizeProperty(resizable, ResizeBorderWidth, resizable.Session()) result, _ := sizeProperty(view, ResizeBorderWidth, view.Session())
if result.Type == Auto || result.Value == 0 { if result.Type == Auto || result.Value == 0 {
return Px(4) return Px(4)
} }
return result return result
} }
func (resizable *resizableData) updateResizeBorderWidth() { func resizableCellSizeCSS(view View) (string, string) {
if resizable.created { w := resizableBorderWidth(view).cssString("4px", view.Session())
htmlID := resizable.htmlID() side := resizableSide(view)
session := resizable.Session()
column, row := resizable.cellSizeCSS()
session.updateCSSProperty(htmlID, "grid-template-columns", column)
session.updateCSSProperty(htmlID, "grid-template-rows", row)
}
}
func (resizable *resizableData) cellSizeCSS() (string, string) {
w := resizable.resizeBorderWidth().cssString("4px", resizable.Session())
side := resizable.getSide()
column := "1fr" column := "1fr"
row := "1fr" row := "1fr"
@ -392,7 +332,7 @@ func (resizable *resizableData) cellSizeCSS() (string, string) {
} }
func (resizable *resizableData) cssStyle(self View, builder cssBuilder) { func (resizable *resizableData) cssStyle(self View, builder cssBuilder) {
column, row := resizable.cellSizeCSS() column, row := resizableCellSizeCSS(resizable)
builder.add("grid-template-columns", column) builder.add("grid-template-columns", column)
builder.add("grid-template-rows", row) builder.add("grid-template-rows", row)
@ -402,12 +342,12 @@ func (resizable *resizableData) cssStyle(self View, builder cssBuilder) {
func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder) { func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder) {
side := resizable.getSide() side := resizableSide(resizable)
left := 1 left := 1
top := 1 top := 1
leftSide := (side & LeftSide) != 0 leftSide := (side & LeftSide) != 0
rightSide := (side & RightSide) != 0 rightSide := (side & RightSide) != 0
w := resizable.resizeBorderWidth().cssString("4px", resizable.Session()) w := resizableBorderWidth(resizable).cssString("4px", resizable.Session())
if leftSide { if leftSide {
left = 2 left = 2
@ -484,8 +424,7 @@ func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder)
} }
} }
if len(resizable.content) > 0 { if view := resizable.content(); view != nil {
view := resizable.content[0]
view.addToCSSStyle(map[string]string{ view.addToCSSStyle(map[string]string{
"grid-column-start": strconv.Itoa(left), "grid-column-start": strconv.Itoa(left),
"grid-column-end": strconv.Itoa(left + 1), "grid-column-end": strconv.Itoa(left + 1),

View File

@ -16,7 +16,7 @@ package rui
// `func(frame rui.Frame)`, // `func(frame rui.Frame)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
const ResizeEvent = "resize-event" const ResizeEvent PropertyName = "resize-event"
func (view *viewData) onResize(self View, x, y, width, height float64) { func (view *viewData) onResize(self View, x, y, width, height float64) {
view.frame.Left = x view.frame.Left = x
@ -31,21 +31,20 @@ func (view *viewData) onResize(self View, x, y, width, height float64) {
func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) { func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) {
} }
func (view *viewData) setFrameListener(tag string, value any) bool { /*
listeners, ok := valueToEventListeners[View, Frame](value) func setFrameListener(properties Properties, tag PropertyName, value any) bool {
if !ok { if listeners, ok := valueToEventListeners[View, Frame](value); ok {
notCompatibleType(tag, value) if len(listeners) == 0 {
return false properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
return true
} }
notCompatibleType(tag, value)
if listeners == nil { return false
delete(view.properties, tag)
} else {
view.properties[tag] = listeners
}
view.propertyChangedEvent(tag)
return true
} }
*/
func (view *viewData) setNoResizeEvent() { func (view *viewData) setNoResizeEvent() {
view.noResizeEvent = true view.noResizeEvent = true

View File

@ -16,7 +16,7 @@ package rui
// `func(frame rui.Frame)`, // `func(frame rui.Frame)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
const ScrollEvent = "scroll-event" const ScrollEvent PropertyName = "scroll-event"
func (view *viewData) onScroll(self View, x, y, width, height float64) { func (view *viewData) onScroll(self View, x, y, width, height float64) {
view.scroll.Left = x view.scroll.Left = x

View File

@ -89,11 +89,11 @@ type Session interface {
RootView() View RootView() View
// Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument. // Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument.
// The type of return value depends on the property. If the property is not set then nil is returned. // The type of return value depends on the property. If the property is not set then nil is returned.
Get(viewID, tag string) any Get(viewID string, tag PropertyName) any
// Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument. // Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument.
// Return "true" if the value has been set, in the opposite case "false" are returned and // 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 // a description of the error is written to the log
Set(viewID, tag string, value any) bool Set(viewID string, tag PropertyName, value any) bool
// DownloadFile downloads (saves) on the client side the file located at the specified path on the server. // DownloadFile downloads (saves) on the client side the file located at the specified path on the server.
DownloadFile(path string) DownloadFile(path string)
@ -134,7 +134,7 @@ type Session interface {
viewByHTMLID(id string) View viewByHTMLID(id string) View
nextViewID() string nextViewID() string
styleProperty(styleTag, property string) any styleProperty(styleTag string, propertyTag PropertyName) any
setBridge(events chan DataObject, bridge bridge) setBridge(events chan DataObject, bridge bridge)
writeInitScript(writer *strings.Builder) writeInitScript(writer *strings.Builder)
@ -270,7 +270,7 @@ func (session *sessionData) close() {
} }
} }
func (session *sessionData) styleProperty(styleTag, propertyTag string) any { func (session *sessionData) styleProperty(styleTag string, propertyTag PropertyName) any {
if style := session.getCurrentTheme().style(styleTag); style != nil { if style := session.getCurrentTheme().style(styleTag); style != nil {
return style.getRaw(propertyTag) return style.getRaw(propertyTag)
} }
@ -376,14 +376,14 @@ func (session *sessionData) setIgnoreViewUpdates(ignore bool) {
session.ignoreUpdates = ignore session.ignoreUpdates = ignore
} }
func (session *sessionData) Get(viewID, tag string) any { func (session *sessionData) Get(viewID string, tag PropertyName) any {
if view := ViewByID(session.RootView(), viewID); view != nil { if view := ViewByID(session.RootView(), viewID); view != nil {
return view.Get(tag) return view.Get(tag)
} }
return nil return nil
} }
func (session *sessionData) Set(viewID, tag string, value any) bool { func (session *sessionData) Set(viewID string, tag PropertyName, value any) bool {
if view := ViewByID(session.RootView(), viewID); view != nil { if view := ViewByID(session.RootView(), viewID); view != nil {
return view.Set(tag, value) return view.Set(tag, value)
} }
@ -785,10 +785,10 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
if viewID, ok := data.PropertyValue("id"); ok { if viewID, ok := data.PropertyValue("id"); ok {
if viewID != "body" { if viewID != "body" {
if view := session.viewByHTMLID(viewID); view != nil { if view := session.viewByHTMLID(viewID); view != nil {
view.handleCommand(view, command, data) view.handleCommand(view, PropertyName(command), data)
} }
} }
if command == KeyDownEvent { if command == string(KeyDownEvent) {
var event KeyEvent var event KeyEvent
event.init(data) event.init(data)
session.hotKey(event) session.hotKey(event)

View File

@ -42,7 +42,7 @@ const (
// //
// Internal type is `Color`, other types converted to it during assignment. // Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details. // See `Color` description for more details.
ColorTag = "color" ColorTag PropertyName = "color"
// Inset is the constant for "inset" property tag. // Inset is the constant for "inset" property tag.
// //
@ -55,7 +55,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Drop shadow inside the frame(as if the content was depressed inside the box). // `true` or `1` or "true", "yes", "on", "1" - Drop shadow inside the frame(as if the content was depressed inside the box).
// `false` or `0` or "false", "no", "off", "0" - Shadow is assumed to be a drop shadow(as if the box were raised above the content). // `false` or `0` or "false", "no", "off", "0" - Shadow is assumed to be a drop shadow(as if the box were raised above the content).
Inset = "inset" Inset PropertyName = "inset"
// XOffset is the constant for "x-offset" property tag. // XOffset is the constant for "x-offset" property tag.
// //
@ -66,7 +66,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
XOffset = "x-offset" XOffset PropertyName = "x-offset"
// YOffset is the constant for "y-offset" property tag. // YOffset is the constant for "y-offset" property tag.
// //
@ -77,7 +77,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
YOffset = "y-offset" YOffset PropertyName = "y-offset"
// BlurRadius is the constant for "blur" property tag. // BlurRadius is the constant for "blur" property tag.
// //
@ -89,7 +89,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
BlurRadius = "blur" BlurRadius PropertyName = "blur"
// SpreadRadius is the constant for "spread-radius" property tag. // SpreadRadius is the constant for "spread-radius" property tag.
// //
@ -100,7 +100,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
SpreadRadius = "spread-radius" SpreadRadius PropertyName = "spread-radius"
) )
// ViewShadow contains attributes of the view shadow // ViewShadow contains attributes of the view shadow
@ -114,7 +114,7 @@ type ViewShadow interface {
} }
type viewShadowData struct { type viewShadowData struct {
propertyList dataProperty
} }
// NewViewShadow create the new shadow for a view. Arguments: // NewViewShadow create the new shadow for a view. Arguments:
@ -188,11 +188,12 @@ func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ViewShado
// "inset" (Inset). Controls (bool) whether to draw shadow inside the frame or outside. // "inset" (Inset). Controls (bool) whether to draw shadow inside the frame or outside.
func NewShadowWithParams(params Params) ViewShadow { func NewShadowWithParams(params Params) ViewShadow {
shadow := new(viewShadowData) shadow := new(viewShadowData)
shadow.propertyList.init() shadow.init()
if params != nil { if params != nil {
for _, tag := range []string{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius} { for _, tag := range []PropertyName{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius} {
if value, ok := params[tag]; ok && value != nil { if value, ok := params[tag]; ok && value != nil {
shadow.Set(tag, value) shadow.set(shadow, tag, value)
} }
} }
} }
@ -202,33 +203,14 @@ func NewShadowWithParams(params Params) ViewShadow {
// parseViewShadow parse DataObject and create ViewShadow object // parseViewShadow parse DataObject and create ViewShadow object
func parseViewShadow(object DataObject) ViewShadow { func parseViewShadow(object DataObject) ViewShadow {
shadow := new(viewShadowData) shadow := new(viewShadowData)
shadow.propertyList.init() shadow.init()
parseProperties(shadow, object) parseProperties(shadow, object)
return shadow return shadow
} }
func (shadow *viewShadowData) Remove(tag string) { func (shadow *viewShadowData) init() {
delete(shadow.properties, strings.ToLower(tag)) shadow.dataProperty.init()
} shadow.supportedProperties = []PropertyName{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius}
func (shadow *viewShadowData) Set(tag string, value any) bool {
if value == nil {
shadow.Remove(tag)
return true
}
tag = strings.ToLower(tag)
switch tag {
case ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius:
return shadow.propertyList.Set(tag, value)
}
ErrorLogF(`"%s" property is not supported by Shadow`, tag)
return false
}
func (shadow *viewShadowData) Get(tag string) any {
return shadow.propertyList.Get(strings.ToLower(tag))
} }
func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session, lead string) bool { func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session, lead string) bool {
@ -316,7 +298,7 @@ func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -325,29 +307,29 @@ func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string
buffer.WriteString(" }") buffer.WriteString(" }")
} }
func (properties *propertyList) setShadow(tag string, value any) bool { func setShadowProperty(properties Properties, tag PropertyName, value any) bool {
if value == nil { if value == nil {
delete(properties.properties, tag) properties.setRaw(tag, nil)
return true return true
} }
switch value := value.(type) { switch value := value.(type) {
case ViewShadow: case ViewShadow:
properties.properties[tag] = []ViewShadow{value} properties.setRaw(tag, []ViewShadow{value})
case []ViewShadow: case []ViewShadow:
if len(value) == 0 { if len(value) == 0 {
delete(properties.properties, tag) properties.setRaw(tag, nil)
} else { } else {
properties.properties[tag] = value properties.setRaw(tag, value)
} }
case DataValue: case DataValue:
if !value.IsObject() { if !value.IsObject() {
return false return false
} }
properties.properties[tag] = []ViewShadow{parseViewShadow(value.Object())} properties.setRaw(tag, []ViewShadow{parseViewShadow(value.Object())})
case []DataValue: case []DataValue:
shadows := []ViewShadow{} shadows := []ViewShadow{}
@ -359,7 +341,7 @@ func (properties *propertyList) setShadow(tag string, value any) bool {
if len(shadows) == 0 { if len(shadows) == 0 {
return false return false
} }
properties.properties[tag] = shadows properties.setRaw(tag, shadows)
case string: case string:
obj := NewDataObject(value) obj := NewDataObject(value)
@ -367,7 +349,7 @@ func (properties *propertyList) setShadow(tag string, value any) bool {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return false
} }
properties.properties[tag] = []ViewShadow{parseViewShadow(obj)} properties.setRaw(tag, []ViewShadow{parseViewShadow(obj)})
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
@ -377,7 +359,7 @@ func (properties *propertyList) setShadow(tag string, value any) bool {
return true return true
} }
func getShadows(properties Properties, tag string) []ViewShadow { func getShadows(properties Properties, tag PropertyName) []ViewShadow {
if value := properties.Get(tag); value != nil { if value := properties.Get(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case []ViewShadow: case []ViewShadow:
@ -390,7 +372,7 @@ func getShadows(properties Properties, tag string) []ViewShadow {
return []ViewShadow{} return []ViewShadow{}
} }
func shadowCSS(properties Properties, tag string, session Session) string { func shadowCSS(properties Properties, tag PropertyName, session Session) string {
shadows := getShadows(properties, tag) shadows := getShadows(properties, tag)
if len(shadows) == 0 { if len(shadows) == 0 {
return "" return ""

View File

@ -55,7 +55,7 @@ type StackLayout interface {
type stackLayoutData struct { type stackLayoutData struct {
viewsContainerData viewsContainerData
peek int peek, prevPeek int
pushView, popView View pushView, popView View
animationType int animationType int
onPushFinished func() onPushFinished func()
@ -71,7 +71,8 @@ func NewStackLayout(session Session, params Params) StackLayout {
} }
func newStackLayout(session Session) View { func newStackLayout(session Session) View {
return NewStackLayout(session, nil) //return NewStackLayout(session, nil)
return new(stackLayoutData)
} }
// Init initialize fields of ViewsContainer by default values // Init initialize fields of ViewsContainer by default values
@ -80,10 +81,9 @@ func (layout *stackLayoutData) init(session Session) {
layout.tag = "StackLayout" layout.tag = "StackLayout"
layout.systemClass = "ruiStackLayout" layout.systemClass = "ruiStackLayout"
layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished} layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished}
} layout.getFunc = layout.get
layout.set = layout.setFunc
func (layout *stackLayoutData) String() string { layout.remove = layout.removeFunc
return getViewString(layout, nil)
} }
func (layout *stackLayoutData) pushFinished(view View, tag string) { func (layout *stackLayoutData) pushFinished(view View, tag string) {
@ -97,7 +97,7 @@ func (layout *stackLayoutData) pushFinished(view View, tag string) {
layout.peek = 0 layout.peek = 0
} }
updateInnerHTML(layout.htmlID(), layout.session) updateInnerHTML(layout.htmlID(), layout.session)
layout.propertyChangedEvent(Current) layout.currentChanged()
} }
if layout.onPushFinished != nil { if layout.onPushFinished != nil {
@ -121,97 +121,91 @@ func (layout *stackLayoutData) popFinished(view View, tag string) {
} }
} }
func (layout *stackLayoutData) Set(tag string, value any) bool { func (layout *stackLayoutData) setFunc(view View, tag PropertyName, value any) []PropertyName {
return layout.set(strings.ToLower(tag), value)
}
func (layout *stackLayoutData) set(tag string, value any) bool {
if value == nil {
layout.remove(tag)
return true
}
switch tag { switch tag {
case TransitionEndEvent: case TransitionEndEvent:
listeners, ok := valueToEventListeners[View, string](value) listeners, ok := valueToEventListeners[View, string](value)
if ok && listeners != nil { if ok && listeners != nil {
listeners = append(listeners, layout.pushFinished) listeners = append(listeners, layout.pushFinished)
listeners = append(listeners, layout.popFinished) listeners = append(listeners, layout.popFinished)
layout.properties[TransitionEndEvent] = listeners view.setRaw(TransitionEndEvent, listeners)
layout.propertyChangedEvent(TransitionEndEvent) return []PropertyName{tag}
} }
return ok return nil
case Current: case Current:
setCurrent := func(index int) { newCurrent := 0
if index != layout.peek {
if layout.peek < len(layout.views) {
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(layout.peek), "visibility", "hidden")
}
layout.peek = index
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(index), "visibility", "visible")
layout.propertyChangedEvent(Current)
}
}
switch value := value.(type) { switch value := value.(type) {
case string: case string:
text, ok := layout.session.resolveConstants(value) text, ok := layout.session.resolveConstants(value)
if !ok { if !ok {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return false return nil
} }
n, err := strconv.Atoi(strings.Trim(text, " \t")) n, err := strconv.Atoi(strings.Trim(text, " \t"))
if err != nil { if err != nil {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
ErrorLog(err.Error()) ErrorLog(err.Error())
return false return nil
} }
setCurrent(n) newCurrent = n
default: default:
n, ok := isInt(value) n, ok := isInt(value)
if !ok { if !ok {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} else if n < 0 || n >= len(layout.views) { } else if n < 0 || n >= len(layout.views) {
ErrorLogF(`The view index "%d" of "%s" property is out of range`, n, tag) ErrorLogF(`The view index "%d" of "%s" property is out of range`, n, tag)
return false return nil
} }
setCurrent(n) newCurrent = n
} }
return true
layout.prevPeek = layout.peek
if newCurrent == layout.peek {
return []PropertyName{}
}
layout.peek = newCurrent
return []PropertyName{tag}
} }
return layout.viewsContainerData.set(tag, value) return layout.viewsContainerData.setFunc(view, tag, value)
} }
func (layout *stackLayoutData) Remove(tag string) { func (layout *stackLayoutData) propertyChanged(view View, tag PropertyName) {
layout.remove(strings.ToLower(tag)) 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.prevPeek), "visibility", "visible")
layout.prevPeek = layout.peek
}
default:
viewsContainerPropertyChanged(view, tag)
}
} }
func (layout *stackLayoutData) remove(tag string) { func (layout *stackLayoutData) removeFunc(view View, tag PropertyName) []PropertyName {
switch tag { switch tag {
case TransitionEndEvent: case TransitionEndEvent:
layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished} view.setRaw(TransitionEndEvent, []func(View, string){layout.pushFinished, layout.popFinished})
layout.propertyChangedEvent(TransitionEndEvent) return []PropertyName{tag}
case Current: case Current:
layout.set(Current, 0) view.setRaw(Current, 0)
return []PropertyName{tag}
default:
layout.viewsContainerData.remove(tag)
} }
return layout.viewsContainerData.removeFunc(view, tag)
} }
func (layout *stackLayoutData) Get(tag string) any { func (layout *stackLayoutData) get(view View, tag PropertyName) any {
return layout.get(strings.ToLower(tag))
}
func (layout *stackLayoutData) get(tag string) any {
if tag == Current { if tag == Current {
return layout.peek return layout.peek
} }
return layout.viewsContainerData.get(tag) return layout.viewsContainerData.get(view, tag)
} }
func (layout *stackLayoutData) Peek() View { func (layout *stackLayoutData) Peek() View {
@ -233,7 +227,7 @@ func (layout *stackLayoutData) MoveToFront(view View) bool {
layout.peek = i layout.peek = i
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible") layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
layout.propertyChangedEvent(Current) layout.currentChanged()
} }
return true return true
} }
@ -243,6 +237,12 @@ func (layout *stackLayoutData) MoveToFront(view View) bool {
return false return false
} }
func (layout *stackLayoutData) currentChanged() {
if listener, ok := layout.changeListener[Current]; ok {
listener(layout, Current)
}
}
func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool { func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool {
peek := int(layout.peek) peek := int(layout.peek)
for i, view := range layout.views { for i, view := range layout.views {
@ -254,7 +254,7 @@ func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool {
layout.peek = i layout.peek = i
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible") layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
layout.propertyChangedEvent(Current) layout.currentChanged()
} }
return true return true
} }
@ -268,7 +268,7 @@ func (layout *stackLayoutData) Append(view View) {
if view != nil { if view != nil {
layout.peek = len(layout.views) layout.peek = len(layout.views)
layout.viewsContainerData.Append(view) layout.viewsContainerData.Append(view)
layout.propertyChangedEvent(Current) layout.currentChanged()
} else { } else {
ErrorLog("StackLayout.Append(nil, ....) is forbidden") ErrorLog("StackLayout.Append(nil, ....) is forbidden")
} }
@ -283,7 +283,7 @@ func (layout *stackLayoutData) Insert(view View, index int) {
layout.peek = count layout.peek = count
} }
layout.viewsContainerData.Insert(view, index) layout.viewsContainerData.Insert(view, index)
layout.propertyChangedEvent(Current) layout.currentChanged()
} else { } else {
ErrorLog("StackLayout.Insert(nil, ....) is forbidden") ErrorLog("StackLayout.Insert(nil, ....) is forbidden")
} }
@ -297,7 +297,7 @@ func (layout *stackLayoutData) RemoveView(index int) View {
if layout.peek > 0 { if layout.peek > 0 {
layout.peek-- layout.peek--
} }
defer layout.propertyChangedEvent(Current) defer layout.currentChanged()
return layout.viewsContainerData.RemoveView(index) return layout.viewsContainerData.RemoveView(index)
} }
@ -352,7 +352,10 @@ func (layout *stackLayoutData) Push(view View, animation int, onPushFinished fun
layout.views = append(layout.views, view) layout.views = append(layout.views, view)
view.setParentID(htmlID) view.setParentID(htmlID)
layout.propertyChangedEvent(Content)
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
} }
func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool { func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool {

View File

@ -25,7 +25,7 @@ func NewSvgImageView(session Session, params Params) SvgImageView {
} }
func newSvgImageView(session Session) View { func newSvgImageView(session Session) View {
return NewSvgImageView(session, nil) return new(svgImageViewData) // NewSvgImageView(session, nil)
} }
// Init initialize fields of imageView by default values // Init initialize fields of imageView by default values
@ -33,14 +33,14 @@ func (imageView *svgImageViewData) init(session Session) {
imageView.viewData.init(session) imageView.viewData.init(session)
imageView.tag = "SvgImageView" imageView.tag = "SvgImageView"
imageView.systemClass = "ruiSvgImageView" imageView.systemClass = "ruiSvgImageView"
imageView.normalize = normalizeSvgImageViewTag
imageView.set = svgImageViewSet
imageView.changed = svgImageViewPropertyChanged
} }
func (imageView *svgImageViewData) String() string { func normalizeSvgImageViewTag(tag PropertyName) PropertyName {
return getViewString(imageView, nil) tag = defaultNormalize(tag)
}
func (imageView *svgImageViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag { switch tag {
case Source, "source": case Source, "source":
tag = Content tag = Content
@ -54,51 +54,29 @@ func (imageView *svgImageViewData) normalizeTag(tag string) string {
return tag return tag
} }
func (imageView *svgImageViewData) Remove(tag string) { func svgImageViewSet(view View, tag PropertyName, value any) []PropertyName {
imageView.remove(imageView.normalizeTag(tag))
}
func (imageView *svgImageViewData) remove(tag string) {
imageView.viewData.remove(tag)
if imageView.created {
switch tag {
case Content:
updateInnerHTML(imageView.htmlID(), imageView.session)
}
}
}
func (imageView *svgImageViewData) Set(tag string, value any) bool {
return imageView.set(imageView.normalizeTag(tag), value)
}
func (imageView *svgImageViewData) set(tag string, value any) bool {
if value == nil {
imageView.remove(tag)
return true
}
switch tag { switch tag {
case Content: case Content:
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
imageView.properties[Content] = text view.setRaw(Content, text)
if imageView.created { return []PropertyName{tag}
updateInnerHTML(imageView.htmlID(), imageView.session)
}
imageView.propertyChangedEvent(Content)
return true
} }
notCompatibleType(Source, value) notCompatibleType(Source, value)
return false return nil
default: default:
return imageView.viewData.set(tag, value) return viewSet(view, tag, value)
} }
} }
func (imageView *svgImageViewData) Get(tag string) any { func svgImageViewPropertyChanged(view View, tag PropertyName) {
return imageView.viewData.get(imageView.normalizeTag(tag)) switch tag {
case Content:
updateInnerHTML(view.htmlID(), view.Session())
default:
viewPropertyChanged(view, tag)
}
} }
func (imageView *svgImageViewData) htmlTag() string { func (imageView *svgImageViewData) htmlTag() string {

View File

@ -256,78 +256,3 @@ func (style *simpleTableLineStyle) RowStyle(row int) Params {
} }
return nil return nil
} }
func (table *tableViewData) setLineStyle(tag string, value any) bool {
switch value := value.(type) {
case []Params:
if len(value) > 0 {
style := new(simpleTableLineStyle)
style.params = value
table.properties[tag] = style
} else {
delete(table.properties, tag)
}
case DataNode:
if params := value.ArrayAsParams(); len(params) > 0 {
style := new(simpleTableLineStyle)
style.params = params
table.properties[tag] = style
} else {
delete(table.properties, tag)
}
default:
return false
}
return true
}
func (table *tableViewData) setRowStyle(value any) bool {
switch value := value.(type) {
case TableRowStyle:
table.properties[RowStyle] = value
}
return table.setLineStyle(RowStyle, value)
}
func (table *tableViewData) getRowStyle() TableRowStyle {
for _, tag := range []string{RowStyle, Content} {
if value := table.getRaw(tag); value != nil {
if style, ok := value.(TableRowStyle); ok {
return style
}
}
}
return nil
}
func (table *tableViewData) setColumnStyle(value any) bool {
switch value := value.(type) {
case TableColumnStyle:
table.properties[ColumnStyle] = value
}
return table.setLineStyle(ColumnStyle, value)
}
func (table *tableViewData) getColumnStyle() TableColumnStyle {
for _, tag := range []string{ColumnStyle, Content} {
if value := table.getRaw(tag); value != nil {
if style, ok := value.(TableColumnStyle); ok {
return style
}
}
}
return nil
}
func (table *tableViewData) getCellStyle() TableCellStyle {
for _, tag := range []string{CellStyle, Content} {
if value := table.getRaw(tag); value != nil {
if style, ok := value.(TableCellStyle); ok {
return style
}
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,19 @@
package rui package rui
import "strings" func newTableCellView(session Session) *tableCellView {
view := new(tableCellView)
func (cell *tableCellView) Set(tag string, value any) bool { view.init(session)
return cell.set(strings.ToLower(tag), value) return view
} }
func (cell *tableCellView) set(tag string, value any) bool { func (cell *tableCellView) init(session Session) {
switch tag { cell.viewData.init(session)
case VerticalAlign: cell.normalize = func(tag PropertyName) PropertyName {
tag = TableVerticalAlign if tag == VerticalAlign {
return TableVerticalAlign
}
return tag
} }
return cell.viewData.set(tag, value)
} }
func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
@ -31,8 +33,10 @@ func GetTableContent(view View, subviewID ...string) TableAdapter {
} }
if view != nil { if view != nil {
if tableView, ok := view.(TableView); ok { if content := view.getRaw(Content); content != nil {
return tableView.content() if adapter, ok := content.(TableAdapter); ok {
return adapter
}
} }
} }
@ -47,8 +51,12 @@ func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
} }
if view != nil { if view != nil {
if tableView, ok := view.(TableView); ok { for _, tag := range []PropertyName{RowStyle, Content} {
return tableView.getRowStyle() if value := view.getRaw(tag); value != nil {
if style, ok := value.(TableRowStyle); ok {
return style
}
}
} }
} }
@ -63,8 +71,12 @@ func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
} }
if view != nil { if view != nil {
if tableView, ok := view.(TableView); ok { for _, tag := range []PropertyName{ColumnStyle, Content} {
return tableView.getColumnStyle() if value := view.getRaw(tag); value != nil {
if style, ok := value.(TableColumnStyle); ok {
return style
}
}
} }
} }
@ -79,9 +91,14 @@ func GetTableCellStyle(view View, subviewID ...string) TableCellStyle {
} }
if view != nil { if view != nil {
if tableView, ok := view.(TableView); ok { for _, tag := range []PropertyName{CellStyle, Content} {
return tableView.getCellStyle() if value := view.getRaw(tag); value != nil {
if style, ok := value.(TableCellStyle); ok {
return style
}
}
} }
return nil
} }
return nil return nil
@ -125,9 +142,7 @@ func GetTableCurrent(view View, subviewID ...string) CellIndex {
if view != nil { if view != nil {
if selectionMode := GetTableSelectionMode(view); selectionMode != NoneSelection { if selectionMode := GetTableSelectionMode(view); selectionMode != NoneSelection {
if tableView, ok := view.(TableView); ok { return tableViewCurrent(view)
return tableView.getCurrent()
}
} }
} }
return CellIndex{Row: -1, Column: -1} return CellIndex{Row: -1, Column: -1}
@ -137,34 +152,14 @@ func GetTableCurrent(view View, subviewID ...string) CellIndex {
// If there are no listeners then the empty list is returned. // If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableCellClickedListeners(view View, subviewID ...string) []func(TableView, int, int) { func GetTableCellClickedListeners(view View, subviewID ...string) []func(TableView, int, int) {
if len(subviewID) > 0 && subviewID[0] != "" { return getEventWithOldListeners[TableView, int](view, subviewID, TableCellClickedEvent)
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(TableCellClickedEvent); value != nil {
if result, ok := value.([]func(TableView, int, int)); ok {
return result
}
}
}
return []func(TableView, int, int){}
} }
// GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected. // GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected.
// If there are no listeners then the empty list is returned. // If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableCellSelectedListeners(view View, subviewID ...string) []func(TableView, int, int) { func GetTableCellSelectedListeners(view View, subviewID ...string) []func(TableView, int, int) {
if len(subviewID) > 0 && subviewID[0] != "" { return getEventWithOldListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(TableCellSelectedEvent); value != nil {
if result, ok := value.([]func(TableView, int, int)); ok {
return result
}
}
}
return []func(TableView, int, int){}
} }
// GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row. // GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row.

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
// Constants for [TabsLayout] specific properties and events // Constants for [TabsLayout] specific view and events
const ( const (
// CurrentTabChangedEvent is the constant for "current-tab-changed" property tag. // CurrentTabChangedEvent is the constant for "current-tab-changed" property tag.
// //
@ -25,7 +25,7 @@ const (
// `func(newTab, oldTab int)`, // `func(newTab, oldTab int)`,
// `func(newTab int)`, // `func(newTab int)`,
// `func()`. // `func()`.
CurrentTabChangedEvent = "current-tab-changed" CurrentTabChangedEvent PropertyName = "current-tab-changed"
// Icon is the constant for "icon" property tag. // Icon is the constant for "icon" property tag.
// //
@ -47,7 +47,7 @@ const (
// Values: // Values:
// `true` or `1` or "true", "yes", "on", "1" - Tab(s) has close button. // `true` or `1` or "true", "yes", "on", "1" - Tab(s) has close button.
// `false` or `0` or "false", "no", "off", "0" - No close button in tab(s). // `false` or `0` or "false", "no", "off", "0" - No close button in tab(s).
TabCloseButton = "tab-close-button" TabCloseButton PropertyName = "tab-close-button"
// TabCloseEvent is the constant for "tab-close-event" property tag. // TabCloseEvent is the constant for "tab-close-event" property tag.
// //
@ -65,7 +65,7 @@ const (
// `func(tab int)`, // `func(tab int)`,
// `func(tabsLayout rui.TabsLayout)`, // `func(tabsLayout rui.TabsLayout)`,
// `func()`. // `func()`.
TabCloseEvent = "tab-close-event" TabCloseEvent PropertyName = "tab-close-event"
// Tabs is the constant for "tabs" property tag. // Tabs is the constant for "tabs" property tag.
// //
@ -82,7 +82,7 @@ const (
// `4`(`LeftListTabs`) or "left-list" - Tabs on the left. The tabs are displayed as a list. // `4`(`LeftListTabs`) or "left-list" - Tabs on the left. The tabs are displayed as a list.
// `5`(`RightListTabs`) or "right-list" - Tabs on the right. The tabs are displayed as a list. // `5`(`RightListTabs`) or "right-list" - Tabs on the right. The tabs are displayed as a list.
// `6`(`HiddenTabs`) or "hidden" - Tabs are hidden. // `6`(`HiddenTabs`) or "hidden" - Tabs are hidden.
Tabs = "tabs" Tabs PropertyName = "tabs"
// TabBarStyle is the constant for "tab-bar-style" property tag. // TabBarStyle is the constant for "tab-bar-style" property tag.
// //
@ -90,7 +90,7 @@ const (
// Set the style for the display of the tab bar. The default value is "ruiTabBar". // Set the style for the display of the tab bar. The default value is "ruiTabBar".
// //
// Supported types: `string`. // Supported types: `string`.
TabBarStyle = "tab-bar-style" TabBarStyle PropertyName = "tab-bar-style"
// TabStyle is the constant for "tab-style" property tag. // TabStyle is the constant for "tab-style" property tag.
// //
@ -98,7 +98,7 @@ const (
// Set the style for the display of the tab. The default value is "ruiTab" or "ruiVerticalTab". // Set the style for the display of the tab. The default value is "ruiTab" or "ruiVerticalTab".
// //
// Supported types: `string`. // Supported types: `string`.
TabStyle = "tab-style" TabStyle PropertyName = "tab-style"
// CurrentTabStyle is the constant for "current-tab-style" property tag. // CurrentTabStyle is the constant for "current-tab-style" property tag.
// //
@ -107,7 +107,7 @@ const (
// "ruiCurrentVerticalTab". // "ruiCurrentVerticalTab".
// //
// Supported types: `string`. // Supported types: `string`.
CurrentTabStyle = "current-tab-style" CurrentTabStyle PropertyName = "current-tab-style"
inactiveTabStyle = "data-inactiveTabStyle" inactiveTabStyle = "data-inactiveTabStyle"
activeTabStyle = "data-activeTabStyle" activeTabStyle = "data-activeTabStyle"
@ -139,8 +139,6 @@ type TabsLayout interface {
type tabsLayoutData struct { type tabsLayoutData struct {
viewsContainerData viewsContainerData
tabListener []func(TabsLayout, int, int)
tabCloseListener []func(TabsLayout, int)
} }
// NewTabsLayout create new TabsLayout object and return it // NewTabsLayout create new TabsLayout object and return it
@ -152,7 +150,8 @@ func NewTabsLayout(session Session, params Params) TabsLayout {
} }
func newTabsLayout(session Session) View { func newTabsLayout(session Session) View {
return NewTabsLayout(session, nil) //return NewTabsLayout(session, nil)
return new(tabsLayoutData)
} }
// Init initialize fields of ViewsContainer by default values // Init initialize fields of ViewsContainer by default values
@ -160,345 +159,229 @@ func (tabsLayout *tabsLayoutData) init(session Session) {
tabsLayout.viewsContainerData.init(session) tabsLayout.viewsContainerData.init(session)
tabsLayout.tag = "TabsLayout" tabsLayout.tag = "TabsLayout"
tabsLayout.systemClass = "ruiTabsLayout" tabsLayout.systemClass = "ruiTabsLayout"
tabsLayout.tabListener = []func(TabsLayout, int, int){} tabsLayout.set = tabsLayout.setFunc
tabsLayout.tabCloseListener = []func(TabsLayout, int){} tabsLayout.changed = tabsLayout.propertyChanged
} }
func (tabsLayout *tabsLayoutData) String() string { func tabsLayoutCurrent(view View, defaultValue int) int {
return getViewString(tabsLayout, nil) result, _ := intProperty(view, Current, view.Session(), defaultValue)
}
func (tabsLayout *tabsLayoutData) currentItem(defaultValue int) int {
result, _ := intProperty(tabsLayout, Current, tabsLayout.session, defaultValue)
return result return result
} }
func (tabsLayout *tabsLayoutData) Get(tag string) any { func (tabsLayout *tabsLayoutData) setFunc(view View, tag PropertyName, value any) []PropertyName {
return tabsLayout.get(strings.ToLower(tag))
}
func (tabsLayout *tabsLayoutData) get(tag string) any {
switch tag { switch tag {
case CurrentTabChangedEvent: case CurrentTabChangedEvent:
return tabsLayout.tabListener return setEventWithOldListener[TabsLayout, int](view, tag, value)
case TabCloseEvent: case TabCloseEvent:
return tabsLayout.tabCloseListener return setViewEventListener[TabsLayout, int](view, tag, value)
}
return tabsLayout.viewsContainerData.get(tag)
}
func (tabsLayout *tabsLayoutData) Remove(tag string) {
tabsLayout.remove(strings.ToLower(tag))
}
func (tabsLayout *tabsLayoutData) remove(tag string) {
switch tag {
case CurrentTabChangedEvent:
if len(tabsLayout.tabListener) > 0 {
tabsLayout.tabListener = []func(TabsLayout, int, int){}
tabsLayout.propertyChangedEvent(tag)
}
return
case TabCloseEvent:
if len(tabsLayout.tabCloseListener) > 0 {
tabsLayout.tabCloseListener = []func(TabsLayout, int){}
tabsLayout.propertyChangedEvent(tag)
}
return
case Current: case Current:
oldCurrent := tabsLayout.currentItem(0) view.setRaw("old-current", tabsLayoutCurrent(view, -1))
delete(tabsLayout.properties, Current)
if oldCurrent == 0 {
return
}
if tabsLayout.created {
tabsLayout.session.callFunc("activateTab", tabsLayout.htmlID(), 0)
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, 0, oldCurrent)
}
}
case Tabs:
delete(tabsLayout.properties, Tabs)
if tabsLayout.created {
htmlID := tabsLayout.htmlID()
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
updateCSSStyle(htmlID, tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabStyle, CurrentTabStyle:
delete(tabsLayout.properties, tag)
if tabsLayout.created {
htmlID := tabsLayout.htmlID()
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabCloseButton:
delete(tabsLayout.properties, tag)
if tabsLayout.created {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
}
default:
tabsLayout.viewsContainerData.remove(tag)
return
}
tabsLayout.propertyChangedEvent(tag)
}
func (tabsLayout *tabsLayoutData) Set(tag string, value any) bool {
return tabsLayout.set(strings.ToLower(tag), value)
}
func (tabsLayout *tabsLayoutData) set(tag string, value any) bool {
if value == nil {
tabsLayout.remove(tag)
return true
}
switch tag {
case CurrentTabChangedEvent:
listeners := tabsLayout.valueToTabListeners(value)
if listeners == nil {
notCompatibleType(tag, value)
return false
}
tabsLayout.tabListener = listeners
case TabCloseEvent:
listeners, ok := valueToEventListeners[TabsLayout, int](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(TabsLayout, int){}
}
tabsLayout.tabCloseListener = listeners
case Current:
if current, ok := value.(int); ok && current < 0 { if current, ok := value.(int); ok && current < 0 {
tabsLayout.remove(Current) view.setRaw(Current, nil)
return true return []PropertyName{tag}
} }
oldCurrent := tabsLayout.currentItem(-1) return setIntProperty(view, Current, value)
if !tabsLayout.setIntProperty(Current, value) {
return false
}
current := tabsLayout.currentItem(0) case TabStyle, CurrentTabStyle, TabBarStyle:
if oldCurrent == current { if text, ok := value.(string); ok {
return true return setStringPropertyValue(view, tag, text)
} }
if tabsLayout.created { notCompatibleType(tag, value)
tabsLayout.session.callFunc("activateTab", tabsLayout.htmlID(), current) return nil
for _, listener := range tabsLayout.tabListener { }
return tabsLayout.viewsContainerData.setFunc(tabsLayout, tag, value)
}
func (tabsLayout *tabsLayoutData) propertyChanged(view View, tag PropertyName) {
switch tag {
case Current:
session := view.Session()
current := GetCurrent(view)
session.callFunc("activateTab", view.htmlID(), current)
if listeners := getEventWithOldListeners[TabsLayout, int](view, nil, CurrentTabChangedEvent); len(listeners) > 0 {
oldCurrent, _ := intProperty(view, "old-current", session, -1)
for _, listener := range listeners {
listener(tabsLayout, current, oldCurrent) listener(tabsLayout, current, oldCurrent)
} }
} }
case Tabs: case Tabs:
if !tabsLayout.setEnumProperty(Tabs, value, enumProperties[Tabs].values) { htmlID := view.htmlID()
return false session := view.Session()
} session.updateProperty(htmlID, inactiveTabStyle, tabsLayoutInactiveTabStyle(view))
if tabsLayout.created { session.updateProperty(htmlID, activeTabStyle, tabsLayoutActiveTabStyle(view))
htmlID := tabsLayout.htmlID() updateCSSStyle(htmlID, session)
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle()) updateInnerHTML(htmlID, session)
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
updateCSSStyle(htmlID, tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabStyle, CurrentTabStyle, TabBarStyle: case TabStyle, CurrentTabStyle, TabBarStyle:
if text, ok := value.(string); ok { htmlID := view.htmlID()
if text == "" { session := view.Session()
delete(tabsLayout.properties, tag) session.updateProperty(htmlID, inactiveTabStyle, tabsLayoutInactiveTabStyle(view))
} else { session.updateProperty(htmlID, activeTabStyle, tabsLayoutActiveTabStyle(view))
tabsLayout.properties[tag] = text updateInnerHTML(htmlID, session)
}
} else {
notCompatibleType(tag, value)
return false
}
if tabsLayout.created {
htmlID := tabsLayout.htmlID()
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabCloseButton: case TabCloseButton:
if !tabsLayout.setBoolProperty(tag, value) { updateInnerHTML(view.htmlID(), view.Session())
return false
}
if tabsLayout.created {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
}
default: default:
return tabsLayout.viewsContainerData.set(tag, value) viewsContainerPropertyChanged(view, tag)
} }
tabsLayout.propertyChangedEvent(tag)
return true
} }
func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) { /*
if value == nil { func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) {
return []func(TabsLayout, int, int){} if value == nil {
} return []func(TabsLayout, int, int){}
switch value := value.(type) {
case func(TabsLayout, int, int):
return []func(TabsLayout, int, int){value}
case func(TabsLayout, int):
fn := func(view TabsLayout, current, _ int) {
value(view, current)
} }
return []func(TabsLayout, int, int){fn}
case func(TabsLayout): switch value := value.(type) {
fn := func(view TabsLayout, _, _ int) { case func(TabsLayout, int, int):
value(view) return []func(TabsLayout, int, int){value}
}
return []func(TabsLayout, int, int){fn}
case func(int, int): case func(TabsLayout, int):
fn := func(_ TabsLayout, current, old int) { fn := func(view TabsLayout, current, _ int) {
value(current, old) value(view, current)
}
return []func(TabsLayout, int, int){fn}
case func(int):
fn := func(_ TabsLayout, current, _ int) {
value(current)
}
return []func(TabsLayout, int, int){fn}
case func():
fn := func(TabsLayout, int, int) {
value()
}
return []func(TabsLayout, int, int){fn}
case []func(TabsLayout, int, int):
return value
case []func(TabsLayout, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
} }
listeners[i] = func(view TabsLayout, current, _ int) { return []func(TabsLayout, int, int){fn}
val(view, current)
}
}
return listeners
case []func(TabsLayout): case func(TabsLayout):
listeners := make([]func(TabsLayout, int, int), len(value)) fn := func(view TabsLayout, _, _ int) {
for i, val := range value { value(view)
if val == nil {
return nil
} }
listeners[i] = func(view TabsLayout, _, _ int) { return []func(TabsLayout, int, int){fn}
val(view)
}
}
return listeners
case []func(int, int): case func(int, int):
listeners := make([]func(TabsLayout, int, int), len(value)) fn := func(_ TabsLayout, current, old int) {
for i, val := range value { value(current, old)
if val == nil {
return nil
} }
listeners[i] = func(_ TabsLayout, current, old int) { return []func(TabsLayout, int, int){fn}
val(current, old)
}
}
return listeners
case []func(int): case func(int):
listeners := make([]func(TabsLayout, int, int), len(value)) fn := func(_ TabsLayout, current, _ int) {
for i, val := range value { value(current)
if val == nil {
return nil
} }
listeners[i] = func(_ TabsLayout, current, _ int) { return []func(TabsLayout, int, int){fn}
val(current)
}
}
return listeners
case []func(): case func():
listeners := make([]func(TabsLayout, int, int), len(value)) fn := func(TabsLayout, int, int) {
for i, val := range value { value()
if val == nil {
return nil
} }
listeners[i] = func(TabsLayout, int, int) { return []func(TabsLayout, int, int){fn}
val()
}
}
return listeners
case []any: case []func(TabsLayout, int, int):
listeners := make([]func(TabsLayout, int, int), len(value)) return value
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TabsLayout, int, int):
listeners[i] = val
case func(TabsLayout, int): case []func(TabsLayout, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, _ int) { listeners[i] = func(view TabsLayout, current, _ int) {
val(view, current) val(view, current)
} }
}
return listeners
case func(TabsLayout): case []func(TabsLayout):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, _, _ int) { listeners[i] = func(view TabsLayout, _, _ int) {
val(view) val(view)
} }
}
return listeners
case func(int, int): case []func(int, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(_ TabsLayout, current, old int) { listeners[i] = func(_ TabsLayout, current, old int) {
val(current, old) val(current, old)
} }
}
return listeners
case func(int): case []func(int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(_ TabsLayout, current, _ int) { listeners[i] = func(_ TabsLayout, current, _ int) {
val(current) val(current)
} }
}
return listeners
case func(): case []func():
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(TabsLayout, int, int) { listeners[i] = func(TabsLayout, int, int) {
val() val()
} }
default:
return nil
} }
} return listeners
return listeners
}
return nil case []any:
} listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TabsLayout, int, int):
listeners[i] = val
case func(TabsLayout, int):
listeners[i] = func(view TabsLayout, current, _ int) {
val(view, current)
}
case func(TabsLayout):
listeners[i] = func(view TabsLayout, _, _ int) {
val(view)
}
case func(int, int):
listeners[i] = func(_ TabsLayout, current, old int) {
val(current, old)
}
case func(int):
listeners[i] = func(_ TabsLayout, current, _ int) {
val(current)
}
case func():
listeners[i] = func(TabsLayout, int, int) {
val()
}
default:
return nil
}
}
return listeners
}
return nil
}
*/
func (tabsLayout *tabsLayoutData) tabsLocation() int { func (tabsLayout *tabsLayoutData) tabsLocation() int {
tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0) tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0)
@ -519,36 +402,42 @@ func (tabsLayout *tabsLayoutData) tabBarStyle() string {
return "ruiTabBar" return "ruiTabBar"
} }
func (tabsLayout *tabsLayoutData) inactiveTabStyle() string { func tabsLayoutInactiveTabStyle(view View) string {
if style, ok := stringProperty(tabsLayout, TabStyle, tabsLayout.session); ok { session := view.Session()
if style, ok := stringProperty(view, TabStyle, session); ok {
return style return style
} }
if value := valueFromStyle(tabsLayout, TabStyle); value != nil { if value := valueFromStyle(view, TabStyle); value != nil {
if style, ok := value.(string); ok { if style, ok := value.(string); ok {
if style, ok = tabsLayout.session.resolveConstants(style); ok { if style, ok = session.resolveConstants(style); ok {
return style return style
} }
} }
} }
switch tabsLayout.tabsLocation() {
tabs, _ := enumProperty(view, Tabs, session, 0)
switch tabs {
case LeftTabs, RightTabs: case LeftTabs, RightTabs:
return "ruiVerticalTab" return "ruiVerticalTab"
} }
return "ruiTab" return "ruiTab"
} }
func (tabsLayout *tabsLayoutData) activeTabStyle() string { func tabsLayoutActiveTabStyle(view View) string {
if style, ok := stringProperty(tabsLayout, CurrentTabStyle, tabsLayout.session); ok { session := view.Session()
if style, ok := stringProperty(view, CurrentTabStyle, session); ok {
return style return style
} }
if value := valueFromStyle(tabsLayout, CurrentTabStyle); value != nil { if value := valueFromStyle(view, CurrentTabStyle); value != nil {
if style, ok := value.(string); ok { if style, ok := value.(string); ok {
if style, ok = tabsLayout.session.resolveConstants(style); ok { if style, ok = session.resolveConstants(style); ok {
return style return style
} }
} }
} }
switch tabsLayout.tabsLocation() {
tabs, _ := enumProperty(view, Tabs, session, 0)
switch tabs {
case LeftTabs, RightTabs: case LeftTabs, RightTabs:
return "ruiCurrentVerticalTab" return "ruiCurrentVerticalTab"
} }
@ -610,7 +499,7 @@ func (tabsLayout *tabsLayoutData) ListItem(index int, session Session) View {
Column: 2, Column: 2,
Content: "✕", Content: "✕",
ClickEvent: func() { ClickEvent: func() {
for _, listener := range tabsLayout.tabCloseListener { for _, listener := range getEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
listener(tabsLayout, index) listener(tabsLayout, index)
} }
}, },
@ -632,7 +521,7 @@ func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
return true return true
} }
func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) { func (tabsLayout *tabsLayoutData) updateTitle(view View, tag PropertyName) {
session := tabsLayout.session session := tabsLayout.session
title, _ := stringProperty(view, Title, session) title, _ := stringProperty(view, Title, session)
if !GetNotTranslate(tabsLayout) { if !GetNotTranslate(tabsLayout) {
@ -641,13 +530,13 @@ func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) {
session.updateInnerHTML(view.htmlID()+"-title", title) session.updateInnerHTML(view.htmlID()+"-title", title)
} }
func (tabsLayout *tabsLayoutData) updateIcon(view View, tag string) { func (tabsLayout *tabsLayoutData) updateIcon(view View, tag PropertyName) {
session := tabsLayout.session session := tabsLayout.session
icon, _ := stringProperty(view, Icon, session) icon, _ := stringProperty(view, Icon, session)
session.updateProperty(view.htmlID()+"-icon", "src", icon) session.updateProperty(view.htmlID()+"-icon", "src", icon)
} }
func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag string) { func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag PropertyName) {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session) updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
} }
@ -662,11 +551,8 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
view.SetChangeListener(Icon, tabsLayout.updateIcon) view.SetChangeListener(Icon, tabsLayout.updateIcon)
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton) view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
if len(tabsLayout.views) == 1 { if len(tabsLayout.views) == 1 {
tabsLayout.properties[Current] = 0 tabsLayout.setRaw(Current, nil)
for _, listener := range tabsLayout.tabListener { tabsLayout.Set(Current, 0)
listener(tabsLayout, 0, -1)
}
defer tabsLayout.propertyChangedEvent(Current)
} }
} }
} }
@ -677,9 +563,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
tabsLayout.views = []View{} tabsLayout.views = []View{}
} }
if view != nil { if view != nil {
if current := tabsLayout.currentItem(0); current >= index { if current := GetCurrent(tabsLayout); current >= index {
tabsLayout.properties[Current] = current + 1 tabsLayout.setRaw(Current, current+1)
defer tabsLayout.propertyChangedEvent(Current) defer tabsLayout.currentChanged()
} }
tabsLayout.viewsContainerData.Insert(view, index) tabsLayout.viewsContainerData.Insert(view, index)
view.SetChangeListener(Title, tabsLayout.updateTitle) view.SetChangeListener(Title, tabsLayout.updateTitle)
@ -688,56 +574,87 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
} }
} }
func (tabsLayout *tabsLayoutData) currentChanged() {
if listener, ok := tabsLayout.changeListener[Current]; ok {
listener(tabsLayout, Current)
}
}
// Remove removes view from list and return it // Remove removes view from list and return it
func (tabsLayout *tabsLayoutData) RemoveView(index int) View { func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
if tabsLayout.views == nil {
tabsLayout.views = []View{} if index < 0 || index >= len(tabsLayout.views) {
return nil return nil
} }
count := len(tabsLayout.views) oldCurrent := GetCurrent(tabsLayout)
if index < 0 || index >= count { newCurrent := oldCurrent
return nil if index < oldCurrent || (index == oldCurrent && oldCurrent > 0) {
newCurrent--
} }
view := tabsLayout.views[index] if view := tabsLayout.viewsContainerData.RemoveView(index); view != nil {
view.setParentID("") view.SetChangeListener(Title, nil)
view.SetChangeListener(Title, nil) view.SetChangeListener(Icon, nil)
view.SetChangeListener(Icon, nil) view.SetChangeListener(TabCloseButton, nil)
view.SetChangeListener(TabCloseButton, nil)
current := tabsLayout.currentItem(0) if newCurrent != oldCurrent {
if index < current || (index == current && current > 0) { tabsLayout.setRaw(Current, newCurrent)
current-- tabsLayout.currentChanged()
}
} }
return nil
if len(tabsLayout.views) == 1 { /*
tabsLayout.views = []View{} if tabsLayout.views == nil {
current = -1 tabsLayout.views = []View{}
} else if index == 0 { return nil
tabsLayout.views = tabsLayout.views[1:] }
} else if index == count-1 {
tabsLayout.views = tabsLayout.views[:index]
} else {
tabsLayout.views = append(tabsLayout.views[:index], tabsLayout.views[index+1:]...)
}
updateInnerHTML(tabsLayout.parentHTMLID(), tabsLayout.session) count := len(tabsLayout.views)
tabsLayout.propertyChangedEvent(Content) if index < 0 || index >= count {
return nil
}
delete(tabsLayout.properties, Current) view := tabsLayout.views[index]
tabsLayout.set(Current, current) view.setParentID("")
return view view.SetChangeListener(Title, nil)
view.SetChangeListener(Icon, nil)
view.SetChangeListener(TabCloseButton, nil)
current := GetCurrent(tabsLayout)
if index < current || (index == current && current > 0) {
current--
}
if len(tabsLayout.views) == 1 {
tabsLayout.views = []View{}
current = -1
} else if index == 0 {
tabsLayout.views = tabsLayout.views[1:]
} else if index == count-1 {
tabsLayout.views = tabsLayout.views[:index]
} else {
tabsLayout.views = append(tabsLayout.views[:index], tabsLayout.views[index+1:]...)
}
updateInnerHTML(tabsLayout.parentHTMLID(), tabsLayout.session)
tabsLayout.propertyChangedEvent(Content)
delete(tabsLayout.view, Current)
tabsLayout.Set(Current, current)
return view
*/
} }
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) { func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
tabsLayout.viewsContainerData.htmlProperties(self, buffer) tabsLayout.viewsContainerData.htmlProperties(self, buffer)
buffer.WriteString(` data-inactiveTabStyle="`) buffer.WriteString(` data-inactiveTabStyle="`)
buffer.WriteString(tabsLayout.inactiveTabStyle()) buffer.WriteString(tabsLayoutInactiveTabStyle(tabsLayout))
buffer.WriteString(`" data-activeTabStyle="`) buffer.WriteString(`" data-activeTabStyle="`)
buffer.WriteString(tabsLayout.activeTabStyle()) buffer.WriteString(tabsLayoutActiveTabStyle(tabsLayout))
buffer.WriteString(`" data-current="`) buffer.WriteString(`" data-current="`)
buffer.WriteString(strconv.Itoa(tabsLayout.currentItem(0))) buffer.WriteString(strconv.Itoa(GetCurrent(tabsLayout)))
buffer.WriteRune('"') buffer.WriteRune('"')
} }
@ -763,8 +680,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
return return
} }
//viewCount := len(tabsLayout.views) current := GetCurrent(tabsLayout)
current := tabsLayout.currentItem(0)
location := tabsLayout.tabsLocation() location := tabsLayout.tabsLocation()
tabsLayoutID := tabsLayout.htmlID() tabsLayoutID := tabsLayout.htmlID()
@ -796,8 +712,8 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
buffer.WriteString(`">`) buffer.WriteString(`">`)
inactiveStyle := tabsLayout.inactiveTabStyle() inactiveStyle := tabsLayoutInactiveTabStyle(tabsLayout)
activeStyle := tabsLayout.activeTabStyle() activeStyle := tabsLayoutActiveTabStyle(tabsLayout)
notTranslate := GetNotTranslate(tabsLayout) notTranslate := GetNotTranslate(tabsLayout)
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session) closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
@ -947,18 +863,18 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
} }
} }
func (tabsLayout *tabsLayoutData) handleCommand(self View, command string, data DataObject) bool { func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "tabClick": case "tabClick":
if numberText, ok := data.PropertyValue("number"); ok { if numberText, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil { if number, err := strconv.Atoi(numberText); err == nil {
current := tabsLayout.currentItem(0) current := GetCurrent(tabsLayout)
if current != number { if current != number {
tabsLayout.properties[Current] = number tabsLayout.setRaw(Current, number)
for _, listener := range tabsLayout.tabListener { for _, listener := range getEventWithOldListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent) {
listener(tabsLayout, number, current) listener(tabsLayout, number, current)
} }
tabsLayout.propertyChangedEvent(Current) tabsLayout.currentChanged()
} }
} }
} }
@ -967,7 +883,7 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command string, data
case "tabCloseClick": case "tabCloseClick":
if numberText, ok := data.PropertyValue("number"); ok { if numberText, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil { if number, err := strconv.Atoi(numberText); err == nil {
for _, listener := range tabsLayout.tabCloseListener { for _, listener := range getEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
listener(tabsLayout, number) listener(tabsLayout, number)
} }
} }

View File

@ -23,116 +23,79 @@ func NewTextView(session Session, params Params) TextView {
} }
func newTextView(session Session) View { func newTextView(session Session) View {
return NewTextView(session, nil) return new(textViewData)
} }
// Init initialize fields of TextView by default values // Init initialize fields of TextView by default values
func (textView *textViewData) init(session Session) { func (textView *textViewData) init(session Session) {
textView.viewData.init(session) textView.viewData.init(session)
textView.tag = "TextView" textView.tag = "TextView"
textView.set = textViewSet
textView.changed = textViewPropertyChanged
} }
func (textView *textViewData) String() string { func textViewPropertyChanged(view View, tag PropertyName) {
return getViewString(textView, nil) switch tag {
} case Text:
updateInnerHTML(view.htmlID(), view.Session())
func (textView *textViewData) Get(tag string) any { case TextOverflow:
return textView.get(strings.ToLower(tag)) session := view.Session()
} if n, ok := enumProperty(view, TextOverflow, session, 0); ok {
values := enumProperties[TextOverflow].cssValues
func (textView *textViewData) Remove(tag string) { if n >= 0 && n < len(values) {
textView.remove(strings.ToLower(tag)) session.updateCSSProperty(view.htmlID(), string(TextOverflow), values[n])
} return
}
func (textView *textViewData) remove(tag string) {
textView.viewData.remove(tag)
if textView.created {
switch tag {
case Text:
updateInnerHTML(textView.htmlID(), textView.session)
case TextOverflow:
textView.textOverflowUpdated()
} }
session.updateCSSProperty(view.htmlID(), string(TextOverflow), "")
case NotTranslate:
updateInnerHTML(view.htmlID(), view.Session())
default:
viewPropertyChanged(view, tag)
} }
} }
func (textView *textViewData) Set(tag string, value any) bool { func textViewSet(view View, tag PropertyName, value any) []PropertyName {
return textView.set(strings.ToLower(tag), value)
}
func (textView *textViewData) set(tag string, value any) bool {
switch tag { switch tag {
case Text: case Text:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
textView.properties[Text] = value view.setRaw(Text, value)
case fmt.Stringer: case fmt.Stringer:
textView.properties[Text] = value.String() view.setRaw(Text, value.String())
case float32: case float32:
textView.properties[Text] = fmt.Sprintf("%g", float64(value)) view.setRaw(Text, fmt.Sprintf("%g", float64(value)))
case float64: case float64:
textView.properties[Text] = fmt.Sprintf("%g", value) view.setRaw(Text, fmt.Sprintf("%g", value))
case []rune: case []rune:
textView.properties[Text] = string(value) view.setRaw(Text, string(value))
case bool: case bool:
if value { if value {
textView.properties[Text] = "true" view.setRaw(Text, "true")
} else { } else {
textView.properties[Text] = "false" view.setRaw(Text, "false")
} }
default: default:
if n, ok := isInt(value); ok { if n, ok := isInt(value); ok {
textView.properties[Text] = fmt.Sprintf("%d", n) view.setRaw(Text, fmt.Sprintf("%d", n))
} else { } else {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
} }
if textView.created { return []PropertyName{Text}
updateInnerHTML(textView.htmlID(), textView.session)
}
case TextOverflow:
if !textView.viewData.set(tag, value) {
return false
}
if textView.created {
textView.textOverflowUpdated()
}
case NotTranslate:
if !textView.viewData.set(tag, value) {
return false
}
if textView.created {
updateInnerHTML(textView.htmlID(), textView.Session())
}
default:
return textView.viewData.set(tag, value)
} }
textView.propertyChangedEvent(tag) return viewSet(view, tag, value)
return true
}
func (textView *textViewData) textOverflowUpdated() {
session := textView.Session()
if n, ok := enumProperty(textView, TextOverflow, session, 0); ok {
values := enumProperties[TextOverflow].cssValues
if n >= 0 && n < len(values) {
session.updateCSSProperty(textView.htmlID(), TextOverflow, values[n])
return
}
}
session.updateCSSProperty(textView.htmlID(), TextOverflow, "")
} }
func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) { func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) {

View File

@ -699,13 +699,13 @@ func (theme *theme) addText(themeText string) bool {
if node := obj.Property(i); node != nil { if node := obj.Property(i); node != nil {
switch node.Type() { switch node.Type() {
case ArrayNode: case ArrayNode:
params[node.Tag()] = node.ArrayElements() params[PropertyName(node.Tag())] = node.ArrayElements()
case ObjectNode: case ObjectNode:
params[node.Tag()] = node.Object() params[PropertyName(node.Tag())] = node.Object()
default: default:
params[node.Tag()] = node.Text() params[PropertyName(node.Tag())] = node.Text()
} }
} }
} }

View File

@ -27,7 +27,7 @@ const (
// `func(newTime time.Time)`, // `func(newTime time.Time)`,
// `func(picker rui.TimePicker)`, // `func(picker rui.TimePicker)`,
// `func()`. // `func()`.
TimeChangedEvent = "time-changed" TimeChangedEvent PropertyName = "time-changed"
// TimePickerMin is the constant for "time-picker-min" property tag. // TimePickerMin is the constant for "time-picker-min" property tag.
// //
@ -44,7 +44,7 @@ const (
// "HH:MM:SS PM" - "08:15:00 AM". // "HH:MM:SS PM" - "08:15:00 AM".
// "HH:MM" - "08:15". // "HH:MM" - "08:15".
// "HH:MM PM" - "08:15 AM". // "HH:MM PM" - "08:15 AM".
TimePickerMin = "time-picker-min" TimePickerMin PropertyName = "time-picker-min"
// TimePickerMax is the constant for "time-picker-max" property tag. // TimePickerMax is the constant for "time-picker-max" property tag.
// //
@ -61,7 +61,7 @@ const (
// "HH:MM:SS PM" - "08:15:00 AM". // "HH:MM:SS PM" - "08:15:00 AM".
// "HH:MM" - "08:15". // "HH:MM" - "08:15".
// "HH:MM PM" - "08:15 AM". // "HH:MM PM" - "08:15 AM".
TimePickerMax = "time-picker-max" TimePickerMax PropertyName = "time-picker-max"
// TimePickerStep is the constant for "time-picker-step" property tag. // TimePickerStep is the constant for "time-picker-step" property tag.
// //
@ -72,7 +72,7 @@ const (
// //
// Values: // Values:
// >= `0` or >= "0" - Step value in seconds used to increment or decrement time. // >= `0` or >= "0" - Step value in seconds used to increment or decrement time.
TimePickerStep = "time-picker-step" TimePickerStep PropertyName = "time-picker-step"
// TimePickerValue is the constant for "time-picker-value" property tag. // TimePickerValue is the constant for "time-picker-value" property tag.
// //
@ -89,7 +89,7 @@ const (
// "HH:MM:SS PM" - "08:15:00 AM". // "HH:MM:SS PM" - "08:15:00 AM".
// "HH:MM" - "08:15". // "HH:MM" - "08:15".
// "HH:MM PM" - "08:15 AM". // "HH:MM PM" - "08:15 AM".
TimePickerValue = "time-picker-value" TimePickerValue PropertyName = "time-picker-value"
timeFormat = "15:04:05" timeFormat = "15:04:05"
) )
@ -101,8 +101,6 @@ type TimePicker interface {
type timePickerData struct { type timePickerData struct {
viewData viewData
dataList
timeChangedListeners []func(TimePicker, time.Time, time.Time)
} }
// NewTimePicker create new TimePicker object and return it // NewTimePicker create new TimePicker object and return it
@ -114,236 +112,154 @@ func NewTimePicker(session Session, params Params) TimePicker {
} }
func newTimePicker(session Session) View { func newTimePicker(session Session) View {
return NewTimePicker(session, nil) return new(timePickerData)
} }
func (picker *timePickerData) init(session Session) { func (picker *timePickerData) init(session Session) {
picker.viewData.init(session) picker.viewData.init(session)
picker.tag = "TimePicker" picker.tag = "TimePicker"
picker.hasHtmlDisabled = true picker.hasHtmlDisabled = true
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){} picker.normalize = normalizeTimePickerTag
picker.dataListInit() picker.set = timePickerSet
} picker.changed = timePickerPropertyChanged
func (picker *timePickerData) String() string {
return getViewString(picker, nil)
} }
func (picker *timePickerData) Focusable() bool { func (picker *timePickerData) Focusable() bool {
return true return true
} }
func (picker *timePickerData) normalizeTag(tag string) string { func normalizeTimePickerTag(tag PropertyName) PropertyName {
tag = strings.ToLower(tag) tag = defaultNormalize(tag)
switch tag { switch tag {
case Type, Min, Max, Step, Value: case Type, Min, Max, Step, Value:
return "time-picker-" + tag return "time-picker-" + tag
} }
return tag return normalizeDataListTag(tag)
} }
func (picker *timePickerData) Remove(tag string) { func stringToTime(value string) (time.Time, bool) {
picker.remove(picker.normalizeTag(tag)) lowText := strings.ToUpper(value)
} pm := strings.HasSuffix(lowText, "PM") || strings.HasSuffix(lowText, "AM")
func (picker *timePickerData) remove(tag string) { var format string
switch tag { switch len(strings.Split(value, ":")) {
case TimeChangedEvent: case 2:
if len(picker.timeChangedListeners) > 0 { if pm {
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){} format = "3:04 PM"
picker.propertyChangedEvent(tag)
}
return
case TimePickerMin:
delete(picker.properties, TimePickerMin)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Min)
}
case TimePickerMax:
delete(picker.properties, TimePickerMax)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Max)
}
case TimePickerStep:
delete(picker.properties, TimePickerStep)
if picker.created {
picker.session.removeProperty(picker.htmlID(), Step)
}
case TimePickerValue:
if _, ok := picker.properties[TimePickerValue]; ok {
oldTime := GetTimePickerValue(picker)
delete(picker.properties, TimePickerValue)
time := GetTimePickerValue(picker)
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
}
for _, listener := range picker.timeChangedListeners {
listener(picker, time, oldTime)
}
} else { } else {
return format = "15:04"
}
case DataList:
if len(picker.dataList.dataList) > 0 {
picker.setDataList(picker, []string{}, true)
} }
default: default:
picker.viewData.remove(tag) if pm {
return format = "03:04:05 PM"
} } else {
picker.propertyChangedEvent(tag) format = "15:04:05"
} }
func (picker *timePickerData) Set(tag string, value any) bool {
return picker.set(picker.normalizeTag(tag), value)
}
func (picker *timePickerData) set(tag string, value any) bool {
if value == nil {
picker.remove(tag)
return true
} }
setTimeValue := func(tag string) (time.Time, bool) { result, err := time.Parse(format, value)
if err != nil {
ErrorLog(err.Error())
return time.Now(), false
}
return result, true
}
func timePickerSet(view View, tag PropertyName, value any) []PropertyName {
setTimeValue := func(tag PropertyName) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case time.Time: case time.Time:
picker.properties[tag] = value view.setRaw(tag, value)
return value, true return []PropertyName{tag}
case string: case string:
if text, ok := picker.Session().resolveConstants(value); ok { if isConstantName(value) {
lowText := strings.ToLower(text) view.setRaw(tag, value)
pm := strings.HasSuffix(lowText, "pm") || strings.HasSuffix(lowText, "am") return []PropertyName{tag}
}
var format string if time, ok := stringToTime(value); ok {
switch len(strings.Split(text, ":")) { view.setRaw(tag, time)
case 2: return []PropertyName{tag}
if pm {
format = "3:04 PM"
} else {
format = "15:04"
}
default:
if pm {
format = "03:04:05 PM"
} else {
format = "15:04:05"
}
}
if time, err := time.Parse(format, text); err == nil {
picker.properties[tag] = value
return time, true
} else {
ErrorLog(err.Error())
}
return time.Now(), false
} }
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return time.Now(), false return nil
} }
switch tag { switch tag {
case TimePickerMin: case TimePickerMin:
old, oldOK := getTimeProperty(picker, TimePickerMin, Min) return setTimeValue(TimePickerMin)
if time, ok := setTimeValue(TimePickerMin); ok {
if !oldOK || time != old { case TimePickerMax:
if picker.created { return setTimeValue(TimePickerMax)
picker.session.updateProperty(picker.htmlID(), Min, time.Format(timeFormat))
} case TimePickerStep:
picker.propertyChangedEvent(tag) return setIntProperty(view, TimePickerStep, value)
}
return true case TimePickerValue:
view.setRaw("old-time", GetTimePickerValue(view))
return setTimeValue(tag)
case TimeChangedEvent:
return setEventWithOldListener[TimePicker, time.Time](view, tag, value)
case DataList:
return setDataList(view, value, timeFormat)
}
return viewSet(view, tag, value)
}
func timePickerPropertyChanged(view View, tag PropertyName) {
session := view.Session()
switch tag {
case TimePickerMin:
if time, ok := GetTimePickerMin(view); ok {
session.updateProperty(view.htmlID(), "min", time.Format(timeFormat))
} else {
session.removeProperty(view.htmlID(), "min")
} }
case TimePickerMax: case TimePickerMax:
old, oldOK := getTimeProperty(picker, TimePickerMax, Max) if time, ok := GetTimePickerMax(view); ok {
if time, ok := setTimeValue(TimePickerMax); ok { session.updateProperty(view.htmlID(), "max", time.Format(timeFormat))
if !oldOK || time != old { } else {
if picker.created { session.removeProperty(view.htmlID(), "max")
picker.session.updateProperty(picker.htmlID(), Max, time.Format(timeFormat))
}
picker.propertyChangedEvent(tag)
}
return true
} }
case TimePickerStep: case TimePickerStep:
oldStep := GetTimePickerStep(picker) if step := GetTimePickerStep(view); step > 0 {
if picker.setIntProperty(TimePickerStep, value) { session.updateProperty(view.htmlID(), "step", strconv.Itoa(step))
if step := GetTimePickerStep(picker); oldStep != step { } else {
if picker.created { session.removeProperty(view.htmlID(), "step")
if step > 0 {
picker.session.updateProperty(picker.htmlID(), Step, strconv.Itoa(step))
} else {
picker.session.removeProperty(picker.htmlID(), Step)
}
}
picker.propertyChangedEvent(tag)
}
return true
} }
case TimePickerValue: case TimePickerValue:
oldTime := GetTimePickerValue(picker) value := GetTimePickerValue(view)
if time, ok := setTimeValue(TimePickerValue); ok { session.callFunc("setInputValue", view.htmlID(), value.Format(timeFormat))
if time != oldTime {
if picker.created { if listeners := GetTimeChangedListeners(view); len(listeners) > 0 {
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat)) oldTime := time.Now()
if val := view.getRaw("old-time"); val != nil {
if time, ok := val.(time.Time); ok {
oldTime = time
} }
for _, listener := range picker.timeChangedListeners {
listener(picker, time, oldTime)
}
picker.propertyChangedEvent(tag)
} }
return true for _, listener := range listeners {
listener(view, value, oldTime)
}
} }
case TimeChangedEvent:
listeners, ok := valueToEventWithOldListeners[TimePicker, time.Time](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(TimePicker, time.Time, time.Time){}
}
picker.timeChangedListeners = listeners
picker.propertyChangedEvent(tag)
return true
case DataList:
return picker.setDataList(picker, value, picker.created)
default: default:
return picker.viewData.set(tag, value) viewPropertyChanged(view, tag)
}
return false
}
func (picker *timePickerData) Get(tag string) any {
return picker.get(picker.normalizeTag(tag))
}
func (picker *timePickerData) get(tag string) any {
switch tag {
case TimeChangedEvent:
return picker.timeChangedListeners
case DataList:
return picker.dataList.dataList
default:
return picker.viewData.get(tag)
} }
} }
@ -352,7 +268,13 @@ func (picker *timePickerData) htmlTag() string {
} }
func (picker *timePickerData) htmlSubviews(self View, buffer *strings.Builder) { func (picker *timePickerData) htmlSubviews(self View, buffer *strings.Builder) {
picker.dataListHtmlSubviews(self, buffer) dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
text, _ = session.resolveConstants(text)
if time, ok := stringToTime(text); ok {
return time.Format(timeFormat)
}
return text
})
} }
func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) { func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) {
@ -387,10 +309,10 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder)
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`) buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
} }
picker.dataListHtmlProperties(picker, buffer) dataListHtmlProperties(picker, buffer)
} }
func (picker *timePickerData) handleCommand(self View, command string, data DataObject) bool { func (picker *timePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyValue("text"); ok {
@ -398,9 +320,13 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
oldValue := GetTimePickerValue(picker) oldValue := GetTimePickerValue(picker)
picker.properties[TimePickerValue] = value picker.properties[TimePickerValue] = value
if value != oldValue { if value != oldValue {
for _, listener := range picker.timeChangedListeners { for _, listener := range GetTimeChangedListeners(picker) {
listener(picker, value, oldValue) listener(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[TimePickerValue]; ok {
listener(picker, TimePickerValue)
}
} }
} }
} }
@ -410,7 +336,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
return picker.viewData.handleCommand(self, command, data) return picker.viewData.handleCommand(self, command, data)
} }
func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) { func getTimeProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool) {
valueToTime := func(value any) (time.Time, bool) { valueToTime := func(value any) (time.Time, bool) {
if value != nil { if value != nil {
switch value := value.(type) { switch value := value.(type) {
@ -419,7 +345,7 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
case string: case string:
if text, ok := view.Session().resolveConstants(value); ok { if text, ok := view.Session().resolveConstants(value); ok {
if result, err := time.Parse(timeFormat, text); err == nil { if result, ok := stringToTime(text); ok {
return result, true return result, true
} }
} }
@ -433,9 +359,11 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
return result, true return result, true
} }
if value := valueFromStyle(view, shortTag); value != nil { for _, tag := range []PropertyName{mainTag, shortTag} {
if result, ok := valueToTime(value); ok { if value := valueFromStyle(view, tag); value != nil {
return result, true if result, ok := valueToTime(value); ok {
return result, true
}
} }
} }
} }

View File

@ -2,7 +2,6 @@ package rui
import ( import (
"strconv" "strconv"
"strings"
) )
// Constants which represent [View] specific touch events properties // Constants which represent [View] specific touch events properties
@ -23,7 +22,7 @@ const (
// `func(event rui.TouchEvent)`, // `func(event rui.TouchEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
TouchStart = "touch-start" TouchStart PropertyName = "touch-start"
// TouchEnd is the constant for "touch-end" property tag. // TouchEnd is the constant for "touch-end" property tag.
// //
@ -41,7 +40,7 @@ const (
// `func(event rui.TouchEvent)`, // `func(event rui.TouchEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
TouchEnd = "touch-end" TouchEnd PropertyName = "touch-end"
// TouchMove is the constant for "touch-move" property tag. // TouchMove is the constant for "touch-move" property tag.
// //
@ -59,7 +58,7 @@ const (
// `func(event rui.TouchEvent)`, // `func(event rui.TouchEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
TouchMove = "touch-move" TouchMove PropertyName = "touch-move"
// TouchCancel is the constant for "touch-cancel" property tag. // TouchCancel is the constant for "touch-cancel" property tag.
// //
@ -78,7 +77,7 @@ const (
// `func(event rui.TouchEvent)`, // `func(event rui.TouchEvent)`,
// `func(view rui.View)`, // `func(view rui.View)`,
// `func()`. // `func()`.
TouchCancel = "touch-cancel" TouchCancel PropertyName = "touch-cancel"
) )
// Touch contains parameters of a single touch of a touch event // Touch contains parameters of a single touch of a touch event
@ -143,54 +142,44 @@ type TouchEvent struct {
MetaKey bool MetaKey bool
} }
var touchEvents = map[string]struct{ jsEvent, jsFunc string }{ /*
TouchStart: {jsEvent: "ontouchstart", jsFunc: "touchStartEvent"}, func setTouchListener(properties Properties, tag PropertyName, value any) bool {
TouchEnd: {jsEvent: "ontouchend", jsFunc: "touchEndEvent"}, if listeners, ok := valueToEventListeners[View, TouchEvent](value); ok {
TouchMove: {jsEvent: "ontouchmove", jsFunc: "touchMoveEvent"}, if len(listeners) == 0 {
TouchCancel: {jsEvent: "ontouchcancel", jsFunc: "touchCancelEvent"}, properties.setRaw(tag, nil)
} } else {
properties.setRaw(tag, listeners)
func (view *viewData) setTouchListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, TouchEvent](value)
if !ok {
notCompatibleType(tag, value)
return false
}
if listeners == nil {
view.removeTouchListener(tag)
} else if js, ok := touchEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
} }
} else { return true
return false
} }
return true notCompatibleType(tag, value)
return false
} }
func (view *viewData) removeTouchListener(tag string) { func (view *viewData) removeTouchListener(tag PropertyName) {
delete(view.properties, tag) delete(view.properties, tag)
if view.created { if view.created {
if js, ok := touchEvents[tag]; ok { if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent) view.session.removeProperty(view.htmlID(), js.jsEvent)
} }
} }
} }
func touchEventsHtml(view View, buffer *strings.Builder) { func touchEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range touchEvents { for _, tag := range []PropertyName{TouchStart, TouchEnd, TouchMove, TouchCancel} {
if value := view.getRaw(tag); value != nil { if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 { if js, ok := eventJsFunc[tag]; ok {
buffer.WriteString(js.jsEvent) if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
buffer.WriteString(`="`) buffer.WriteString(js.jsEvent)
buffer.WriteString(js.jsFunc) buffer.WriteString(`="`)
buffer.WriteString(`(this, event)" `) buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
} }
} }
} }
} }
*/
func (event *TouchEvent) init(data DataObject) { func (event *TouchEvent) init(data DataObject) {
@ -225,7 +214,7 @@ func (event *TouchEvent) init(data DataObject) {
event.MetaKey = dataBoolProperty(data, "metaKey") event.MetaKey = dataBoolProperty(data, "metaKey")
} }
func handleTouchEvents(view View, tag string, data DataObject) { func handleTouchEvents(view View, tag PropertyName, data DataObject) {
listeners := getEventListeners[View, TouchEvent](view, nil, tag) listeners := getEventListeners[View, TouchEvent](view, nil, tag)
if len(listeners) == 0 { if len(listeners) == 0 {
return return

View File

@ -15,7 +15,7 @@ const (
// //
// Values: // Values:
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
VideoWidth = "video-width" VideoWidth PropertyName = "video-width"
// VideoHeight is the constant for "video-height" property tag. // VideoHeight is the constant for "video-height" property tag.
// //
@ -25,7 +25,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
VideoHeight = "video-height" VideoHeight PropertyName = "video-height"
// Poster is the constant for "poster" property tag. // Poster is the constant for "poster" property tag.
// //
@ -34,7 +34,7 @@ const (
// displayed until the first frame is available, then the first frame is shown as the poster frame. // displayed until the first frame is available, then the first frame is shown as the poster frame.
// //
// Supported types: `string`. // Supported types: `string`.
Poster = "poster" Poster PropertyName = "poster"
) )
// VideoPlayer is a type of a [View] which can play video files // VideoPlayer is a type of a [View] which can play video files
@ -50,92 +50,56 @@ type videoPlayerData struct {
func NewVideoPlayer(session Session, params Params) VideoPlayer { func NewVideoPlayer(session Session, params Params) VideoPlayer {
view := new(videoPlayerData) view := new(videoPlayerData)
view.init(session) view.init(session)
view.tag = "VideoPlayer"
setInitParams(view, params) setInitParams(view, params)
return view return view
} }
func newVideoPlayer(session Session) View { func newVideoPlayer(session Session) View {
return NewVideoPlayer(session, nil) return new(videoPlayerData) // NewVideoPlayer(session, nil)
} }
func (player *videoPlayerData) init(session Session) { func (player *videoPlayerData) init(session Session) {
player.mediaPlayerData.init(session) player.mediaPlayerData.init(session)
player.tag = "VideoPlayer" player.tag = "VideoPlayer"
} player.changed = videoPlayerPropertyChanged
func (player *videoPlayerData) String() string {
return getViewString(player, nil)
} }
func (player *videoPlayerData) htmlTag() string { func (player *videoPlayerData) htmlTag() string {
return "video" return "video"
} }
func (player *videoPlayerData) Remove(tag string) { func videoPlayerPropertyChanged(view View, tag PropertyName) {
player.remove(strings.ToLower(tag))
} session := view.Session()
updateSize := func(cssTag string) {
if size, ok := floatTextProperty(view, tag, session, 0); ok {
if size != "0" {
session.updateProperty(view.htmlID(), cssTag, size)
} else {
session.removeProperty(view.htmlID(), cssTag)
}
}
}
func (player *videoPlayerData) remove(tag string) {
switch tag { switch tag {
case VideoWidth: case VideoWidth:
delete(player.properties, tag) updateSize("width")
player.session.removeProperty(player.htmlID(), "width")
case VideoHeight: case VideoHeight:
delete(player.properties, tag) updateSize("height")
player.session.removeProperty(player.htmlID(), "height")
case Poster: case Poster:
delete(player.properties, tag) if url, ok := stringProperty(view, Poster, session); ok {
player.session.removeProperty(player.htmlID(), Poster) session.updateProperty(view.htmlID(), string(Poster), url)
} else {
session.removeProperty(view.htmlID(), string(Poster))
}
default: default:
player.mediaPlayerData.remove(tag) mediaPlayerPropertyChanged(view, tag)
} }
} }
func (player *videoPlayerData) Set(tag string, value any) bool {
return player.set(strings.ToLower(tag), value)
}
func (player *videoPlayerData) set(tag string, value any) bool {
if value == nil {
player.remove(tag)
return true
}
if player.mediaPlayerData.set(tag, value) {
session := player.Session()
updateSize := func(cssTag string) {
if size, ok := floatTextProperty(player, tag, session, 0); ok {
if size != "0" {
session.updateProperty(player.htmlID(), cssTag, size)
} else {
session.removeProperty(player.htmlID(), cssTag)
}
}
}
switch tag {
case VideoWidth:
updateSize("width")
case VideoHeight:
updateSize("height")
case Poster:
if url, ok := stringProperty(player, Poster, session); ok {
session.updateProperty(player.htmlID(), Poster, url)
}
}
return true
}
return false
}
func (player *videoPlayerData) htmlProperties(self View, buffer *strings.Builder) { func (player *videoPlayerData) htmlProperties(self View, buffer *strings.Builder) {
player.mediaPlayerData.htmlProperties(self, buffer) player.mediaPlayerData.htmlProperties(self, buffer)

608
view.go
View File

@ -62,15 +62,16 @@ type View interface {
// SetAnimated sets the value (second argument) of the property with name defined by the first argument. // 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 // 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 // a description of the error is written to the log
SetAnimated(tag string, value any, animation Animation) bool SetAnimated(tag PropertyName, value any, animation Animation) bool
// SetChangeListener set the function to track the change of the View property // SetChangeListener set the function to track the change of the View property
SetChangeListener(tag string, listener func(View, string)) SetChangeListener(tag PropertyName, listener func(View, PropertyName))
// HasFocus returns 'true' if the view has focus // HasFocus returns 'true' if the view has focus
HasFocus() bool HasFocus() bool
handleCommand(self View, command string, data DataObject) bool init(session Session)
handleCommand(self View, command PropertyName, data DataObject) bool
htmlClass(disabled bool) string htmlClass(disabled bool) string
htmlTag() string htmlTag() string
closeHTMLTag() bool closeHTMLTag() bool
@ -81,7 +82,8 @@ type View interface {
htmlProperties(self View, buffer *strings.Builder) htmlProperties(self View, buffer *strings.Builder)
cssStyle(self View, builder cssBuilder) cssStyle(self View, builder cssBuilder)
addToCSSStyle(addCSS map[string]string) addToCSSStyle(addCSS map[string]string)
exscludeTags() []string exscludeTags() []PropertyName
htmlDisabledProperty() bool
onResize(self View, x, y, width, height float64) onResize(self View, x, y, width, height float64)
onItemResize(self View, index string, x, y, width, height float64) onItemResize(self View, index string, x, y, width, height float64)
@ -99,8 +101,8 @@ type viewData struct {
_htmlID string _htmlID string
parentID string parentID string
systemClass string systemClass string
changeListener map[string]func(View, string) changeListener map[PropertyName]func(View, PropertyName)
singleTransition map[string]Animation singleTransition map[PropertyName]Animation
addCSS map[string]string addCSS map[string]string
frame Frame frame Frame
scroll Frame scroll Frame
@ -108,12 +110,21 @@ type viewData struct {
created bool created bool
hasFocus bool hasFocus bool
hasHtmlDisabled bool hasHtmlDisabled bool
//animation map[string]AnimationEndListener 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 { 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 := new(viewData)
view.init(session) view.init(session)
setInitParams(view, params)
return view return view
} }
@ -132,22 +143,18 @@ func setInitParams(view View, params Params) {
} }
} }
// 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 (view *viewData) init(session Session) { func (view *viewData) init(session Session) {
view.viewStyle.init() view.viewStyle.init()
view.getFunc = viewGet
view.set = viewSet
view.normalize = normalizeViewTag
view.changed = viewPropertyChanged
view.tag = "View" view.tag = "View"
view.session = session view.session = session
view.changeListener = map[string]func(View, string){} view.changeListener = map[PropertyName]func(View, PropertyName){}
view.addCSS = map[string]string{} view.addCSS = map[string]string{}
//view.animation = map[string]AnimationEndListener{} //view.animation = map[string]AnimationEndListener{}
view.singleTransition = map[string]Animation{} view.singleTransition = map[PropertyName]Animation{}
view.noResizeEvent = false view.noResizeEvent = false
view.created = false view.created = false
view.hasHtmlDisabled = false view.hasHtmlDisabled = false
@ -205,71 +212,112 @@ func (view *viewData) Focusable() bool {
return false return false
} }
func (view *viewData) Remove(tag string) { func (view *viewData) Remove(tag PropertyName) {
view.remove(strings.ToLower(tag)) tag = view.normalize(tag)
} var changedTags []PropertyName = nil
func (view *viewData) remove(tag string) {
switch tag { switch tag {
case ID: case ID:
view.viewID = "" if view.viewID != "" {
view.viewID = ""
case TabIndex, "tab-index": changedTags = []PropertyName{ID}
delete(view.properties, tag)
if view.Focusable() {
view.session.updateProperty(view.htmlID(), "tabindex", "0")
} else {
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
} }
case UserData: case AnimationTag:
delete(view.properties, tag) if val := view.getRaw(AnimationTag); val != nil {
if animations, ok := val.([]Animation); ok {
for _, animation := range animations {
animation.unused(view.session)
}
}
case Style, StyleDisabled: view.setRaw(AnimationTag, nil)
if _, ok := view.properties[tag]; ok { changedTags = []PropertyName{AnimationTag}
delete(view.properties, tag)
view.session.updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view)))
}
case FocusEvent, LostFocusEvent:
view.removeFocusListener(tag)
case KeyDownEvent, KeyUpEvent:
view.removeKeyListener(tag)
case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent:
view.removeMouseListener(tag)
case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel:
view.removePointerListener(tag)
case TouchStart, TouchEnd, TouchMove, TouchCancel:
view.removeTouchListener(tag)
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent:
view.removeTransitionListener(tag)
case AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent:
view.removeAnimationListener(tag)
case ResizeEvent, ScrollEvent:
delete(view.properties, tag)
case Content:
if _, ok := view.properties[Content]; ok {
delete(view.properties, Content)
updateInnerHTML(view.htmlID(), view.session)
} }
default: default:
view.viewStyle.remove(tag) changedTags = view.remove(view, tag)
viewPropertyChanged(view, tag)
} }
view.propertyChangedEvent(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) propertyChangedEvent(tag string) { 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 { if listener, ok := view.changeListener[tag]; ok {
listener(view, tag) listener(view, tag)
} }
@ -319,113 +367,58 @@ func (view *viewData) propertyChangedEvent(tag string) {
listener(view, tag) listener(view, tag)
} }
} }
*/
func (view *viewData) Set(tag string, value any) bool { func viewRemove(properties Properties, tag PropertyName) []PropertyName {
return view.set(strings.ToLower(tag), value) return viewStyleRemove(properties, tag)
} }
func (view *viewData) set(tag string, value any) bool { func viewSet(view View, tag PropertyName, value any) []PropertyName {
if value == nil {
view.remove(tag)
return true
}
result := func(res bool) bool {
if res {
view.propertyChangedEvent(tag)
}
return res
}
switch tag { switch tag {
case ID:
text, ok := value.(string)
if !ok {
notCompatibleType(ID, value)
return false
}
view.viewID = text
case AnimationTag:
oldAnimations := []Animation{}
if val, ok := view.properties[AnimationTag]; ok && val != nil {
if animation, ok := val.([]Animation); ok {
oldAnimations = animation
}
}
if !view.setAnimation(tag, value) {
return false
}
for _, animation := range oldAnimations {
animation.unused(view.session)
}
if view.created {
viewPropertyChanged(view, tag)
}
case TabIndex, "tab-index": case TabIndex, "tab-index":
if !view.setIntProperty(tag, value) { return setIntProperty(view, TabIndex, value)
return false
}
if value, ok := intProperty(view, TabIndex, view.Session(), 0); ok {
view.session.updateProperty(view.htmlID(), "tabindex", strconv.Itoa(value))
} else if view.Focusable() {
view.session.updateProperty(view.htmlID(), "tabindex", "0")
} else {
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
}
case UserData: case UserData:
view.properties[tag] = value view.setRaw(tag, value)
return []PropertyName{UserData}
case Style, StyleDisabled: case Style, StyleDisabled:
text, ok := value.(string) if text, ok := value.(string); ok {
if !ok { view.setRaw(tag, text)
notCompatibleType(ID, value) return []PropertyName{tag}
return false
}
view.properties[tag] = text
if view.created {
view.session.updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view)))
} }
notCompatibleType(ID, value)
return nil
case FocusEvent, LostFocusEvent: case FocusEvent, LostFocusEvent:
return result(view.setFocusListener(tag, value)) return setNoParamEventListener[View](view, tag, value)
case KeyDownEvent, KeyUpEvent: case KeyDownEvent, KeyUpEvent:
return result(view.setKeyListener(tag, value)) return setViewEventListener[View, KeyEvent](view, tag, value)
case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent: case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent:
return result(view.setMouseListener(tag, value)) return setViewEventListener[View, MouseEvent](view, tag, value)
case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel: case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel:
return result(view.setPointerListener(tag, value)) return setViewEventListener[View, PointerEvent](view, tag, value)
case TouchStart, TouchEnd, TouchMove, TouchCancel: case TouchStart, TouchEnd, TouchMove, TouchCancel:
return result(view.setTouchListener(tag, value)) return setViewEventListener[View, TouchEvent](view, tag, value)
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent: case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent,
return result(view.setTransitionListener(tag, value)) AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent:
return setViewEventListener[View, string](view, tag, value)
case AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent: //return setTransitionListener(view, tag, value), tag
return result(view.setAnimationListener(tag, value)) //return setAnimationListener(view, tag, value), tag
case ResizeEvent, ScrollEvent: case ResizeEvent, ScrollEvent:
return result(view.setFrameListener(tag, value)) return setViewEventListener[View, Frame](view, tag, value)
//return setFrameListener(view, tag, value), tag
default:
if !view.viewStyle.set(tag, value) {
return false
}
if view.created {
viewPropertyChanged(view, tag)
}
} }
view.propertyChangedEvent(tag) return viewStyleSet(view, tag, value)
return true
} }
func (view *viewData) SetParams(params Params) bool { func (view *viewData) SetParams(params Params) bool {
@ -446,15 +439,29 @@ func (view *viewData) SetParams(params Params) bool {
return result return result
} }
func viewPropertyChanged(view *viewData, tag string) { func viewPropertyChanged(view View, tag PropertyName) {
if view.updateTransformProperty(tag) { /*
return if view.updateTransformProperty(tag) {
} return
}
*/
htmlID := view.htmlID() htmlID := view.htmlID()
session := view.session session := view.Session()
switch tag { 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: case Disabled:
tabIndex := GetTabIndex(view, htmlID) tabIndex := GetTabIndex(view, htmlID)
enabledClass := view.htmlClass(false) enabledClass := view.htmlClass(false)
@ -462,7 +469,7 @@ func viewPropertyChanged(view *viewData, tag string) {
session.startUpdateScript(htmlID) session.startUpdateScript(htmlID)
if IsDisabled(view) { if IsDisabled(view) {
session.updateProperty(htmlID, "data-disabled", "1") session.updateProperty(htmlID, "data-disabled", "1")
if view.hasHtmlDisabled { if view.htmlDisabledProperty() {
session.updateProperty(htmlID, "disabled", true) session.updateProperty(htmlID, "disabled", true)
} }
if tabIndex >= 0 { if tabIndex >= 0 {
@ -473,7 +480,7 @@ func viewPropertyChanged(view *viewData, tag string) {
} }
} else { } else {
session.updateProperty(htmlID, "data-disabled", "0") session.updateProperty(htmlID, "data-disabled", "0")
if view.hasHtmlDisabled { if view.htmlDisabledProperty() {
session.removeProperty(htmlID, "disabled") session.removeProperty(htmlID, "disabled")
} }
if tabIndex >= 0 { if tabIndex >= 0 {
@ -485,82 +492,65 @@ func viewPropertyChanged(view *viewData, tag string) {
} }
session.finishUpdateScript(htmlID) session.finishUpdateScript(htmlID)
updateInnerHTML(htmlID, session) updateInnerHTML(htmlID, session)
return
case Visibility: case Visibility:
switch GetVisibility(view) { switch GetVisibility(view) {
case Invisible: case Invisible:
session.updateCSSProperty(htmlID, Visibility, "hidden") session.updateCSSProperty(htmlID, string(Visibility), "hidden")
session.updateCSSProperty(htmlID, "display", "") session.updateCSSProperty(htmlID, "display", "")
session.callFunc("hideTooltip") session.callFunc("hideTooltip")
case Gone: case Gone:
session.updateCSSProperty(htmlID, Visibility, "hidden") session.updateCSSProperty(htmlID, string(Visibility), "hidden")
session.updateCSSProperty(htmlID, "display", "none") session.updateCSSProperty(htmlID, "display", "none")
session.callFunc("hideTooltip") session.callFunc("hideTooltip")
default: default:
session.updateCSSProperty(htmlID, Visibility, "visible") session.updateCSSProperty(htmlID, string(Visibility), "visible")
session.updateCSSProperty(htmlID, "display", "") session.updateCSSProperty(htmlID, "display", "")
} }
return
case Background: case Background:
session.updateCSSProperty(htmlID, Background, view.backgroundCSS(session)) session.updateCSSProperty(htmlID, string(Background), backgroundCSS(view, session))
return
case Border: case Border, BorderLeft, BorderRight, BorderTop, BorderBottom:
if getBorder(view, Border) == nil { cssWidth := ""
if session.startUpdateScript(htmlID) { cssColor := ""
defer session.finishUpdateScript(htmlID) cssStyle := "none"
}
session.updateCSSProperty(htmlID, BorderWidth, "")
session.updateCSSProperty(htmlID, BorderColor, "")
session.updateCSSProperty(htmlID, BorderStyle, "none")
return
}
fallthrough
case BorderLeft, BorderRight, BorderTop, BorderBottom: if border := getBorderProperty(view, Border); border != nil {
if border := getBorder(view, Border); border != nil { cssWidth = border.cssWidthValue(session)
if session.startUpdateScript(htmlID) { cssColor = border.cssColorValue(session)
defer session.finishUpdateScript(htmlID) cssStyle = border.cssStyleValue(session)
}
session.updateCSSProperty(htmlID, BorderWidth, border.cssWidthValue(session))
session.updateCSSProperty(htmlID, BorderColor, border.cssColorValue(session))
session.updateCSSProperty(htmlID, BorderStyle, border.cssStyleValue(session))
} }
return
session.updateCSSProperty(htmlID, string(BorderWidth), cssWidth)
session.updateCSSProperty(htmlID, string(BorderColor), cssColor)
session.updateCSSProperty(htmlID, string(BorderStyle), cssStyle)
case BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle: case BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle:
if border := getBorder(view, Border); border != nil { if border := getBorderProperty(view, Border); border != nil {
session.updateCSSProperty(htmlID, BorderStyle, border.cssStyleValue(session)) session.updateCSSProperty(htmlID, string(BorderStyle), border.cssStyleValue(session))
} }
return
case BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor: case BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor:
if border := getBorder(view, Border); border != nil { if border := getBorderProperty(view, Border); border != nil {
session.updateCSSProperty(htmlID, BorderColor, border.cssColorValue(session)) session.updateCSSProperty(htmlID, string(BorderColor), border.cssColorValue(session))
} }
return
case BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth: case BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth:
if border := getBorder(view, Border); border != nil { if border := getBorderProperty(view, Border); border != nil {
session.updateCSSProperty(htmlID, BorderWidth, border.cssWidthValue(session)) session.updateCSSProperty(htmlID, string(BorderWidth), border.cssWidthValue(session))
} }
return
case Outline, OutlineColor, OutlineStyle, OutlineWidth: case Outline, OutlineColor, OutlineStyle, OutlineWidth:
session.updateCSSProperty(htmlID, Outline, GetOutline(view).cssString(session)) session.updateCSSProperty(htmlID, string(Outline), GetOutline(view).cssString(session))
return
case Shadow: case Shadow:
session.updateCSSProperty(htmlID, "box-shadow", shadowCSS(view, Shadow, session)) session.updateCSSProperty(htmlID, "box-shadow", shadowCSS(view, Shadow, session))
return
case TextShadow: case TextShadow:
session.updateCSSProperty(htmlID, "text-shadow", shadowCSS(view, TextShadow, session)) session.updateCSSProperty(htmlID, "text-shadow", shadowCSS(view, TextShadow, session))
return
case Radius, RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY, case Radius, RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
@ -568,19 +558,16 @@ func viewPropertyChanged(view *viewData, tag string) {
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
radius := GetRadius(view) radius := GetRadius(view)
session.updateCSSProperty(htmlID, "border-radius", radius.cssString(session)) session.updateCSSProperty(htmlID, "border-radius", radius.cssString(session))
return
case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft, case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft,
"top-margin", "right-margin", "bottom-margin", "left-margin": "top-margin", "right-margin", "bottom-margin", "left-margin":
margin := GetMargin(view) margin := GetMargin(view)
session.updateCSSProperty(htmlID, Margin, margin.cssString(session)) session.updateCSSProperty(htmlID, string(Margin), margin.cssString(session))
return
case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
"top-padding", "right-padding", "bottom-padding", "left-padding": "top-padding", "right-padding", "bottom-padding", "left-padding":
padding := GetPadding(view) padding := GetPadding(view)
session.updateCSSProperty(htmlID, Padding, padding.cssString(session)) session.updateCSSProperty(htmlID, string(Padding), padding.cssString(session))
return
case AvoidBreak: case AvoidBreak:
if avoid, ok := boolProperty(view, AvoidBreak, session); ok { if avoid, ok := boolProperty(view, AvoidBreak, session); ok {
@ -590,7 +577,6 @@ func viewPropertyChanged(view *viewData, tag string) {
session.updateCSSProperty(htmlID, "break-inside", "auto") session.updateCSSProperty(htmlID, "break-inside", "auto")
} }
} }
return
case Clip: case Clip:
if clip := getClipShape(view, Clip, session); clip != nil && clip.valid(session) { if clip := getClipShape(view, Clip, session); clip != nil && clip.valid(session) {
@ -598,29 +584,26 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, `clip-path`, "none") session.updateCSSProperty(htmlID, `clip-path`, "none")
} }
return
case ShapeOutside: case ShapeOutside:
if clip := getClipShape(view, ShapeOutside, session); clip != nil && clip.valid(session) { if clip := getClipShape(view, ShapeOutside, session); clip != nil && clip.valid(session) {
session.updateCSSProperty(htmlID, ShapeOutside, clip.cssStyle(session)) session.updateCSSProperty(htmlID, string(ShapeOutside), clip.cssStyle(session))
} else { } else {
session.updateCSSProperty(htmlID, ShapeOutside, "none") session.updateCSSProperty(htmlID, string(ShapeOutside), "none")
} }
return
case Filter: case Filter:
text := "" text := ""
if value := view.getRaw(tag); value != nil { if value := view.getRaw(Filter); value != nil {
if filter, ok := value.(ViewFilter); ok { if filter, ok := value.(ViewFilter); ok {
text = filter.cssStyle(session) text = filter.cssStyle(session)
} }
} }
session.updateCSSProperty(htmlID, tag, text) session.updateCSSProperty(htmlID, string(Filter), text)
return
case BackdropFilter: case BackdropFilter:
text := "" text := ""
if value := view.getRaw(tag); value != nil { if value := view.getRaw(BackdropFilter); value != nil {
if filter, ok := value.(ViewFilter); ok { if filter, ok := value.(ViewFilter); ok {
text = filter.cssStyle(session) text = filter.cssStyle(session)
} }
@ -629,8 +612,7 @@ func viewPropertyChanged(view *viewData, tag string) {
defer session.finishUpdateScript(htmlID) defer session.finishUpdateScript(htmlID)
} }
session.updateCSSProperty(htmlID, "-webkit-backdrop-filter", text) session.updateCSSProperty(htmlID, "-webkit-backdrop-filter", text)
session.updateCSSProperty(htmlID, tag, text) session.updateCSSProperty(htmlID, string(BackdropFilter), text)
return
case FontName: case FontName:
if font, ok := stringProperty(view, FontName, session); ok { if font, ok := stringProperty(view, FontName, session); ok {
@ -638,7 +620,6 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, "font-family", "") session.updateCSSProperty(htmlID, "font-family", "")
} }
return
case Italic: case Italic:
if state, ok := boolProperty(view, tag, session); ok { if state, ok := boolProperty(view, tag, session); ok {
@ -650,7 +631,6 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, "font-style", "") session.updateCSSProperty(htmlID, "font-style", "")
} }
return
case SmallCaps: case SmallCaps:
if state, ok := boolProperty(view, tag, session); ok { if state, ok := boolProperty(view, tag, session); ok {
@ -662,22 +642,18 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, "font-variant", "") session.updateCSSProperty(htmlID, "font-variant", "")
} }
return
case Strikethrough, Overline, Underline: case Strikethrough, Overline, Underline:
session.updateCSSProperty(htmlID, "text-decoration", view.cssTextDecoration(session)) session.updateCSSProperty(htmlID, "text-decoration", textDecorationCSS(view, session))
for _, tag2 := range []string{TextLineColor, TextLineStyle, TextLineThickness} { for _, tag2 := range []PropertyName{TextLineColor, TextLineStyle, TextLineThickness} {
viewPropertyChanged(view, tag2) viewPropertyChanged(view, tag2)
} }
return
case Transition: case Transition:
view.updateTransitionCSS() session.updateCSSProperty(htmlID, "transition", transitionCSS(view, session))
return
case AnimationTag: case AnimationTag:
session.updateCSSProperty(htmlID, AnimationTag, view.animationCSS(session)) session.updateCSSProperty(htmlID, "animation", animationCSS(view, session))
return
case AnimationPaused: case AnimationPaused:
paused, ok := boolProperty(view, AnimationPaused, session) paused, ok := boolProperty(view, AnimationPaused, session)
@ -688,21 +664,18 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, `animation-play-state`, `running`) session.updateCSSProperty(htmlID, `animation-play-state`, `running`)
} }
return
case ZIndex, Order, TabSize: case ZIndex, Order, TabSize:
if i, ok := intProperty(view, tag, session, 0); ok { if i, ok := intProperty(view, tag, session, 0); ok {
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i)) session.updateCSSProperty(htmlID, string(tag), strconv.Itoa(i))
} else { } else {
session.updateCSSProperty(htmlID, tag, "") session.updateCSSProperty(htmlID, string(tag), "")
} }
return
case Row, Column: case Row, Column:
if parentID := view.parentHTMLID(); parentID != "" { if parentID := view.parentHTMLID(); parentID != "" {
updateInnerHTML(parentID, session) updateInnerHTML(parentID, session)
} }
return
case UserSelect: case UserSelect:
if session.startUpdateScript(htmlID) { if session.startUpdateScript(htmlID) {
@ -720,7 +693,6 @@ func viewPropertyChanged(view *viewData, tag string) {
session.updateCSSProperty(htmlID, "-webkit-user-select", "") session.updateCSSProperty(htmlID, "-webkit-user-select", "")
session.updateCSSProperty(htmlID, "user-select", "") session.updateCSSProperty(htmlID, "user-select", "")
} }
return
case ColumnSpanAll: case ColumnSpanAll:
if spanAll, ok := boolProperty(view, ColumnSpanAll, session); ok && spanAll { if spanAll, ok := boolProperty(view, ColumnSpanAll, session); ok && spanAll {
@ -728,7 +700,6 @@ func viewPropertyChanged(view *viewData, tag string) {
} else { } else {
session.updateCSSProperty(htmlID, `column-span`, `none`) session.updateCSSProperty(htmlID, `column-span`, `none`)
} }
return
case Tooltip: case Tooltip:
if tooltip := GetTooltip(view); tooltip == "" { if tooltip := GetTooltip(view); tooltip == "" {
@ -738,68 +709,109 @@ func viewPropertyChanged(view *viewData, tag string) {
session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)") session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)")
session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)") session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)")
} }
return
}
if cssTag, ok := sizeProperties[tag]; ok { case PerspectiveOriginX, PerspectiveOriginY:
if size, ok := sizeProperty(view, tag, session); ok { if getTransform3D(view, session) {
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session)) x, y := GetPerspectiveOrigin(view)
} else { value := ""
session.updateCSSProperty(htmlID, cssTag, "") if x.Type != Auto || y.Type != Auto {
value = x.cssString("50%", session) + " " + y.cssString("50%", session)
}
session.updateCSSProperty(htmlID, "perspective-origin", value)
} }
return
}
colorTags := map[string]string{ case BackfaceVisible:
BackgroundColor: BackgroundColor, if getTransform3D(view, session) {
TextColor: "color", if GetBackfaceVisible(view) {
TextLineColor: "text-decoration-color", session.updateCSSProperty(htmlID, string(BackfaceVisible), "visible")
CaretColor: CaretColor,
AccentColor: 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
}
for _, floatTag := range []string{Opacity, ScaleX, ScaleY, ScaleZ, RotateX, RotateY, RotateZ} {
if tag == floatTag {
if f, ok := floatTextProperty(view, floatTag, session, 0); ok {
session.updateCSSProperty(htmlID, floatTag, f)
} else { } else {
session.updateCSSProperty(htmlID, floatTag, "") 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 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) Get(tag string) any { func viewGet(view View, tag PropertyName) any {
return view.get(strings.ToLower(tag))
}
func (view *viewData) get(tag string) any {
if tag == ID { if tag == ID {
if view.viewID != "" { if id := view.ID(); id != "" {
return view.viewID return id
} else { } else {
return nil return nil
} }
} }
return view.viewStyle.get(tag) return viewStyleGet(view, tag)
} }
func (view *viewData) htmlTag() string { func (view *viewData) htmlTag() string {
@ -848,6 +860,10 @@ func (view *viewData) cssStyle(self View, builder cssBuilder) {
} }
} }
func (view *viewData) htmlDisabledProperty() bool {
return view.hasHtmlDisabled
}
func (view *viewData) htmlProperties(self View, buffer *strings.Builder) { func (view *viewData) htmlProperties(self View, buffer *strings.Builder) {
view.created = true view.created = true
@ -908,23 +924,30 @@ func viewHTML(view View, buffer *strings.Builder) {
} }
} }
hasTooltip := false
if tooltip := GetTooltip(view); tooltip != "" { if tooltip := GetTooltip(view); tooltip != "" {
buffer.WriteString(`data-tooltip=" `) buffer.WriteString(`data-tooltip=" `)
buffer.WriteString(tooltip) buffer.WriteString(tooltip)
buffer.WriteString(`" `) buffer.WriteString(`" onmouseenter="mouseEnterEvent(this, event)" onmouseleave="mouseLeaveEvent(this, event)" `)
hasTooltip = true
} }
buffer.WriteString(`onscroll="scrollEvent(this, event)" `) buffer.WriteString(`onscroll="scrollEvent(this, event)" `)
keyEventsHtml(view, buffer)
mouseEventsHtml(view, buffer, hasTooltip)
pointerEventsHtml(view, buffer)
touchEventsHtml(view, buffer)
focusEventsHtml(view, buffer) focusEventsHtml(view, buffer)
transitionEventsHtml(view, buffer) keyEventsHtml(view, buffer)
animationEventsHtml(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('>') buffer.WriteRune('>')
view.htmlSubviews(view, buffer) view.htmlSubviews(view, buffer)
@ -957,7 +980,7 @@ func (view *viewData) htmlClass(disabled bool) string {
return cls return cls
} }
func (view *viewData) handleCommand(self View, command string, data DataObject) bool { func (view *viewData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case KeyDownEvent, KeyUpEvent: case KeyDownEvent, KeyUpEvent:
@ -976,13 +999,13 @@ func (view *viewData) handleCommand(self View, command string, data DataObject)
case FocusEvent: case FocusEvent:
view.hasFocus = true view.hasFocus = true
for _, listener := range getFocusListeners(view, nil, command) { for _, listener := range getNoParamEventListeners[View](view, nil, command) {
listener(self) listener(self)
} }
case LostFocusEvent: case LostFocusEvent:
view.hasFocus = false view.hasFocus = false
for _, listener := range getFocusListeners(view, nil, command) { for _, listener := range getNoParamEventListeners[View](view, nil, command) {
listener(self) listener(self)
} }
@ -1030,7 +1053,7 @@ func (view *viewData) handleCommand(self View, command string, data DataObject)
} }
func (view *viewData) SetChangeListener(tag string, listener func(View, string)) { func (view *viewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
if listener == nil { if listener == nil {
delete(view.changeListener, tag) delete(view.changeListener, tag)
} else { } else {
@ -1043,9 +1066,12 @@ func (view *viewData) HasFocus() bool {
} }
func (view *viewData) String() string { func (view *viewData) String() string {
return getViewString(view, nil) buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
writeViewStyle(view.tag, view, buffer, "", nil)
return buffer.String()
} }
func (view *viewData) exscludeTags() []string { func (view *viewData) exscludeTags() []PropertyName {
return nil return nil
} }

View File

@ -15,19 +15,19 @@ type ClipShape interface {
} }
type insetClip struct { type insetClip struct {
propertyList dataProperty
} }
type ellipseClip struct { type ellipseClip struct {
propertyList dataProperty
} }
type circleClip struct { type circleClip struct {
propertyList dataProperty
} }
type polygonClip struct { type polygonClip struct {
points []any dataProperty
} }
// InsetClip creates a rectangle View clipping area. // InsetClip creates a rectangle View clipping area.
@ -39,12 +39,12 @@ type polygonClip struct {
func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShape { func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShape {
clip := new(insetClip) clip := new(insetClip)
clip.init() clip.init()
clip.Set(Top, top) clip.setRaw(Top, top)
clip.Set(Right, right) clip.setRaw(Right, right)
clip.Set(Bottom, bottom) clip.setRaw(Bottom, bottom)
clip.Set(Left, left) clip.setRaw(Left, left)
if radius != nil { if radius != nil {
clip.Set(Radius, radius) clip.setRaw(Radius, radius)
} }
return clip return clip
} }
@ -53,9 +53,9 @@ func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipSha
func CircleClip(x, y, radius SizeUnit) ClipShape { func CircleClip(x, y, radius SizeUnit) ClipShape {
clip := new(circleClip) clip := new(circleClip)
clip.init() clip.init()
clip.Set(X, x) clip.setRaw(X, x)
clip.Set(Y, y) clip.setRaw(Y, y)
clip.Set(Radius, radius) clip.setRaw(Radius, radius)
return clip return clip
} }
@ -63,10 +63,10 @@ func CircleClip(x, y, radius SizeUnit) ClipShape {
func EllipseClip(x, y, rx, ry SizeUnit) ClipShape { func EllipseClip(x, y, rx, ry SizeUnit) ClipShape {
clip := new(ellipseClip) clip := new(ellipseClip)
clip.init() clip.init()
clip.Set(X, x) clip.setRaw(X, x)
clip.Set(Y, y) clip.setRaw(Y, y)
clip.Set(RadiusX, rx) clip.setRaw(RadiusX, rx)
clip.Set(RadiusY, ry) clip.setRaw(RadiusY, ry)
return clip return clip
} }
@ -75,8 +75,8 @@ func EllipseClip(x, y, rx, ry SizeUnit) ClipShape {
// or the text representation of SizeUnit, or elements of SizeUnit type. // or the text representation of SizeUnit, or elements of SizeUnit type.
func PolygonClip(points []any) ClipShape { func PolygonClip(points []any) ClipShape {
clip := new(polygonClip) clip := new(polygonClip)
clip.points = []any{} clip.init()
if clip.Set(Points, points) { if polygonClipSet(clip, Points, points) != nil {
return clip return clip
} }
return nil return nil
@ -85,34 +85,45 @@ func PolygonClip(points []any) ClipShape {
// PolygonPointsClip creates a polygon View clipping area. // PolygonPointsClip creates a polygon View clipping area.
func PolygonPointsClip(points []SizeUnit) ClipShape { func PolygonPointsClip(points []SizeUnit) ClipShape {
clip := new(polygonClip) clip := new(polygonClip)
clip.points = []any{} clip.init()
if clip.Set(Points, points) { if polygonClipSet(clip, Points, points) != nil {
return clip return clip
} }
return nil return nil
} }
func (clip *insetClip) Set(tag string, value any) bool { func (clip *insetClip) init() {
switch strings.ToLower(tag) { clip.dataProperty.init()
clip.set = insetClipSet
clip.supportedProperties = []PropertyName{
Top, Right, Bottom, Left, Radius,
RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY,
}
}
func insetClipSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case Top, Right, Bottom, Left: case Top, Right, Bottom, Left:
if value == nil { return setSizeProperty(properties, tag, value)
clip.Remove(tag)
return true
}
return clip.setSizeProperty(tag, value)
case Radius: case Radius:
return clip.setRadius(value) return setRadiusProperty(properties, value)
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY, case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
return clip.setRadiusElement(tag, value) if setRadiusPropertyElement(properties, tag, value) {
return []PropertyName{tag, Radius}
}
return nil
} }
ErrorLogF(`"%s" property is not supported by the inset clip shape`, tag) ErrorLogF(`"%s" property is not supported by the inset clip shape`, tag)
return false return nil
} }
func (clip *insetClip) String() string { func (clip *insetClip) String() string {
@ -122,12 +133,12 @@ func (clip *insetClip) String() string {
func (clip *insetClip) writeString(buffer *strings.Builder, indent string) { func (clip *insetClip) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("inset { ") buffer.WriteString("inset { ")
comma := false comma := false
for _, tag := range []string{Top, Right, Bottom, Left, Radius} { for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius} {
if value, ok := clip.properties[tag]; ok { if value, ok := clip.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -143,7 +154,7 @@ func (clip *insetClip) cssStyle(session Session) string {
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
leadText := "inset(" leadText := "inset("
for _, tag := range []string{Top, Right, Bottom, Left} { for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
value, _ := sizeProperty(clip, tag, session) value, _ := sizeProperty(clip, tag, session)
buffer.WriteString(leadText) buffer.WriteString(leadText)
buffer.WriteString(value.cssString("0px", session)) buffer.WriteString(value.cssString("0px", session))
@ -160,7 +171,7 @@ func (clip *insetClip) cssStyle(session Session) string {
} }
func (clip *insetClip) valid(session Session) bool { func (clip *insetClip) valid(session Session) bool {
for _, tag := range []string{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} { for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
if value, ok := sizeProperty(clip, tag, session); ok && value.Type != Auto && value.Value != 0 { if value, ok := sizeProperty(clip, tag, session); ok && value.Type != Auto && value.Value != 0 {
return true return true
} }
@ -168,18 +179,20 @@ func (clip *insetClip) valid(session Session) bool {
return false return false
} }
func (clip *circleClip) Set(tag string, value any) bool { func (clip *circleClip) init() {
if value == nil { clip.dataProperty.init()
clip.Remove(tag) clip.set = circleClipSet
} clip.supportedProperties = []PropertyName{X, Y, Radius}
}
switch strings.ToLower(tag) { func circleClipSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case X, Y, Radius: case X, Y, Radius:
return clip.setSizeProperty(tag, value) return setSizeProperty(properties, tag, value)
} }
ErrorLogF(`"%s" property is not supported by the circle clip shape`, tag) ErrorLogF(`"%s" property is not supported by the circle clip shape`, tag)
return false return nil
} }
func (clip *circleClip) String() string { func (clip *circleClip) String() string {
@ -189,12 +202,12 @@ func (clip *circleClip) String() string {
func (clip *circleClip) writeString(buffer *strings.Builder, indent string) { func (clip *circleClip) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("circle { ") buffer.WriteString("circle { ")
comma := false comma := false
for _, tag := range []string{Radius, X, Y} { for _, tag := range []PropertyName{Radius, X, Y} {
if value, ok := clip.properties[tag]; ok { if value, ok := clip.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -232,22 +245,27 @@ func (clip *circleClip) valid(session Session) bool {
return true return true
} }
func (clip *ellipseClip) Set(tag string, value any) bool { func (clip *ellipseClip) init() {
if value == nil { clip.dataProperty.init()
clip.Remove(tag) clip.set = ellipseClipSet
} clip.supportedProperties = []PropertyName{X, Y, Radius, RadiusX, RadiusY}
}
switch strings.ToLower(tag) { func ellipseClipSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case X, Y, RadiusX, RadiusY: case X, Y, RadiusX, RadiusY:
return clip.setSizeProperty(tag, value) return setSizeProperty(properties, tag, value)
case Radius: case Radius:
return clip.setSizeProperty(RadiusX, value) && if result := setSizeProperty(properties, RadiusX, value); result != nil {
clip.setSizeProperty(RadiusY, value) properties.setRaw(RadiusY, properties.getRaw(RadiusX))
return append(result, RadiusY)
}
return nil
} }
ErrorLogF(`"%s" property is not supported by the ellipse clip shape`, tag) ErrorLogF(`"%s" property is not supported by the ellipse clip shape`, tag)
return false return nil
} }
func (clip *ellipseClip) String() string { func (clip *ellipseClip) String() string {
@ -257,12 +275,12 @@ func (clip *ellipseClip) String() string {
func (clip *ellipseClip) writeString(buffer *strings.Builder, indent string) { func (clip *ellipseClip) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("ellipse { ") buffer.WriteString("ellipse { ")
comma := false comma := false
for _, tag := range []string{RadiusX, RadiusY, X, Y} { for _, tag := range []PropertyName{RadiusX, RadiusY, X, Y} {
if value, ok := clip.properties[tag]; ok { if value, ok := clip.properties[tag]; ok {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -302,104 +320,91 @@ func (clip *ellipseClip) valid(session Session) bool {
return rx.Value != 0 && ry.Value != 0 return rx.Value != 0 && ry.Value != 0
} }
func (clip *polygonClip) Get(tag string) any { func (clip *polygonClip) init() {
if Points == strings.ToLower(tag) { clip.dataProperty.init()
return clip.points clip.set = polygonClipSet
} clip.supportedProperties = []PropertyName{Points}
return nil
} }
func (clip *polygonClip) getRaw(tag string) any { func polygonClipSet(properties Properties, tag PropertyName, value any) []PropertyName {
return clip.Get(tag) if Points == tag {
}
func (clip *polygonClip) Set(tag string, value any) bool {
if Points == strings.ToLower(tag) {
switch value := value.(type) { switch value := value.(type) {
case []any: case []any:
result := true points := make([]any, len(value))
clip.points = make([]any, len(value))
for i, val := range value { for i, val := range value {
switch val := val.(type) { switch val := val.(type) {
case string: case string:
if isConstantName(val) { if isConstantName(val) {
clip.points[i] = val points[i] = val
} else if size, ok := StringToSizeUnit(val); ok { } else if size, ok := StringToSizeUnit(val); ok {
clip.points[i] = size points[i] = size
} else { } else {
notCompatibleType(tag, val) notCompatibleType(tag, val)
result = false return nil
} }
case SizeUnit: case SizeUnit:
clip.points[i] = val points[i] = val
default: default:
notCompatibleType(tag, val) notCompatibleType(tag, val)
clip.points[i] = AutoSize() points[i] = AutoSize()
result = false return nil
} }
} }
return result properties.setRaw(Points, points)
return []PropertyName{tag}
case []SizeUnit: case []SizeUnit:
clip.points = make([]any, len(value)) points := make([]any, len(value))
for i, point := range value { for i, point := range value {
clip.points[i] = point points[i] = point
} }
return true properties.setRaw(Points, points)
return []PropertyName{tag}
case string: case string:
result := true
values := strings.Split(value, ",") values := strings.Split(value, ",")
clip.points = make([]any, len(values)) points := make([]any, len(values))
for i, val := range values { for i, val := range values {
val = strings.Trim(val, " \t\n\r") val = strings.Trim(val, " \t\n\r")
if isConstantName(val) { if isConstantName(val) {
clip.points[i] = val points[i] = val
} else if size, ok := StringToSizeUnit(val); ok { } else if size, ok := StringToSizeUnit(val); ok {
clip.points[i] = size points[i] = size
} else { } else {
notCompatibleType(tag, val) notCompatibleType(tag, val)
result = false return nil
} }
} }
return result properties.setRaw(Points, points)
return []PropertyName{tag}
} }
} }
return false return nil
}
func (clip *polygonClip) setRaw(tag string, value any) {
clip.Set(tag, value)
}
func (clip *polygonClip) Remove(tag string) {
if Points == strings.ToLower(tag) {
clip.points = []any{}
}
}
func (clip *polygonClip) Clear() {
clip.points = []any{}
}
func (clip *polygonClip) AllTags() []string {
return []string{Points}
} }
func (clip *polygonClip) String() string { func (clip *polygonClip) String() string {
return runStringWriter(clip) return runStringWriter(clip)
} }
func (clip *polygonClip) points() []any {
if value := clip.getRaw(Points); value != nil {
if points, ok := value.([]any); ok {
return points
}
}
return nil
}
func (clip *polygonClip) writeString(buffer *strings.Builder, indent string) { func (clip *polygonClip) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("inset { ") buffer.WriteString("inset { ")
if clip.points != nil { if points := clip.points(); points != nil {
buffer.WriteString(Points) buffer.WriteString(string(Points))
buffer.WriteString(` = "`) buffer.WriteString(` = "`)
for i, value := range clip.points { for i, value := range points {
if i > 0 { if i > 0 {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
@ -408,13 +413,13 @@ func (clip *polygonClip) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString(`" `) buffer.WriteString(`" `)
} }
buffer.WriteRune('}') buffer.WriteRune('}')
} }
func (clip *polygonClip) cssStyle(session Session) string { func (clip *polygonClip) cssStyle(session Session) string {
count := len(clip.points) points := clip.points()
count := len(points)
if count < 2 { if count < 2 {
return "" return ""
} }
@ -443,9 +448,9 @@ func (clip *polygonClip) cssStyle(session Session) string {
leadText := "polygon(" leadText := "polygon("
for i := 1; i < count; i += 2 { for i := 1; i < count; i += 2 {
buffer.WriteString(leadText) buffer.WriteString(leadText)
writePoint(clip.points[i-1]) writePoint(points[i-1])
buffer.WriteRune(' ') buffer.WriteRune(' ')
writePoint(clip.points[i]) writePoint(points[i])
leadText = ", " leadText = ", "
} }
@ -454,42 +459,46 @@ func (clip *polygonClip) cssStyle(session Session) string {
} }
func (clip *polygonClip) valid(session Session) bool { func (clip *polygonClip) valid(session Session) bool {
return len(clip.points) > 0 return len(clip.points()) > 0
} }
func parseClipShape(obj DataObject) ClipShape { func parseClipShape(obj DataObject) ClipShape {
switch obj.Tag() { switch obj.Tag() {
case "inset": case "inset":
clip := new(insetClip) clip := new(insetClip)
for _, tag := range []string{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} { clip.init()
if value, ok := obj.PropertyValue(tag); ok { for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
clip.Set(tag, value) if value, ok := obj.PropertyValue(string(tag)); ok {
insetClipSet(clip, tag, value)
} }
} }
return clip return clip
case "circle": case "circle":
clip := new(ellipseClip) clip := new(ellipseClip)
for _, tag := range []string{X, Y, Radius} { clip.init()
if value, ok := obj.PropertyValue(tag); ok { for _, tag := range []PropertyName{X, Y, Radius} {
clip.Set(tag, value) if value, ok := obj.PropertyValue(string(tag)); ok {
circleClipSet(clip, tag, value)
} }
} }
return clip return clip
case "ellipse": case "ellipse":
clip := new(ellipseClip) clip := new(ellipseClip)
for _, tag := range []string{X, Y, RadiusX, RadiusY} { clip.init()
if value, ok := obj.PropertyValue(tag); ok { for _, tag := range []PropertyName{X, Y, RadiusX, RadiusY} {
clip.Set(tag, value) if value, ok := obj.PropertyValue(string(tag)); ok {
ellipseClipSet(clip, tag, value)
} }
} }
return clip return clip
case "polygon": case "polygon":
clip := new(ellipseClip) clip := new(ellipseClip)
if value, ok := obj.PropertyValue(Points); ok { clip.init()
clip.Set(Points, value) if value, ok := obj.PropertyValue(string(Points)); ok {
polygonClipSet(clip, Points, value)
} }
return clip return clip
} }
@ -497,45 +506,45 @@ func parseClipShape(obj DataObject) ClipShape {
return nil return nil
} }
func (style *viewStyle) setClipShape(tag string, value any) bool { func setClipShapeProperty(properties Properties, tag PropertyName, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case ClipShape: case ClipShape:
style.properties[tag] = value properties.setRaw(tag, value)
return true return []PropertyName{tag}
case string: case string:
if isConstantName(value) { if isConstantName(value) {
style.properties[tag] = value properties.setRaw(tag, value)
return true return []PropertyName{tag}
} }
if obj := NewDataObject(value); obj == nil { if obj := NewDataObject(value); obj == nil {
if clip := parseClipShape(obj); clip != nil { if clip := parseClipShape(obj); clip != nil {
style.properties[tag] = clip properties.setRaw(tag, clip)
return true return []PropertyName{tag}
} }
} }
case DataObject: case DataObject:
if clip := parseClipShape(value); clip != nil { if clip := parseClipShape(value); clip != nil {
style.properties[tag] = clip properties.setRaw(tag, clip)
return true return []PropertyName{tag}
} }
case DataValue: case DataValue:
if value.IsObject() { if value.IsObject() {
if clip := parseClipShape(value.Object()); clip != nil { if clip := parseClipShape(value.Object()); clip != nil {
style.properties[tag] = clip properties.setRaw(tag, clip)
return true return []PropertyName{tag}
} }
} }
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
func getClipShape(prop Properties, tag string, session Session) ClipShape { func getClipShape(prop Properties, tag PropertyName, session Session) ClipShape {
if value := prop.getRaw(tag); value != nil { if value := prop.getRaw(tag); value != nil {
switch value := value.(type) { switch value := value.(type) {
case ClipShape: case ClipShape:

View File

@ -85,6 +85,7 @@ func CreateViewFromObject(session Session, object DataObject) View {
defer session.setIgnoreViewUpdates(false) defer session.setIgnoreViewUpdates(false)
} }
view := creator(session) view := creator(session)
view.init(session)
if customView, ok := view.(CustomView); ok { if customView, ok := view.(CustomView); ok {
if !InitCustomView(customView, tag, session, nil) { if !InitCustomView(customView, tag, session, nil) {
return nil return nil

View File

@ -17,7 +17,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Blur = "blur" Blur PropertyName = "blur"
// Brightness is the constant for "brightness" property tag. // Brightness is the constant for "brightness" property tag.
// //
@ -29,7 +29,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Brightness = "brightness" Brightness PropertyName = "brightness"
// Contrast is the constant for "contrast" property tag. // Contrast is the constant for "contrast" property tag.
// //
@ -40,7 +40,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Contrast = "contrast" Contrast PropertyName = "contrast"
// DropShadow is the constant for "drop-shadow" property tag. // DropShadow is the constant for "drop-shadow" property tag.
// //
@ -58,7 +58,7 @@ const (
// `[]ViewShadow` - stored as is, no conversion performed. // `[]ViewShadow` - stored as is, no conversion performed.
// `ViewShadow` - converted to `[]ViewShadow`. // `ViewShadow` - converted to `[]ViewShadow`.
// `string` - string representation of `ViewShadow`. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}". // `string` - string representation of `ViewShadow`. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}".
DropShadow = "drop-shadow" DropShadow PropertyName = "drop-shadow"
// Grayscale is the constant for "grayscale" property tag. // Grayscale is the constant for "grayscale" property tag.
// //
@ -70,7 +70,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Grayscale = "grayscale" Grayscale PropertyName = "grayscale"
// HueRotate is the constant for "hue-rotate" property tag. // HueRotate is the constant for "hue-rotate" property tag.
// //
@ -89,7 +89,7 @@ const (
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created. // `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
// `float` - a new `AngleUnit` value will be created with `Radian` as a type. // `float` - a new `AngleUnit` value will be created with `Radian` as a type.
// `int` - a new `AngleUnit` value will be created with `Radian` as a type. // `int` - a new `AngleUnit` value will be created with `Radian` as a type.
HueRotate = "hue-rotate" HueRotate PropertyName = "hue-rotate"
// Invert is the constant for "invert" property tag. // Invert is the constant for "invert" property tag.
// //
@ -101,7 +101,7 @@ const (
// Supported types: `float64`, `int`, `string`. // Supported types: `float64`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Invert = "invert" Invert PropertyName = "invert"
// Saturate is the constant for "saturate" property tag. // Saturate is the constant for "saturate" property tag.
// //
@ -113,7 +113,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Saturate = "saturate" Saturate PropertyName = "saturate"
// Sepia is the constant for "sepia" property tag. // Sepia is the constant for "sepia" property tag.
// //
@ -125,9 +125,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
Sepia = "sepia" Sepia PropertyName = "sepia"
//Opacity = "opacity"
) )
// ViewFilter defines an applied to a View a graphical effects like blur or color shift. // ViewFilter defines an applied to a View a graphical effects like blur or color shift.
@ -140,20 +138,20 @@ type ViewFilter interface {
} }
type viewFilter struct { type viewFilter struct {
propertyList dataProperty
} }
// NewViewFilter creates the new ViewFilter // NewViewFilter creates the new ViewFilter
func NewViewFilter(params Params) ViewFilter { func NewViewFilter(params Params) ViewFilter {
if params != nil { if len(params) > 0 {
filter := new(viewFilter) filter := new(viewFilter)
filter.init() filter.init()
for tag, value := range params { for tag, value := range params {
filter.Set(tag, value) if !filter.Set(tag, value) {
} return nil
if len(filter.properties) > 0 { }
return filter
} }
return filter
} }
return nil return nil
} }
@ -166,10 +164,10 @@ func newViewFilter(obj DataObject) ViewFilter {
tag := node.Tag() tag := node.Tag()
switch node.Type() { switch node.Type() {
case TextNode: case TextNode:
filter.Set(tag, node.Text()) filter.Set(PropertyName(tag), node.Text())
case ObjectNode: case ObjectNode:
if tag == HueRotate { if tag == string(HueRotate) {
// TODO // TODO
} else { } else {
ErrorLog(`Invalid value of "` + tag + `"`) ErrorLog(`Invalid value of "` + tag + `"`)
@ -188,28 +186,31 @@ func newViewFilter(obj DataObject) ViewFilter {
return nil return nil
} }
func (filter *viewFilter) Set(tag string, value any) bool { func (filter *viewFilter) init() {
if value == nil { filter.dataProperty.init()
filter.Remove(tag) filter.set = viewFilterSet
return true filter.supportedProperties = []PropertyName{Blur, Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia, HueRotate, DropShadow}
} }
switch strings.ToLower(tag) { func viewFilterSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case Blur, Brightness, Contrast, Saturate: case Blur, Brightness, Contrast, Saturate:
return filter.setFloatProperty(tag, value, 0, 10000) return setFloatProperty(properties, tag, value, 0, 10000)
case Grayscale, Invert, Opacity, Sepia: case Grayscale, Invert, Opacity, Sepia:
return filter.setFloatProperty(tag, value, 0, 100) return setFloatProperty(properties, tag, value, 0, 100)
case HueRotate: case HueRotate:
return filter.setAngleProperty(tag, value) return setAngleProperty(properties, tag, value)
case DropShadow: case DropShadow:
return filter.setShadow(tag, value) if setShadowProperty(properties, tag, value) {
return []PropertyName{tag}
}
} }
ErrorLogF(`"%s" property is not supported by the view filter`, tag) ErrorLogF(`"%s" property is not supported by the view filter`, tag)
return false return nil
} }
func (filter *viewFilter) String() string { func (filter *viewFilter) String() string {
@ -225,7 +226,7 @@ func (filter *viewFilter) writeString(buffer *strings.Builder, indent string) {
if comma { if comma {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
comma = true comma = true
@ -239,18 +240,18 @@ func (filter *viewFilter) cssStyle(session Session) string {
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
if value, ok := floatTextProperty(filter, Blur, session, 0); ok { if value, ok := floatTextProperty(filter, Blur, session, 0); ok {
buffer.WriteString(Blur) buffer.WriteString(string(Blur))
buffer.WriteRune('(') buffer.WriteRune('(')
buffer.WriteString(value) buffer.WriteString(value)
buffer.WriteString("px)") buffer.WriteString("px)")
} }
for _, tag := range []string{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} { for _, tag := range []PropertyName{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} {
if value, ok := floatTextProperty(filter, tag, session, 0); ok { if value, ok := floatTextProperty(filter, tag, session, 0); ok {
if buffer.Len() > 0 { if buffer.Len() > 0 {
buffer.WriteRune(' ') buffer.WriteRune(' ')
} }
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteRune('(') buffer.WriteRune('(')
buffer.WriteString(value) buffer.WriteString(value)
buffer.WriteString("%)") buffer.WriteString("%)")
@ -261,7 +262,7 @@ func (filter *viewFilter) cssStyle(session Session) string {
if buffer.Len() > 0 { if buffer.Len() > 0 {
buffer.WriteRune(' ') buffer.WriteRune(' ')
} }
buffer.WriteString(HueRotate) buffer.WriteString(string(HueRotate))
buffer.WriteRune('(') buffer.WriteRune('(')
buffer.WriteString(value.cssString()) buffer.WriteString(value.cssString())
buffer.WriteRune(')') buffer.WriteRune(')')
@ -284,36 +285,37 @@ func (filter *viewFilter) cssStyle(session Session) string {
return buffer.String() return buffer.String()
} }
func (style *viewStyle) setFilter(tag string, value any) bool { func setFilterProperty(properties Properties, tag PropertyName, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case ViewFilter: case ViewFilter:
style.properties[tag] = value properties.setRaw(tag, value)
return true return []PropertyName{tag}
case string: case string:
if obj := NewDataObject(value); obj == nil { if obj := NewDataObject(value); obj == nil {
if filter := newViewFilter(obj); filter != nil { if filter := newViewFilter(obj); filter != nil {
style.properties[tag] = filter properties.setRaw(tag, filter)
return true return []PropertyName{tag}
} }
} }
case DataObject: case DataObject:
if filter := newViewFilter(value); filter != nil { if filter := newViewFilter(value); filter != nil {
style.properties[tag] = filter properties.setRaw(tag, filter)
return true return []PropertyName{tag}
} }
case DataValue: case DataValue:
if value.IsObject() { if value.IsObject() {
if filter := newViewFilter(value.Object()); filter != nil { if filter := newViewFilter(value.Object()); filter != nil {
style.properties[tag] = filter properties.setRaw(tag, filter)
return true return []PropertyName{tag}
} }
} }
} }
notCompatibleType(tag, value) notCompatibleType(tag, value)
return false return nil
} }
// GetFilter returns a View graphical effects like blur or color shift. // GetFilter returns a View graphical effects like blur or color shift.

View File

@ -12,72 +12,31 @@ type ViewStyle interface {
Properties Properties
// Transition returns the transition animation of the property. Returns nil is there is no transition animation. // Transition returns the transition animation of the property. Returns nil is there is no transition animation.
Transition(tag string) Animation Transition(tag PropertyName) Animation
// Transitions returns the map of transition animations. The result is always non-nil. // Transitions returns the map of transition animations. The result is always non-nil.
Transitions() map[string]Animation Transitions() map[PropertyName]Animation
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and // SetTransition sets the transition animation for the property if "animation" argument is not nil, and
// removes the transition animation of the property if "animation" argument is nil. // removes the transition animation of the property if "animation" argument is nil.
// The "tag" argument is the property name. // The "tag" argument is the property name.
SetTransition(tag string, animation Animation) SetTransition(tag PropertyName, animation Animation)
cssViewStyle(buffer cssBuilder, session Session) cssViewStyle(buffer cssBuilder, session Session)
} }
type viewStyle struct { type viewStyle struct {
propertyList propertyList
transitions map[string]Animation //transitions map[PropertyName]Animation
}
// Range defines range limits. The First and Last value are included in the range
type Range struct {
First, Last int
} }
type stringWriter interface { type stringWriter interface {
writeString(buffer *strings.Builder, indent string) writeString(buffer *strings.Builder, indent string)
} }
// String returns a string representation of the Range struct
func (r Range) String() string {
if r.First == r.Last {
return fmt.Sprintf("%d", r.First)
}
return fmt.Sprintf("%d:%d", r.First, r.Last)
}
func (r *Range) setValue(value string) bool {
var err error
if strings.Contains(value, ":") {
values := strings.Split(value, ":")
if len(values) != 2 {
ErrorLog("Invalid range value: " + value)
return false
}
if r.First, err = strconv.Atoi(strings.Trim(values[0], " \t\n\r")); err != nil {
ErrorLog(`Invalid first range value "` + value + `" (` + err.Error() + ")")
return false
}
if r.Last, err = strconv.Atoi(strings.Trim(values[1], " \t\n\r")); err != nil {
ErrorLog(`Invalid last range value "` + value + `" (` + err.Error() + ")")
return false
}
return true
}
if r.First, err = strconv.Atoi(value); err != nil {
ErrorLog(`Invalid range value "` + value + `" (` + err.Error() + ")")
return false
}
r.Last = r.First
return true
}
func (style *viewStyle) init() { func (style *viewStyle) init() {
style.propertyList.init() style.propertyList.init()
//style.shadows = []ViewShadow{} style.normalize = normalizeViewStyleTag
style.transitions = map[string]Animation{}
} }
// NewViewStyle create new ViewStyle object // NewViewStyle create new ViewStyle object
@ -90,19 +49,19 @@ func NewViewStyle(params Params) ViewStyle {
return style return style
} }
func (style *viewStyle) cssTextDecoration(session Session) string { func textDecorationCSS(properties Properties, session Session) string {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
noDecoration := false noDecoration := false
if strikethrough, ok := boolProperty(style, Strikethrough, session); ok { if strikethrough, ok := boolProperty(properties, Strikethrough, session); ok {
if strikethrough { if strikethrough {
buffer.WriteString("line-through") buffer.WriteString("line-through")
} }
noDecoration = true noDecoration = true
} }
if overline, ok := boolProperty(style, Overline, session); ok { if overline, ok := boolProperty(properties, Overline, session); ok {
if overline { if overline {
if buffer.Len() > 0 { if buffer.Len() > 0 {
buffer.WriteRune(' ') buffer.WriteRune(' ')
@ -112,7 +71,7 @@ func (style *viewStyle) cssTextDecoration(session Session) string {
noDecoration = true noDecoration = true
} }
if underline, ok := boolProperty(style, Underline, session); ok { if underline, ok := boolProperty(properties, Underline, session); ok {
if underline { if underline {
if buffer.Len() > 0 { if buffer.Len() > 0 {
buffer.WriteRune(' ') buffer.WriteRune(' ')
@ -149,8 +108,8 @@ func split4Values(text string) []string {
return []string{} return []string{}
} }
func (style *viewStyle) backgroundCSS(session Session) string { func backgroundCSS(properties Properties, session Session) string {
if value, ok := style.properties[Background]; ok { if value := properties.getRaw(Background); value != nil {
if backgrounds, ok := value.([]BackgroundElement); ok { if backgrounds, ok := value.([]BackgroundElement); ok {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
@ -184,15 +143,15 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
} }
if margin, ok := boundsProperty(style, Margin, session); ok { if margin, ok := getBounds(style, Margin, session); ok {
margin.cssValue(Margin, builder, session) margin.cssValue(Margin, builder, session)
} }
if padding, ok := boundsProperty(style, Padding, session); ok { if padding, ok := getBounds(style, Padding, session); ok {
padding.cssValue(Padding, builder, session) padding.cssValue(Padding, builder, session)
} }
if border := getBorder(style, Border); border != nil { if border := getBorderProperty(style, Border); border != nil {
border.cssStyle(builder, session) border.cssStyle(builder, session)
border.cssWidth(builder, session) border.cssWidth(builder, session)
border.cssColor(builder, session) border.cssColor(builder, session)
@ -201,27 +160,27 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
radius := getRadius(style, session) radius := getRadius(style, session)
radius.cssValue(builder, session) radius.cssValue(builder, session)
if outline := getOutline(style); outline != nil { if outline := getOutlineProperty(style); outline != nil {
outline.ViewOutline(session).cssValue(builder, session) outline.ViewOutline(session).cssValue(builder, session)
} }
for _, tag := range []string{ZIndex, Order} { for _, tag := range []PropertyName{ZIndex, Order} {
if value, ok := intProperty(style, tag, session, 0); ok { if value, ok := intProperty(style, tag, session, 0); ok {
builder.add(tag, strconv.Itoa(value)) builder.add(string(tag), strconv.Itoa(value))
} }
} }
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 { if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32)) builder.add(string(Opacity), strconv.FormatFloat(opacity, 'f', 3, 32))
} }
for _, tag := range []string{ColumnCount, TabSize} { for _, tag := range []PropertyName{ColumnCount, TabSize} {
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 { if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
builder.add(tag, strconv.Itoa(value)) builder.add(string(tag), strconv.Itoa(value))
} }
} }
for _, tag := range []string{ for _, tag := range []PropertyName{
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness, TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} { ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
@ -229,18 +188,22 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto { if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
cssTag, ok := sizeProperties[tag] cssTag, ok := sizeProperties[tag]
if !ok { if !ok {
cssTag = tag cssTag = string(tag)
} }
builder.add(cssTag, size.cssString("", session)) builder.add(cssTag, size.cssString("", session))
} }
} }
colorProperties := []struct{ property, cssTag string }{ type propertyCss struct {
{BackgroundColor, BackgroundColor}, property PropertyName
cssTag string
}
colorProperties := []propertyCss{
{BackgroundColor, string(BackgroundColor)},
{TextColor, "color"}, {TextColor, "color"},
{TextLineColor, "text-decoration-color"}, {TextLineColor, "text-decoration-color"},
{CaretColor, CaretColor}, {CaretColor, string(CaretColor)},
{AccentColor, AccentColor}, {AccentColor, string(AccentColor)},
} }
for _, p := range colorProperties { for _, p := range colorProperties {
if color, ok := colorProperty(style, p.property, session); ok && color != 0 { if color, ok := colorProperty(style, p.property, session); ok && color != 0 {
@ -249,10 +212,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
if value, ok := enumProperty(style, BackgroundClip, session, 0); ok { if value, ok := enumProperty(style, BackgroundClip, session, 0); ok {
builder.add(BackgroundClip, enumProperties[BackgroundClip].values[value]) builder.add(string(BackgroundClip), enumProperties[BackgroundClip].values[value])
} }
if background := style.backgroundCSS(session); background != "" { if background := backgroundCSS(style, session); background != "" {
builder.add("background", background) builder.add("background", background)
} }
@ -261,7 +224,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
writingMode := 0 writingMode := 0
for _, tag := range []string{ for _, tag := range []PropertyName{
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection, Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor, VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} { WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
@ -282,7 +245,11 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
} }
for _, prop := range []struct{ tag, cssTag, off, on string }{ type boolPropertyCss struct {
tag PropertyName
cssTag, off, on string
}
for _, prop := range []boolPropertyCss{
{tag: Italic, cssTag: "font-style", off: "normal", on: "italic"}, {tag: Italic, cssTag: "font-style", off: "normal", on: "italic"},
{tag: SmallCaps, cssTag: "font-variant", off: "normal", on: "small-caps"}, {tag: SmallCaps, cssTag: "font-variant", off: "normal", on: "small-caps"},
} { } {
@ -295,7 +262,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
} }
if text := style.cssTextDecoration(session); text != "" { if text := textDecorationCSS(style, session); text != "" {
builder.add("text-decoration", text) builder.add("text-decoration", text)
} }
@ -416,10 +383,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if r, ok := rangeProperty(style, Column, session); ok { if r, ok := rangeProperty(style, Column, session); ok {
builder.add("grid-column", fmt.Sprintf("%d / %d", r.First+1, r.Last+2)) builder.add("grid-column", fmt.Sprintf("%d / %d", r.First+1, r.Last+2))
} }
if text := style.gridCellSizesCSS(CellWidth, session); text != "" { if text := gridCellSizesCSS(style, CellWidth, session); text != "" {
builder.add(`grid-template-columns`, text) builder.add(`grid-template-columns`, text)
} }
if text := style.gridCellSizesCSS(CellHeight, session); text != "" { if text := gridCellSizesCSS(style, CellHeight, session); text != "" {
builder.add(`grid-template-rows`, text) builder.add(`grid-template-rows`, text)
} }
@ -436,7 +403,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if value := style.getRaw(Filter); value != nil { if value := style.getRaw(Filter); value != nil {
if filter, ok := value.(ViewFilter); ok { if filter, ok := value.(ViewFilter); ok {
if text := filter.cssStyle(session); text != "" { if text := filter.cssStyle(session); text != "" {
builder.add(Filter, text) builder.add(string(Filter), text)
} }
} }
} }
@ -445,17 +412,17 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if filter, ok := value.(ViewFilter); ok { if filter, ok := value.(ViewFilter); ok {
if text := filter.cssStyle(session); text != "" { if text := filter.cssStyle(session); text != "" {
builder.add(`-webkit-backdrop-filter`, text) builder.add(`-webkit-backdrop-filter`, text)
builder.add(BackdropFilter, text) builder.add(string(BackdropFilter), text)
} }
} }
} }
if transition := style.transitionCSS(session); transition != "" { if transition := transitionCSS(style, session); transition != "" {
builder.add(`transition`, transition) builder.add(`transition`, transition)
} }
if animation := style.animationCSS(session); animation != "" { if animation := animationCSS(style, session); animation != "" {
builder.add(AnimationTag, animation) builder.add(string(AnimationTag), animation)
} }
if pause, ok := boolProperty(style, AnimationPaused, session); ok { if pause, ok := boolProperty(style, AnimationPaused, session); ok {
@ -504,20 +471,48 @@ func valueToOrientation(value any, session Session) (int, bool) {
return 0, false return 0, false
} }
func (style *viewStyle) Get(tag string) any { func normalizeViewStyleTag(tag PropertyName) PropertyName {
return style.get(strings.ToLower(tag)) tag = defaultNormalize(tag)
switch tag {
case "top-margin":
return MarginTop
case "right-margin":
return MarginRight
case "bottom-margin":
return MarginBottom
case "left-margin":
return MarginLeft
case "top-padding":
return PaddingTop
case "right-padding":
return PaddingRight
case "bottom-padding":
return PaddingBottom
case "left-padding":
return PaddingLeft
}
return tag
} }
func (style *viewStyle) get(tag string) any { func (style *viewStyle) Get(tag PropertyName) any {
return viewStyleGet(style, normalizeViewStyleTag(tag))
}
func viewStyleGet(style Properties, tag PropertyName) any {
switch tag { switch tag {
case Border, CellBorder:
return getBorder(&style.propertyList, tag)
case BorderLeft, BorderRight, BorderTop, BorderBottom, case BorderLeft, BorderRight, BorderTop, BorderBottom,
BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle, BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle,
BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor, BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor,
BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth: BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth:
if border := getBorder(style, Border); border != nil { if border := getBorderProperty(style, Border); border != nil {
return border.Get(tag) return border.Get(tag)
} }
return nil return nil
@ -526,7 +521,7 @@ func (style *viewStyle) get(tag string) any {
CellBorderStyle, CellBorderLeftStyle, CellBorderRightStyle, CellBorderTopStyle, CellBorderBottomStyle, CellBorderStyle, CellBorderLeftStyle, CellBorderRightStyle, CellBorderTopStyle, CellBorderBottomStyle,
CellBorderColor, CellBorderLeftColor, CellBorderRightColor, CellBorderTopColor, CellBorderBottomColor, CellBorderColor, CellBorderLeftColor, CellBorderRightColor, CellBorderTopColor, CellBorderBottomColor,
CellBorderWidth, CellBorderLeftWidth, CellBorderRightWidth, CellBorderTopWidth, CellBorderBottomWidth: CellBorderWidth, CellBorderLeftWidth, CellBorderRightWidth, CellBorderTopWidth, CellBorderBottomWidth:
if border := getBorder(style, CellBorder); border != nil { if border := getBorderProperty(style, CellBorder); border != nil {
return border.Get(tag) return border.Get(tag)
} }
return nil return nil
@ -537,46 +532,22 @@ func (style *viewStyle) get(tag string) any {
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
return getRadiusElement(style, tag) return getRadiusElement(style, tag)
case ColumnSeparator:
if val, ok := style.properties[ColumnSeparator]; ok {
return val.(ColumnSeparatorProperty)
}
return nil
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor: case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
if val, ok := style.properties[ColumnSeparator]; ok { if val := style.getRaw(ColumnSeparator); val != nil {
separator := val.(ColumnSeparatorProperty) separator := val.(ColumnSeparatorProperty)
return separator.Get(tag) return separator.Get(tag)
} }
return nil return nil
case Transition:
if len(style.transitions) == 0 {
return nil
}
result := map[string]Animation{}
for tag, animation := range style.transitions {
result[tag] = animation
}
return result
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 := style.transformProperty(); transform != nil { if transform := getTransformProperty(style); transform != nil {
return transform.Get(tag) return transform.Get(tag)
} }
return nil return nil
} }
return style.propertyList.getRaw(tag) return style.getRaw(tag)
}
func (style *viewStyle) AllTags() []string {
result := style.propertyList.AllTags()
if len(style.transitions) > 0 {
result = append(result, Transition)
}
return result
} }
func supportedPropertyValue(value any) bool { func supportedPropertyValue(value any) bool {
@ -595,14 +566,14 @@ func supportedPropertyValue(value any) bool {
case []BackgroundElement: case []BackgroundElement:
case []BackgroundGradientPoint: case []BackgroundGradientPoint:
case []BackgroundGradientAngle: case []BackgroundGradientAngle:
case map[string]Animation: case map[PropertyName]Animation:
default: default:
return false return false
} }
return true return true
} }
func writePropertyValue(buffer *strings.Builder, tag string, value any, indent string) { func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, indent string) {
writeString := func(text string) { writeString := func(text string) {
simple := (tag != Text && tag != Title && tag != Summary) simple := (tag != Text && tag != Title && tag != Summary)
@ -804,7 +775,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
} }
buffer.WriteRune('"') buffer.WriteRune('"')
case map[string]Animation: case map[PropertyName]Animation:
switch count := len(value); count { switch count := len(value); count {
case 0: case 0:
buffer.WriteString("[]") buffer.WriteString("[]")
@ -816,11 +787,13 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
} }
default: default:
tags := make([]string, 0, len(value)) tags := make([]PropertyName, 0, len(value))
for tag := range value { for tag := range value {
tags = append(tags, tag) tags = append(tags, tag)
} }
sort.Strings(tags) sort.Slice(tags, func(i, j int) bool {
return tags[i] < tags[j]
})
buffer.WriteString("[\n") buffer.WriteString("[\n")
indent2 := indent + "\t" indent2 := indent + "\t"
for _, tag := range tags { for _, tag := range tags {
@ -836,12 +809,12 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
} }
} }
func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent string, excludeTags []string) { func writeViewStyle(name string, view Properties, buffer *strings.Builder, indent string, excludeTags []PropertyName) {
buffer.WriteString(name) buffer.WriteString(name)
buffer.WriteString(" {\n") buffer.WriteString(" {\n")
indent += "\t" indent += "\t"
writeProperty := func(tag string, value any) { writeProperty := func(tag PropertyName, value any) {
for _, exclude := range excludeTags { for _, exclude := range excludeTags {
if exclude == tag { if exclude == tag {
return return
@ -850,7 +823,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
if supportedPropertyValue(value) { if supportedPropertyValue(value) {
buffer.WriteString(indent) buffer.WriteString(indent)
buffer.WriteString(tag) buffer.WriteString(string(tag))
buffer.WriteString(" = ") buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent) writePropertyValue(buffer, tag, value, indent)
buffer.WriteString(",\n") buffer.WriteString(",\n")
@ -858,7 +831,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
} }
tags := view.AllTags() tags := view.AllTags()
removeTag := func(tag string) { removeTag := func(tag PropertyName) {
for i, t := range tags { for i, t := range tags {
if t == tag { if t == tag {
if i == 0 { if i == 0 {
@ -873,7 +846,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
} }
} }
tagOrder := []string{ tagOrder := []PropertyName{
ID, Row, Column, Top, Right, Bottom, Left, Semantics, Cursor, Visibility, ID, Row, Column, Top, Right, Bottom, Left, Semantics, Cursor, Visibility,
Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight,
Margin, Padding, BackgroundClip, BackgroundColor, Background, Border, Radius, Outline, Shadow, Margin, Padding, BackgroundClip, BackgroundColor, Background, Border, Radius, Outline, Shadow,
@ -894,7 +867,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
} }
} }
finalTags := []string{ finalTags := []PropertyName{
Perspective, PerspectiveOriginX, PerspectiveOriginY, BackfaceVisible, OriginX, OriginY, OriginZ, Perspective, PerspectiveOriginX, PerspectiveOriginY, BackfaceVisible, OriginX, OriginY, OriginZ,
TransformTag, Clip, Filter, BackdropFilter, Summary, Content, Transition} TransformTag, Clip, Filter, BackdropFilter, Summary, Content, Transition}
for _, tag := range finalTags { for _, tag := range finalTags {
@ -918,14 +891,6 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
buffer.WriteString("}") buffer.WriteString("}")
} }
func getViewString(view View, excludeTags []string) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
writeViewStyle(view.Tag(), view, buffer, "", excludeTags)
return buffer.String()
}
func runStringWriter(writer stringWriter) string { func runStringWriter(writer stringWriter) string {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)

View File

@ -4,93 +4,19 @@ import (
"strings" "strings"
) )
func (style *viewStyle) setRange(tag string, value any) bool { func setTransitionProperty(properties Properties, value any) bool {
switch value := value.(type) {
case string:
if strings.Contains(value, "@") {
style.properties[tag] = value
return true
}
var r Range
if !r.setValue(value) {
invalidPropertyValue(tag, value)
return false
}
style.properties[tag] = r
case int: transitions := map[PropertyName]Animation{}
style.properties[tag] = Range{First: value, Last: value}
case Range:
style.properties[tag] = value
default:
notCompatibleType(tag, value)
return false
}
return true
}
func (style *viewStyle) setBackground(value any) bool {
background := []BackgroundElement{}
switch value := value.(type) {
case BackgroundElement:
background = []BackgroundElement{value}
case []BackgroundElement:
background = value
case []DataValue:
for _, el := range value {
if el.IsObject() {
if element := createBackground(el.Object()); element != nil {
background = append(background, element)
}
} else if obj := ParseDataText(el.Value()); obj != nil {
if element := createBackground(obj); element != nil {
background = append(background, element)
}
}
}
case DataObject:
if element := createBackground(value); element != nil {
background = []BackgroundElement{element}
}
case []DataObject:
for _, obj := range value {
if element := createBackground(obj); element != nil {
background = append(background, element)
}
}
case string:
if obj := ParseDataText(value); obj != nil {
if element := createBackground(obj); element != nil {
background = []BackgroundElement{element}
}
}
}
if len(background) > 0 {
style.properties[Background] = background
return true
}
return false
}
func (style *viewStyle) setTransition(tag string, value any) bool {
setObject := func(obj DataObject) bool { setObject := func(obj DataObject) bool {
if obj != nil { if obj != nil {
tag := strings.ToLower(tag) tag := strings.ToLower(obj.Tag())
switch tag { switch tag {
case "", "_": case "", "_":
ErrorLog("Invalid transition property name") ErrorLog("Invalid transition property name")
default: default:
style.transitions[tag] = parseAnimation(obj) transitions[PropertyName(tag)] = parseAnimation(obj)
return true return true
} }
} }
@ -99,111 +25,140 @@ func (style *viewStyle) setTransition(tag string, value any) bool {
switch value := value.(type) { switch value := value.(type) {
case Params: case Params:
result := true
for tag, val := range value { for tag, val := range value {
tag = strings.ToLower(strings.Trim(tag, " \t")) tag = defaultNormalize(tag)
if tag == "" { if tag == "" {
ErrorLog("Invalid transition property name") ErrorLog("Invalid transition property name")
result = false return false
} else if val == nil { }
delete(style.transitions, tag)
} else if animation, ok := val.(Animation); ok { if val != nil {
style.transitions[tag] = animation if animation, ok := val.(Animation); ok {
} else { transitions[PropertyName(tag)] = animation
notCompatibleType(Transition, val) } else {
result = false notCompatibleType(Transition, val)
return false
}
} }
} }
return result if len(transitions) == 0 {
transitions = nil
}
properties.setRaw(Transition, transitions)
return true
case DataObject: case DataObject:
return setObject(value) if setObject(value) {
properties.setRaw(Transition, transitions)
return true
}
return false
case DataNode: case DataNode:
switch value.Type() { switch value.Type() {
case ObjectNode: case ObjectNode:
return setObject(value.Object()) if setObject(value.Object()) {
properties.setRaw(Transition, transitions)
return true
}
return false
case ArrayNode: case ArrayNode:
result := true
for i := 0; i < value.ArraySize(); i++ { for i := 0; i < value.ArraySize(); i++ {
if obj := value.ArrayElement(i).Object(); obj != nil { if obj := value.ArrayElement(i).Object(); obj != nil {
result = setObject(obj) && result if !setObject(obj) {
return false
}
} else { } else {
notCompatibleType(tag, value.ArrayElement(i)) notCompatibleType(Transition, value.ArrayElement(i))
return false
}
}
if len(transitions) == 0 {
transitions = nil
}
properties.setRaw(Transition, transitions)
return true
}
}
notCompatibleType(Transition, value)
return false
}
/*
func (style *viewStyle) setTransition(tag PropertyName, value any) bool {
setObject := func(obj DataObject) bool {
if obj != nil {
tag := defaultNormalize(tag)
switch tag {
case "", "_":
ErrorLog("Invalid transition property name")
default:
style.transitions[tag] = parseAnimation(obj)
return true
}
}
return false
}
switch value := value.(type) {
case Params:
result := true
for tag, val := range value {
tag = defaultNormalize(tag)
if tag == "" {
ErrorLog("Invalid transition property name")
result = false
} else if val == nil {
delete(style.transitions, tag)
} else if animation, ok := val.(Animation); ok {
style.transitions[tag] = animation
} else {
notCompatibleType(Transition, val)
result = false result = false
} }
} }
return result return result
}
}
notCompatibleType(tag, value) case DataObject:
return false return setObject(value)
}
func (style *viewStyle) setAnimation(tag string, value any) bool { case DataNode:
switch value.Type() {
case ObjectNode:
return setObject(value.Object())
set := func(animations []Animation) { case ArrayNode:
style.properties[tag] = animations result := true
for _, animation := range animations { for i := 0; i < value.ArraySize(); i++ {
animation.used() if obj := value.ArrayElement(i).Object(); obj != nil {
} result = setObject(obj) && result
} } else {
notCompatibleType(tag, value.ArrayElement(i))
switch value := value.(type) { result = false
case Animation: }
set([]Animation{value})
return true
case []Animation:
set(value)
return true
case DataObject:
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
set([]Animation{animation})
return true
}
case DataNode:
animations := []Animation{}
result := true
for i := 0; i < value.ArraySize(); i++ {
if obj := value.ArrayElement(i).Object(); obj != nil {
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
animations = append(animations, anim)
} else {
result = false
} }
} else { return result
notCompatibleType(tag, value.ArrayElement(i))
result = false
} }
} }
if result && len(animations) > 0 {
set(animations) notCompatibleType(tag, value)
} return false
return result
} }
*/
notCompatibleType(tag, value) func viewStyleRemove(properties Properties, tag PropertyName) []PropertyName {
return false
}
func (style *viewStyle) Remove(tag string) {
style.remove(strings.ToLower(tag))
}
func (style *viewStyle) remove(tag string) {
switch tag { switch tag {
case BorderStyle, BorderColor, BorderWidth, case BorderStyle, BorderColor, BorderWidth,
BorderLeft, BorderLeftStyle, BorderLeftColor, BorderLeftWidth, BorderLeft, BorderLeftStyle, BorderLeftColor, BorderLeftWidth,
BorderRight, BorderRightStyle, BorderRightColor, BorderRightWidth, BorderRight, BorderRightStyle, BorderRightColor, BorderRightWidth,
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth, BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth: BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
if border := getBorder(style, Border); border != nil { if border := getBorderProperty(properties, Border); border != nil && border.deleteTag(tag) {
border.delete(tag) return []PropertyName{Border}
} }
case CellBorderStyle, CellBorderColor, CellBorderWidth, case CellBorderStyle, CellBorderColor, CellBorderWidth,
@ -211,58 +166,59 @@ func (style *viewStyle) remove(tag string) {
CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth, CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth,
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth, CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth: CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
if border := getBorder(style, CellBorder); border != nil { if border := getBorderProperty(properties, CellBorder); border != nil && border.deleteTag(tag) {
border.delete(tag) return []PropertyName{CellBorder}
} }
case MarginTop, MarginRight, MarginBottom, MarginLeft, case MarginTop, MarginRight, MarginBottom, MarginLeft:
"top-margin", "right-margin", "bottom-margin", "left-margin": return removeBoundsPropertySide(properties, Margin, tag)
style.removeBoundsSide(Margin, tag)
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft:
"top-padding", "right-padding", "bottom-padding", "left-padding": return removeBoundsPropertySide(properties, Padding, tag)
style.removeBoundsSide(Padding, tag)
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft: case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
style.removeBoundsSide(CellPadding, tag) return removeBoundsPropertySide(properties, CellPadding, tag)
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY, case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
style.removeRadiusElement(tag) if removeRadiusPropertyElement(properties, tag) {
return []PropertyName{Radius, tag}
}
case OutlineStyle, OutlineWidth, OutlineColor: case OutlineStyle, OutlineWidth, OutlineColor:
if outline := getOutline(style); outline != nil { if outline := getOutlineProperty(properties); outline != nil {
outline.Remove(tag) outline.Remove(tag)
if outline.empty() {
properties.setRaw(Outline, nil)
}
return []PropertyName{Outline, tag}
} }
default: default:
style.propertyList.remove(tag) return propertiesRemove(properties, tag)
}
}
func (style *viewStyle) Set(tag string, value any) bool {
return style.set(strings.ToLower(tag), value)
}
func (style *viewStyle) set(tag string, value any) bool {
if value == nil {
style.remove(tag)
return true
} }
return []PropertyName{}
}
func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName {
switch tag { switch tag {
case Shadow, TextShadow: case Shadow, TextShadow:
return style.setShadow(tag, value) if setShadowProperty(style, tag, value) {
return []PropertyName{tag}
}
case Background: case Background:
return style.setBackground(value) return setBackgroundProperty(style, value)
case Border, CellBorder: case Border, CellBorder:
if border := newBorderProperty(value); border != nil { if border := newBorderProperty(value); border != nil {
style.properties[tag] = border style.setRaw(tag, border)
return true return []PropertyName{tag}
} else {
return nil
} }
case BorderStyle, BorderColor, BorderWidth, case BorderStyle, BorderColor, BorderWidth,
@ -271,16 +227,7 @@ func (style *viewStyle) set(tag string, value any) bool {
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth, BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth: BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
border := getBorder(style, Border) return setBorderPropertyElement(style, Border, tag, value)
if border == nil {
border = NewBorder(nil)
if border.Set(tag, value) {
style.properties[Border] = border
return true
}
return false
}
return border.Set(tag, value)
case CellBorderStyle, CellBorderColor, CellBorderWidth, case CellBorderStyle, CellBorderColor, CellBorderWidth,
CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth, CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth,
@ -288,173 +235,211 @@ func (style *viewStyle) set(tag string, value any) bool {
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth, CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth: CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
border := getBorder(style, CellBorder) return setBorderPropertyElement(style, CellBorder, tag, value)
if border == nil {
border = NewBorder(nil)
if border.Set(tag, value) {
style.properties[CellBorder] = border
return true
}
return false
}
return border.Set(tag, value)
case Radius: case Radius:
return style.setRadius(value) return setRadiusProperty(style, value)
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY, case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
return style.setRadiusElement(tag, value) if setRadiusPropertyElement(style, tag, value) {
return []PropertyName{Radius, tag}
}
case Margin, Padding, CellPadding: case Margin, Padding, CellPadding:
return style.setBounds(tag, value) return setBoundsProperty(style, tag, value)
case MarginTop, MarginRight, MarginBottom, MarginLeft, case MarginTop, MarginRight, MarginBottom, MarginLeft:
"top-margin", "right-margin", "bottom-margin", "left-margin": return setBoundsPropertySide(style, Margin, tag, value)
return style.setBoundsSide(Margin, tag, value)
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft:
"top-padding", "right-padding", "bottom-padding", "left-padding": return setBoundsPropertySide(style, Padding, tag, value)
return style.setBoundsSide(Padding, tag, value)
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft: case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
return style.setBoundsSide(CellPadding, tag, value) return setBoundsPropertySide(style, CellPadding, tag, value)
case HeadStyle, FootStyle: case HeadStyle, FootStyle:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
style.properties[tag] = value style.setRaw(tag, value)
return true
case Params: case Params:
style.properties[tag] = value style.setRaw(tag, value)
return true
case DataObject: case DataObject:
if params := value.ToParams(); len(params) > 0 { if params := value.ToParams(); len(params) > 0 {
style.properties[tag] = params style.setRaw(tag, params)
} else {
style.setRaw(tag, nil)
} }
return true
default:
notCompatibleType(tag, value)
return nil
} }
return []PropertyName{tag}
case CellStyle, ColumnStyle, RowStyle: case CellStyle, ColumnStyle, RowStyle:
switch value := value.(type) { switch value := value.(type) {
case string: case string:
style.properties[tag] = value style.setRaw(tag, value)
return true
case Params: case Params:
style.properties[tag] = value style.setRaw(tag, value)
return true
case DataObject: case DataObject:
if params := value.ToParams(); len(params) > 0 { if params := value.ToParams(); len(params) > 0 {
style.properties[tag] = params style.setRaw(tag, params)
} else {
style.setRaw(tag, nil)
} }
return true
case DataNode: case DataNode:
switch value.Type() { switch value.Type() {
case TextNode: case TextNode:
if text := value.Text(); text != "" { if text := value.Text(); text != "" {
style.properties[tag] = text style.setRaw(tag, text)
} else {
style.setRaw(tag, nil)
} }
return true
case ObjectNode: case ObjectNode:
if obj := value.Object(); obj != nil { if obj := value.Object(); obj != nil {
if params := obj.ToParams(); len(params) > 0 { if params := obj.ToParams(); len(params) > 0 {
style.properties[tag] = params style.setRaw(tag, params)
} else {
style.setRaw(tag, nil)
} }
return true } else {
notCompatibleType(tag, value)
return nil
} }
case ArrayNode: default:
// TODO notCompatibleType(tag, value)
return nil
} }
default:
notCompatibleType(tag, value)
return nil
} }
return []PropertyName{tag}
case Outline: case Outline:
return style.setOutline(value) return setOutlineProperty(style, value)
case OutlineStyle, OutlineWidth, OutlineColor: case OutlineStyle, OutlineWidth, OutlineColor:
if outline := getOutline(style); outline != nil { if outline := getOutlineProperty(style); outline != nil {
return outline.Set(tag, value) if outline.Set(tag, value) {
return []PropertyName{Outline, tag}
}
} else {
outline := NewOutlineProperty(nil)
if outline.Set(tag, value) {
style.setRaw(Outline, outline)
return []PropertyName{Outline, tag}
}
} }
style.properties[Outline] = NewOutlineProperty(Params{tag: value}) return nil
return true
case TransformTag: case TransformTag:
return style.setTransform(value) if setTransformProperty(style, value) {
return []PropertyName{TransformTag}
} else {
return nil
}
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, case Perspective, RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
TranslateX, TranslateY, TranslateZ: TranslateX, TranslateY, TranslateZ:
return style.setTransformProperty(tag, value) return setTransformPropertyElement(style, tag, value)
case Orientation: case Orientation:
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
switch strings.ToLower(text) { switch strings.ToLower(text) {
case "vertical": case "vertical":
style.properties[Orientation] = TopDownOrientation style.setRaw(Orientation, TopDownOrientation)
return true return []PropertyName{Orientation}
case "horizontal": case "horizontal":
style.properties[Orientation] = StartToEndOrientation style.setRaw(Orientation, StartToEndOrientation)
return true return []PropertyName{Orientation}
} }
} }
case TextWeight: case TextWeight:
if n, ok := value.(int); ok && n >= 100 && n%100 == 0 { if n, ok := value.(int); ok {
n /= 100 if n >= 100 && n%100 == 0 {
if n > 0 && n <= 9 { n /= 100
style.properties[TextWeight] = n if n > 0 && n <= 9 {
return true style.setRaw(TextWeight, n)
return []PropertyName{TextWeight}
}
} }
} }
case Row, Column: case Row, Column:
return style.setRange(tag, value) return setRangeProperty(style, tag, value)
case CellWidth, CellHeight: case CellWidth, CellHeight:
return style.setGridCellSize(tag, value) return setGridCellSize(style, tag, value)
case ColumnSeparator: case ColumnSeparator:
if separator := newColumnSeparatorProperty(value); separator != nil { if separator := newColumnSeparatorProperty(value); separator != nil {
style.properties[ColumnSeparator] = separator style.setRaw(ColumnSeparator, separator)
return true return []PropertyName{tag}
} }
return false return nil
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor: case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
var separator ColumnSeparatorProperty = nil if separator := getColumnSeparatorProperty(style); separator != nil {
if val, ok := style.properties[ColumnSeparator]; ok { if separator.Set(tag, value) {
separator = val.(ColumnSeparatorProperty) return []PropertyName{ColumnSeparator, tag}
}
} else {
separator := newColumnSeparatorProperty(nil)
if separator.Set(tag, value) {
style.setRaw(ColumnSeparator, separator)
return []PropertyName{ColumnSeparator, tag}
}
} }
if separator == nil { return nil
separator = newColumnSeparatorProperty(nil)
}
if separator.Set(tag, value) {
style.properties[ColumnSeparator] = separator
return true
}
return false
case Clip, ShapeOutside: case Clip, ShapeOutside:
return style.setClipShape(tag, value) return setClipShapeProperty(style, tag, value)
case Filter, BackdropFilter: case Filter, BackdropFilter:
return style.setFilter(tag, value) return setFilterProperty(style, tag, value)
case Transition: case Transition:
return style.setTransition(tag, value) if setTransitionProperty(style, value) {
return []PropertyName{tag}
} else {
return nil
}
case AnimationTag: case AnimationTag:
return style.setAnimation(tag, value) if setAnimationProperty(style, tag, value) {
return []PropertyName{tag}
} else {
return nil
}
} }
return style.propertyList.set(tag, value) return propertiesSet(style, tag, value)
}
func (style *viewStyle) Set(tag PropertyName, value any) bool {
if value == nil {
style.Remove(tag)
return true
}
return viewStyleSet(style, normalizeViewStyleTag(tag), value) != nil
}
func (style *viewStyle) Remove(tag PropertyName) {
viewStyleRemove(style, normalizeViewStyleTag(tag))
} }

View File

@ -8,87 +8,6 @@ import (
// Constants for [Transform] specific properties // Constants for [Transform] specific properties
const ( const (
// Perspective is the constant for "perspective" property tag.
//
// Used by `View`.
// Distance between the z-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. The default value is 0 (no 3D effects).
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
Perspective = "perspective"
// PerspectiveOriginX is the constant for "perspective-origin-x" property tag.
//
// Used by `View`.
// x-coordinate of the position at which the viewer is looking. It is used as the vanishing point by the "perspective"
// property. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
PerspectiveOriginX = "perspective-origin-x"
// PerspectiveOriginY is the constant for "perspective-origin-y" property tag.
//
// Used by `View`.
// y-coordinate of the position at which the viewer is looking. It is used as the vanishing point by the "perspective"
// property. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
PerspectiveOriginY = "perspective-origin-y"
// BackfaceVisible is the constant for "backface-visibility" property tag.
//
// Used by `View`.
// Controls whether the back face of a view is visible when turned towards the user. Default value is `true`.
//
// Supported types: `bool`, `int`, `string`.
//
// Values:
// `true` or `1` or "true", "yes", "on", "1" - Back face is visible when turned towards the user.
// `false` or `0` or "false", "no", "off", "0" - Back face is hidden, effectively making the view invisible when turned away from the user.
BackfaceVisible = "backface-visibility"
// OriginX is the constant for "origin-x" property tag.
//
// Used by `View`.
// x-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginX = "origin-x"
// OriginY is the constant for "origin-y" property tag.
//
// Used by `View`.
// y-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginY = "origin-y"
// OriginZ is the constant for "origin-z" property tag.
//
// Used by `View`.
// z-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginZ = "origin-z"
// TransformTag is the constant for "transform" property tag. // TransformTag is the constant for "transform" property tag.
// //
// Used by `View`. // Used by `View`.
@ -101,7 +20,88 @@ const (
// Conversion rules: // Conversion rules:
// `Transform` - stored as is, no conversion performed. // `Transform` - 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}".
TransformTag = "transform" TransformTag PropertyName = "transform"
// Perspective is the constant for "perspective" property tag.
//
// Used by `View`.
// Distance between the z-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. The default value is 0 (no 3D effects).
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
Perspective PropertyName = "perspective"
// PerspectiveOriginX is the constant for "perspective-origin-x" property tag.
//
// Used by `View`.
// x-coordinate of the position at which the viewer is looking. It is used as the vanishing point by the "perspective"
// property. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
PerspectiveOriginX PropertyName = "perspective-origin-x"
// PerspectiveOriginY is the constant for "perspective-origin-y" property tag.
//
// Used by `View`.
// y-coordinate of the position at which the viewer is looking. It is used as the vanishing point by the "perspective"
// property. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
PerspectiveOriginY PropertyName = "perspective-origin-y"
// BackfaceVisible is the constant for "backface-visibility" property tag.
//
// Used by `View`.
// Controls whether the back face of a view is visible when turned towards the user. Default value is `true`.
//
// Supported types: `bool`, `int`, `string`.
//
// Values:
// `true` or `1` or "true", "yes", "on", "1" - Back face is visible when turned towards the user.
// `false` or `0` or "false", "no", "off", "0" - Back face is hidden, effectively making the view invisible when turned away from the user.
BackfaceVisible PropertyName = "backface-visibility"
// OriginX is the constant for "origin-x" property tag.
//
// Used by `View`.
// x-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginX PropertyName = "origin-x"
// OriginY is the constant for "origin-y" property tag.
//
// Used by `View`.
// y-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginY PropertyName = "origin-y"
// OriginZ is the constant for "origin-z" property tag.
//
// Used by `View`.
// z-coordinate of the point around which a view transformation is applied. The default value is 50%.
//
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
OriginZ PropertyName = "origin-z"
// TranslateX is the constant for "translate-x" property tag. // TranslateX is the constant for "translate-x" property tag.
// //
@ -122,7 +122,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TranslateX = "translate-x" TranslateX PropertyName = "translate-x"
// TranslateY is the constant for "translate-y" property tag. // TranslateY is the constant for "translate-y" property tag.
// //
@ -143,7 +143,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TranslateY = "translate-y" TranslateY PropertyName = "translate-y"
// TranslateZ is the constant for "translate-z" property tag. // TranslateZ is the constant for "translate-z" property tag.
// //
@ -164,7 +164,7 @@ const (
// //
// Internal type is `SizeUnit`, other types converted to it during assignment. // Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details. // See `SizeUnit` description for more details.
TranslateZ = "translate-z" TranslateZ PropertyName = "translate-z"
// ScaleX is the constant for "scale-x" property tag. // ScaleX is the constant for "scale-x" property tag.
// //
@ -185,7 +185,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
ScaleX = "scale-x" ScaleX PropertyName = "scale-x"
// ScaleY is the constant for "scale-y" property tag. // ScaleY is the constant for "scale-y" property tag.
// //
@ -206,7 +206,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
ScaleY = "scale-y" ScaleY PropertyName = "scale-y"
// ScaleZ is the constant for "scale-z" property tag. // ScaleZ is the constant for "scale-z" property tag.
// //
@ -227,7 +227,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
ScaleZ = "scale-z" ScaleZ PropertyName = "scale-z"
// Rotate is the constant for "rotate" property tag. // Rotate is the constant for "rotate" property tag.
// //
@ -260,7 +260,7 @@ const (
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created. // `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
// `float` - a new `AngleUnit` value will be created with `Radian` as a type. // `float` - a new `AngleUnit` value will be created with `Radian` as a type.
// `int` - a new `AngleUnit` value will be created with `Radian` as a type. // `int` - a new `AngleUnit` value will be created with `Radian` as a type.
Rotate = "rotate" Rotate PropertyName = "rotate"
// RotateX is the constant for "rotate-x" property tag. // RotateX is the constant for "rotate-x" property tag.
// //
@ -279,7 +279,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
RotateX = "rotate-x" RotateX PropertyName = "rotate-x"
// RotateY is the constant for "rotate-y" property tag. // RotateY is the constant for "rotate-y" property tag.
// //
@ -298,7 +298,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
RotateY = "rotate-y" RotateY PropertyName = "rotate-y"
// RotateZ is the constant for "rotate-z" property tag. // RotateZ is the constant for "rotate-z" property tag.
// //
@ -317,7 +317,7 @@ const (
// Supported types: `float`, `int`, `string`. // Supported types: `float`, `int`, `string`.
// //
// Internal type is `float`, other types converted to it during assignment. // Internal type is `float`, other types converted to it during assignment.
RotateZ = "rotate-z" RotateZ PropertyName = "rotate-z"
// SkewX is the constant for "skew-x" property tag. // SkewX is the constant for "skew-x" property tag.
// //
@ -350,7 +350,7 @@ const (
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created. // `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
// `float` - a new `AngleUnit` value will be created with `Radian` as a type. // `float` - a new `AngleUnit` value will be created with `Radian` as a type.
// `int` - a new `AngleUnit` value will be created with `Radian` as a type. // `int` - a new `AngleUnit` value will be created with `Radian` as a type.
SkewX = "skew-x" SkewX PropertyName = "skew-x"
// SkewY is the constant for "skew-y" property tag. // SkewY is the constant for "skew-y" property tag.
// //
@ -383,44 +383,97 @@ const (
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created. // `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
// `float` - a new `AngleUnit` value will be created with `Radian` as a type. // `float` - a new `AngleUnit` value will be created with `Radian` as a type.
// `int` - a new `AngleUnit` value will be created with `Radian` as a type. // `int` - a new `AngleUnit` value will be created with `Radian` as a type.
SkewY = "skew-y" SkewY PropertyName = "skew-y"
) )
// Transform interface specifies view transformation parameters: the x-, y-, and z-axis translation values, // Transform interface specifies view transformation parameters: the x-, y-, and z-axis translation values,
// the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate, // the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate,
// the angle of the view rotation. // the angle of the view rotation.
// Valid property tags: TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"), // Valid property tags: Perspective ("perspective"), TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"),
// ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"), // ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"),
// RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y") // RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y")
type Transform interface { type Transform interface {
Properties Properties
fmt.Stringer fmt.Stringer
stringWriter stringWriter
transformCSS(session Session, transform3D bool) string transformCSS(session Session) string
} }
type transformData struct { type transformData struct {
propertyList dataProperty
} }
// NewTransform creates a new transform property data and return its interface // NewTransform creates a new transform property data and return its interface
func NewTransform(params Params) Transform { func NewTransform(params Params) Transform {
transform := new(transformData) transform := new(transformData)
transform.properties = map[string]any{} transform.init()
for tag, value := range params { for tag, value := range params {
transform.Set(tag, value) transform.Set(tag, value)
} }
return transform return transform
} }
func (style *viewStyle) setTransform(value any) bool { func (transform *transformData) init() {
transform.dataProperty.init()
transform.set = transformSet
transform.supportedProperties = []PropertyName{
RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
Perspective, TranslateX, TranslateY, TranslateZ,
}
}
func (transform *transformData) String() string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
transform.writeString(buffer, "")
return buffer.String()
}
func (transform *transformData) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ")
comma := false
for _, tag := range transform.supportedProperties {
if value, ok := transform.properties[tag]; ok {
if comma {
buffer.WriteString(", ")
}
buffer.WriteString(string(tag))
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent)
comma = true
}
}
buffer.WriteString(" }")
}
func transformSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case RotateX, RotateY, RotateZ:
return setFloatProperty(properties, tag, value, 0, 1)
case Rotate, SkewX, SkewY:
return setAngleProperty(properties, tag, value)
case ScaleX, ScaleY, ScaleZ:
return setFloatProperty(properties, tag, value, -math.MaxFloat64, math.MaxFloat64)
case Perspective, TranslateX, TranslateY, TranslateZ:
return setSizeProperty(properties, tag, value)
}
return nil
}
func setTransformProperty(properties Properties, value any) bool {
setObject := func(obj DataObject) bool { setObject := func(obj DataObject) bool {
transform := NewTransform(nil) transform := NewTransform(nil)
ok := true ok := true
for i := 0; i < obj.PropertyCount(); i++ { for i := 0; i < obj.PropertyCount(); i++ {
if prop := obj.Property(i); prop.Type() == TextNode { if prop := obj.Property(i); prop.Type() == TextNode {
if !transform.Set(prop.Tag(), prop.Text()) { if !transform.Set(PropertyName(prop.Tag()), prop.Text()) {
ok = false ok = false
} }
} else { } else {
@ -428,17 +481,17 @@ func (style *viewStyle) setTransform(value any) bool {
} }
} }
if !ok && len(transform.AllTags()) == 0 { if !ok && transform.empty() {
return false return false
} }
style.properties[TransformTag] = transform properties.setRaw(TransformTag, transform)
return true return true
} }
switch value := value.(type) { switch value := value.(type) {
case Transform: case Transform:
style.properties[TransformTag] = value properties.setRaw(TransformTag, value)
return true return true
case DataObject: case DataObject:
@ -462,8 +515,8 @@ func (style *viewStyle) setTransform(value any) bool {
return false return false
} }
func (style *viewStyle) transformProperty() Transform { func getTransformProperty(properties Properties) Transform {
if val, ok := style.properties[TransformTag]; ok { if val := properties.getRaw(TransformTag); val != nil {
if transform, ok := val.(Transform); ok { if transform, ok := val.(Transform); ok {
return transform return transform
} }
@ -471,80 +524,26 @@ func (style *viewStyle) transformProperty() Transform {
return nil return nil
} }
func (style *viewStyle) setTransformProperty(tag string, value any) bool { func setTransformPropertyElement(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag { switch tag {
case 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:
if transform := style.transformProperty(); transform != nil { if transform := getTransformProperty(properties); transform != nil {
return transform.Set(tag, value) if result := transformSet(transform, tag, value); result != nil {
} result = append(result, TransformTag)
}
transform := NewTransform(nil) } else {
if !transform.Set(tag, value) { transform := NewTransform(nil)
return false if result := transformSet(transform, tag, value); result != nil {
} properties.setRaw(TransformTag, transform)
result = append(result, TransformTag)
style.properties[TransformTag] = transform
return true
}
ErrorLogF(`"Transform" interface does not support the "%s" property`, tag)
return false
}
func (transform *transformData) String() string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
transform.writeString(buffer, "")
return buffer.String()
}
func (transform *transformData) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ")
comma := false
for _, tag := range []string{SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ} {
if value, ok := transform.properties[tag]; ok {
if comma {
buffer.WriteString(", ")
} }
buffer.WriteString(tag)
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent)
comma = true
} }
}
buffer.WriteString(" }")
}
func (transform *transformData) Set(tag string, value any) bool { default:
return transform.set(strings.ToLower(tag), value) ErrorLogF(`"Transform" interface does not support the "%s" property`, tag)
}
func (transform *transformData) set(tag string, value any) bool {
if value == nil {
_, exist := transform.properties[tag]
if exist {
delete(transform.properties, tag)
}
return exist
} }
switch tag { return nil
case RotateX, RotateY, RotateZ:
return transform.setFloatProperty(tag, value, 0, 1)
case Rotate, SkewX, SkewY:
return transform.setAngleProperty(tag, value)
case ScaleX, ScaleY, ScaleZ:
return transform.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64)
case TranslateX, TranslateY, TranslateZ:
return transform.setSizeProperty(tag, value)
}
return false
} }
func getTransform3D(style Properties, session Session) bool { func getTransform3D(style Properties, session Session) bool {
@ -578,61 +577,75 @@ func (transform *transformData) getTranslate(session Session) (SizeUnit, SizeUni
return x, y, z return x, y, z
} }
func (transform *transformData) transformCSS(session Session, transform3D bool) string { func (transform *transformData) transformCSS(session Session) string {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
if perspective, ok := sizeProperty(transform, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 {
buffer.WriteString(`perspective(`)
buffer.WriteString(perspective.cssString("0", session))
buffer.WriteString(") ")
}
skewX, skewY, skewOK := transform.getSkew(session) skewX, skewY, skewOK := transform.getSkew(session)
if skewOK { if skewOK {
buffer.WriteString(`skew(`) buffer.WriteString(`skew(`)
buffer.WriteString(skewX.cssString()) buffer.WriteString(skewX.cssString())
buffer.WriteRune(',') buffer.WriteRune(',')
buffer.WriteString(skewY.cssString()) buffer.WriteString(skewY.cssString())
buffer.WriteRune(')') buffer.WriteString(") ")
} }
x, y, z := transform.getTranslate(session) x, y, z := transform.getTranslate(session)
if z.Type != Auto && z.Value != 0 {
buffer.WriteString(`translate3d(`)
buffer.WriteString(x.cssString("0", session))
buffer.WriteRune(',')
buffer.WriteString(y.cssString("0", session))
buffer.WriteRune(',')
buffer.WriteString(z.cssString("0", session))
buffer.WriteString(") ")
} else if (x.Type != Auto && x.Value != 0) || (y.Type != Auto && y.Value != 0) {
buffer.WriteString(`translate(`)
buffer.WriteString(x.cssString("0", session))
buffer.WriteRune(',')
buffer.WriteString(y.cssString("0", session))
buffer.WriteString(") ")
}
scaleX, okScaleX := floatTextProperty(transform, ScaleX, session, 1) scaleX, okScaleX := floatTextProperty(transform, ScaleX, session, 1)
scaleY, okScaleY := floatTextProperty(transform, ScaleY, session, 1) scaleY, okScaleY := floatTextProperty(transform, ScaleY, session, 1)
scaleZ, okScaleZ := floatTextProperty(transform, ScaleZ, session, 1)
if okScaleZ {
if transform3D { buffer.WriteString(`scale3d(`)
if x.Type != Auto || y.Type != Auto || z.Type != Auto { buffer.WriteString(scaleX)
if buffer.Len() > 0 { buffer.WriteRune(',')
buffer.WriteRune(' ') buffer.WriteString(scaleY)
} buffer.WriteRune(',')
buffer.WriteString(`translate3d(`) buffer.WriteString(scaleZ)
buffer.WriteString(x.cssString("0", session)) buffer.WriteString(") ")
buffer.WriteRune(',')
buffer.WriteString(y.cssString("0", session))
buffer.WriteRune(',')
buffer.WriteString(z.cssString("0", session))
buffer.WriteRune(')')
}
scaleZ, okScaleZ := floatTextProperty(transform, ScaleZ, session, 1) } else if okScaleX || okScaleY {
if okScaleX || okScaleY || okScaleZ {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString(`scale3d(`)
buffer.WriteString(scaleX)
buffer.WriteRune(',')
buffer.WriteString(scaleY)
buffer.WriteRune(',')
buffer.WriteString(scaleZ)
buffer.WriteRune(')')
}
if angle, ok := angleProperty(transform, Rotate, session); ok { buffer.WriteString(`scale(`)
rotateX, _ := floatTextProperty(transform, RotateX, session, 1) buffer.WriteString(scaleX)
rotateY, _ := floatTextProperty(transform, RotateY, session, 1) buffer.WriteRune(',')
rotateZ, _ := floatTextProperty(transform, RotateZ, session, 1) buffer.WriteString(scaleY)
buffer.WriteString(") ")
}
if angle, ok := angleProperty(transform, Rotate, session); ok {
rotateX, xOK := floatTextProperty(transform, RotateX, session, 1)
rotateY, yOK := floatTextProperty(transform, RotateY, session, 1)
rotateZ, zOK := floatTextProperty(transform, RotateZ, session, 1)
if xOK || yOK || zOK {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString(`rotate3d(`) buffer.WriteString(`rotate3d(`)
buffer.WriteString(rotateX) buffer.WriteString(rotateX)
buffer.WriteRune(',') buffer.WriteRune(',')
@ -641,90 +654,56 @@ func (transform *transformData) transformCSS(session Session, transform3D bool)
buffer.WriteString(rotateZ) buffer.WriteString(rotateZ)
buffer.WriteRune(',') buffer.WriteRune(',')
buffer.WriteString(angle.cssString()) buffer.WriteString(angle.cssString())
buffer.WriteRune(')') buffer.WriteString(") ")
}
} else { } else {
if x.Type != Auto || y.Type != Auto {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString(`translate(`)
buffer.WriteString(x.cssString("0", session))
buffer.WriteRune(',')
buffer.WriteString(y.cssString("0", session))
buffer.WriteRune(')')
}
if okScaleX || okScaleY {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString(`scale(`)
buffer.WriteString(scaleX)
buffer.WriteRune(',')
buffer.WriteString(scaleY)
buffer.WriteRune(')')
}
if angle, ok := angleProperty(transform, Rotate, session); ok {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString(`rotate(`) buffer.WriteString(`rotate(`)
buffer.WriteString(angle.cssString()) buffer.WriteString(angle.cssString())
buffer.WriteRune(')') buffer.WriteString(") ")
} }
} }
return buffer.String() length := buffer.Len()
if length == 0 {
return ""
}
result := buffer.String()
return result[:length-1]
} }
func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) { func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) {
transform3D := getTransform3D(style, session) x, y := getPerspectiveOrigin(style, session)
if transform3D { if x.Type != Auto || y.Type != Auto {
if perspective, ok := sizeProperty(style, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 { builder.addValues(`perspective-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session))
builder.add(`perspective`, perspective.cssString("0", session)) }
}
x, y := getPerspectiveOrigin(style, session) if backfaceVisible, ok := boolProperty(style, BackfaceVisible, session); ok {
if x.Type != Auto || y.Type != Auto { if backfaceVisible {
builder.addValues(`perspective-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session)) builder.add(`backface-visibility`, `visible`)
} } else {
builder.add(`backface-visibility`, `hidden`)
if backfaceVisible, ok := boolProperty(style, BackfaceVisible, session); ok {
if backfaceVisible {
builder.add(`backface-visibility`, `visible`)
} else {
builder.add(`backface-visibility`, `hidden`)
}
}
x, y, z := getOrigin(style, session)
if x.Type != Auto || y.Type != Auto || z.Type != Auto {
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session), z.cssString("0", session))
}
} else {
x, y, _ := getOrigin(style, session)
if x.Type != Auto || y.Type != Auto {
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session))
} }
} }
if transform := style.transformProperty(); transform != nil { x, y, z := getOrigin(style, session)
builder.add(`transform`, transform.transformCSS(session, transform3D)) if z.Type != Auto && z.Value != 0 {
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session), z.cssString("0", session))
} else if x.Type != Auto || y.Type != Auto {
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session))
}
if transform := getTransformProperty(style); transform != nil {
builder.add(`transform`, transform.transformCSS(session))
} }
} }
func (view *viewData) updateTransformProperty(tag string) bool { /*
func (view *viewData) updateTransformProperty(tag PropertyName) bool {
htmlID := view.htmlID() htmlID := view.htmlID()
session := view.session session := view.session
switch tag { switch tag {
case Perspective:
updateCSSStyle(htmlID, session)
case PerspectiveOriginX, PerspectiveOriginY: case PerspectiveOriginX, PerspectiveOriginY:
if getTransform3D(view, session) { if getTransform3D(view, session) {
x, y := GetPerspectiveOrigin(view) x, y := GetPerspectiveOrigin(view)
@ -738,31 +717,27 @@ func (view *viewData) updateTransformProperty(tag string) bool {
case BackfaceVisible: case BackfaceVisible:
if getTransform3D(view, session) { if getTransform3D(view, session) {
if GetBackfaceVisible(view) { if GetBackfaceVisible(view) {
session.updateCSSProperty(htmlID, BackfaceVisible, "visible") session.updateCSSProperty(htmlID, string(BackfaceVisible), "visible")
} else { } else {
session.updateCSSProperty(htmlID, BackfaceVisible, "hidden") session.updateCSSProperty(htmlID, string(BackfaceVisible), "hidden")
} }
} }
case OriginX, OriginY, OriginZ: case OriginX, OriginY, OriginZ:
x, y, z := getOrigin(view, session) x, y, z := getOrigin(view, session)
value := "" value := ""
if getTransform3D(view, session) {
if x.Type != Auto || y.Type != Auto || z.Type != Auto { if z.Type != Auto {
value = x.cssString("50%", session) + " " + y.cssString("50%", session) + " " + z.cssString("50%", session) value = x.cssString("50%", session) + " " + y.cssString("50%", session) + " " + z.cssString("50%", session)
} } else if x.Type != Auto || y.Type != Auto {
} else { value = x.cssString("50%", session) + " " + y.cssString("50%", session)
if x.Type != Auto || y.Type != Auto {
value = x.cssString("50%", session) + " " + y.cssString("50%", session)
}
} }
session.updateCSSProperty(htmlID, "transform-origin", value) session.updateCSSProperty(htmlID, "transform-origin", value)
case TransformTag, SkewX, SkewY, TranslateX, TranslateY, TranslateZ, case TransformTag, SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ: ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ:
if transform := view.transformProperty(); transform != nil { if transform := getTransformProperty(view); transform != nil {
transform3D := getTransform3D(view, session) session.updateCSSProperty(htmlID, "transform", transform.transformCSS(session))
session.updateCSSProperty(htmlID, "transform", transform.transformCSS(session, transform3D))
} else { } else {
session.updateCSSProperty(htmlID, "transform", "") session.updateCSSProperty(htmlID, "transform", "")
} }
@ -773,3 +748,4 @@ func (view *viewData) updateTransformProperty(tag string) bool {
return true return true
} }
*/

View File

@ -3,7 +3,7 @@ package rui
// Get returns a value of the property with name "tag" of the "rootView" subview with "viewID" id value. // Get returns a value of the property with name "tag" of the "rootView" subview with "viewID" id value.
// The type of return value depends on the property. // The type of return value depends on the property.
// If the subview don't exists or the property is not set then nil is returned. // If the subview don't exists or the property is not set then nil is returned.
func Get(rootView View, viewID, tag string) any { func Get(rootView View, viewID string, tag PropertyName) any {
var view View var view View
if viewID != "" { if viewID != "" {
view = ViewByID(rootView, viewID) view = ViewByID(rootView, viewID)
@ -19,7 +19,7 @@ func Get(rootView View, viewID, tag string) any {
// Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result: // Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
// true - success, // true - success,
// false - error (incompatible type or invalid format of a string value, see AppLog). // false - error (incompatible type or invalid format of a string value, see AppLog).
func Set(rootView View, viewID, tag string, value any) bool { func Set(rootView View, viewID string, tag PropertyName, value any) bool {
var view View var view View
if viewID != "" { if viewID != "" {
view = ViewByID(rootView, viewID) view = ViewByID(rootView, viewID)
@ -34,7 +34,7 @@ func Set(rootView View, viewID, tag string, value any) bool {
// SetChangeListener sets a listener for changing a subview property value. // SetChangeListener sets a listener for changing a subview property value.
// If the second argument (subviewID) is not specified or it is "" then a listener for the first argument (view) is set // If the second argument (subviewID) is not specified or it is "" then a listener for the first argument (view) is set
func SetChangeListener(view View, viewID, tag string, listener func(View, string)) { func SetChangeListener(view View, viewID string, tag PropertyName, listener func(View, PropertyName)) {
if viewID != "" { if viewID != "" {
view = ViewByID(view, viewID) view = ViewByID(view, viewID)
} }
@ -294,7 +294,7 @@ func GetBorder(view View, subviewID ...string) ViewBorders {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
if view != nil { if view != nil {
if border := getBorder(view, Border); border != nil { if border := getBorderProperty(view, Border); border != nil {
return border.ViewBorders(view.Session()) return border.ViewBorders(view.Session())
} }
} }
@ -320,7 +320,7 @@ func GetOutline(view View, subviewID ...string) ViewOutline {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
if view != nil { if view != nil {
if outline := getOutline(view); outline != nil { if outline := getOutlineProperty(view); outline != nil {
return outline.ViewOutline(view.Session()) return outline.ViewOutline(view.Session())
} }
} }
@ -706,9 +706,9 @@ func GetNotTranslate(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, NotTranslate, true) return boolStyledProperty(view, subviewID, NotTranslate, true)
} }
func valueFromStyle(view View, tag string) any { func valueFromStyle(view View, tag PropertyName) any {
session := view.Session() session := view.Session()
getValue := func(styleTag string) any { getValue := func(styleTag PropertyName) any {
if style, ok := stringProperty(view, styleTag, session); ok { if style, ok := stringProperty(view, styleTag, session); ok {
if style, ok := session.resolveConstants(style); ok { if style, ok := session.resolveConstants(style); ok {
return session.styleProperty(style, tag) return session.styleProperty(style, tag)
@ -725,7 +725,7 @@ func valueFromStyle(view View, tag string) any {
return getValue(Style) return getValue(Style)
} }
func sizeStyledProperty(view View, subviewID []string, tag string, inherit bool) SizeUnit { func sizeStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) SizeUnit {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -749,7 +749,7 @@ func sizeStyledProperty(view View, subviewID []string, tag string, inherit bool)
return AutoSize() return AutoSize()
} }
func enumStyledProperty(view View, subviewID []string, tag string, defaultValue int, inherit bool) int { func enumStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue int, inherit bool) int {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -773,7 +773,7 @@ func enumStyledProperty(view View, subviewID []string, tag string, defaultValue
return defaultValue return defaultValue
} }
func boolStyledProperty(view View, subviewID []string, tag string, inherit bool) bool { func boolStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) bool {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -798,7 +798,7 @@ func boolStyledProperty(view View, subviewID []string, tag string, inherit bool)
return false return false
} }
func intStyledProperty(view View, subviewID []string, tag string, defaultValue int) int { func intStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue int) int {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -815,7 +815,7 @@ func intStyledProperty(view View, subviewID []string, tag string, defaultValue i
return defaultValue return defaultValue
} }
func floatStyledProperty(view View, subviewID []string, tag string, defaultValue float64) float64 { func floatStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue float64) float64 {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }
@ -831,7 +831,7 @@ func floatStyledProperty(view View, subviewID []string, tag string, defaultValue
return defaultValue return defaultValue
} }
func colorStyledProperty(view View, subviewID []string, tag string, inherit bool) Color { func colorStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) Color {
if len(subviewID) > 0 && subviewID[0] != "" { if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0]) view = ViewByID(view, subviewID[0])
} }

View File

@ -24,6 +24,8 @@ type ViewsContainer interface {
// ViewIndex returns the index of view, -1 overwise // ViewIndex returns the index of view, -1 overwise
ViewIndex(view View) int ViewIndex(view View) int
setContent(value any) bool
} }
type viewsContainerData struct { type viewsContainerData struct {
@ -36,10 +38,10 @@ func (container *viewsContainerData) init(session Session) {
container.viewData.init(session) container.viewData.init(session)
container.tag = "ViewsContainer" container.tag = "ViewsContainer"
container.views = []View{} container.views = []View{}
} container.getFunc = container.get
container.set = container.setFunc
func (container *viewsContainerData) String() string { container.remove = container.removeFunc
return getViewString(container, nil) container.changed = viewsContainerPropertyChanged
} }
func (container *viewsContainerData) setParentID(parentID string) { func (container *viewsContainerData) setParentID(parentID string) {
@ -62,6 +64,13 @@ func (container *viewsContainerData) Views() []View {
return []View{} return []View{}
} }
func viewsContainerContentChanged(container *viewsContainerData) {
updateInnerHTML(container.htmlID(), container.Session())
if listener, ok := container.changeListener[Content]; ok {
listener(container, Content)
}
}
// Append appends a view to the end of the list of a view children // Append appends a view to the end of the list of a view children
func (container *viewsContainerData) Append(view View) { func (container *viewsContainerData) Append(view View) {
if view != nil { if view != nil {
@ -72,8 +81,7 @@ func (container *viewsContainerData) Append(view View) {
} else { } else {
container.views = append(container.views, view) container.views = append(container.views, view)
} }
updateInnerHTML(container.htmlID(), container.session) viewsContainerContentChanged(container)
container.propertyChangedEvent(Content)
} }
} }
@ -86,13 +94,11 @@ func (container *viewsContainerData) Insert(view View, index int) {
} else if index > 0 { } else if index > 0 {
view.setParentID(htmlID) view.setParentID(htmlID)
container.views = append(container.views[:index], append([]View{view}, container.views[index:]...)...) container.views = append(container.views[:index], append([]View{view}, container.views[index:]...)...)
updateInnerHTML(container.htmlID(), container.session) viewsContainerContentChanged(container)
container.propertyChangedEvent(Content)
} else { } else {
view.setParentID(htmlID) view.setParentID(htmlID)
container.views = append([]View{view}, container.views...) container.views = append([]View{view}, container.views...)
updateInnerHTML(container.htmlID(), container.session) viewsContainerContentChanged(container)
container.propertyChangedEvent(Content)
} }
} }
} }
@ -119,8 +125,7 @@ func (container *viewsContainerData) RemoveView(index int) View {
} }
view.setParentID("") view.setParentID("")
updateInnerHTML(container.htmlID(), container.session) viewsContainerContentChanged(container)
container.propertyChangedEvent(Content)
return view return view
} }
@ -158,67 +163,60 @@ func viewFromTextValue(text string, session Session) View {
return NewTextView(session, Params{Text: text}) return NewTextView(session, Params{Text: text})
} }
func (container *viewsContainerData) Remove(tag string) { func (container *viewsContainerData) removeFunc(view View, tag PropertyName) []PropertyName {
container.remove(strings.ToLower(tag))
}
func (container *viewsContainerData) remove(tag string) {
switch tag { switch tag {
case Content: case Content:
if container.views == nil || len(container.views) > 0 { if len(container.views) > 0 {
container.views = []View{} container.views = []View{}
updateInnerHTML(container.htmlID(), container.Session()) return []PropertyName{tag}
} }
container.propertyChangedEvent(Content) return []PropertyName{}
case Disabled: case Disabled:
if _, ok := container.properties[Disabled]; ok { if view.getRaw(Disabled) != nil {
delete(container.properties, Disabled) view.setRaw(Disabled, nil)
if container.views != nil { for _, view := range container.views {
for _, view := range container.views { view.Remove(Disabled)
view.Remove(Disabled)
}
} }
container.propertyChangedEvent(tag) return []PropertyName{tag}
} }
default:
container.viewData.remove(tag)
} }
return viewRemove(view, tag)
} }
func (container *viewsContainerData) Set(tag string, value any) bool { func (container *viewsContainerData) setFunc(self View, tag PropertyName, value any) []PropertyName {
return container.set(strings.ToLower(tag), value)
}
func (container *viewsContainerData) set(tag string, value any) bool {
if value == nil {
container.remove(tag)
return true
}
switch tag { switch tag {
case Content: case Content:
return container.setContent(value) if container.setContent(value) {
return []PropertyName{tag}
}
return nil
case Disabled: case Disabled:
oldDisabled := IsDisabled(container) oldDisabled := IsDisabled(container)
if container.viewData.Set(Disabled, value) { result := viewSet(self, Disabled, value)
if result != nil {
disabled := IsDisabled(container) disabled := IsDisabled(container)
if oldDisabled != disabled { if oldDisabled != disabled {
if container.views != nil { for _, view := range container.views {
for _, view := range container.views { view.Set(Disabled, disabled)
view.Set(Disabled, disabled)
}
} }
} }
container.propertyChangedEvent(tag)
return true
} }
return false return result
} }
return container.viewData.set(tag, value) return viewSet(self, tag, value)
}
func viewsContainerPropertyChanged(view View, tag PropertyName) {
switch tag {
case Content:
updateInnerHTML(view.htmlID(), view.Session())
default:
viewPropertyChanged(view, tag)
}
} }
func (container *viewsContainerData) setContent(value any) bool { func (container *viewsContainerData) setContent(value any) bool {
@ -291,25 +289,16 @@ func (container *viewsContainerData) setContent(value any) bool {
} }
} }
if container.created {
updateInnerHTML(htmlID, container.session)
}
container.propertyChangedEvent(Content)
return true return true
} }
func (container *viewsContainerData) Get(tag string) any { func (container *viewsContainerData) get(view View, tag PropertyName) any {
return container.get(strings.ToLower(tag))
}
func (container *viewsContainerData) get(tag string) any {
switch tag { switch tag {
case Content: case Content:
return container.views return container.views
default: default:
return container.viewData.get(tag) return viewGet(view, tag)
} }
} }