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 реализует данный интерфейс):
type Properties interface {
Get(tag string) any
Get(tag PropertyName) any
Set(tag string, value any) bool
Remove(tag string)
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):
type Properties interface {
Get(tag string) any
Get(tag PropertyName) any
Set(tag string, value any) bool
Remove(tag string)
Clear()

View File

@ -20,7 +20,8 @@ func NewAbsoluteLayout(session Session, params Params) AbsoluteLayout {
}
func newAbsoluteLayout(session Session) View {
return NewAbsoluteLayout(session, nil)
//return NewAbsoluteLayout(session, nil)
return new(absoluteLayoutData)
}
// 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.
// See `Animation` description for more details.
AnimationTag = "animation"
AnimationTag PropertyName = "animation"
// AnimationPaused is the constant for "animation-paused" property tag.
//
@ -30,21 +30,21 @@ const (
// Values:
// `true` or `1` or "true", "yes", "on", "1" - Animation is paused.
// `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.
//
// Used by `View`.
// Sets transition animation of view properties. Each provided property must contain `Animation` which describe how
// particular property will be animated on property value change. Transition animation can be applied to properties of the
// type `SizeUnit`, `Color`, `AngleUnit`, `float64` and composite properties that contain elements of the listed types(for
// example, "shadow", "border", etc.). If we'll try to animate other properties with internal type like `bool` or
// Sets transition animation of view properties. Each provided property must contain `Animation` which describe how
// particular property will be animated on property value change. Transition animation can be applied to properties of the
// type `SizeUnit`, `Color`, `AngleUnit`, `float64` and composite properties that contain elements of the listed types(for
// example, "shadow", "border", etc.). If we'll try to animate other properties with internal type like `bool` or
// `string` no error will occur, simply there will be no animation.
//
// Supported types: `Params`.
//
// See `Params` description for more details.
Transition = "transition"
Transition PropertyName = "transition"
// PropertyTag is the constant for "property" property tag.
//
@ -55,7 +55,7 @@ const (
//
// Internal type is `[]AnimatedProperty`, other types converted to it during assignment.
// See `AnimatedProperty` description for more details.
PropertyTag = "property"
PropertyTag PropertyName = "property"
// Duration is the constant for "duration" property tag.
//
@ -65,19 +65,19 @@ const (
// Supported types: `float`, `int`, `string`.
//
// Internal type is `float`, other types converted to it during assignment.
Duration = "duration"
Duration PropertyName = "duration"
// Delay is the constant for "delay" property tag.
//
// Used by `Animation`.
// Specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the
// Specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the
// animation.
//
// Supported types: `float`, `int`, `string`.
//
// 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.
//
@ -92,7 +92,7 @@ const (
// "ease-out"(`EaseOutTiming`) - Speed is fast at first, but decreases in the end.
// "ease-in-out"(`EaseInOutTiming`) - Speed is slow at first, but quickly increases and at the end it decreases again.
// "linear"(`LinearTiming`) - Constant speed.
TimingFunction = "timing-function"
TimingFunction PropertyName = "timing-function"
// IterationCount is the constant for "iteration-count" property tag.
//
@ -102,12 +102,12 @@ const (
// Supported types: `int`, `string`.
//
// 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.
//
// Used by `Animation`.
// Whether an animation should play forward, backward, or alternate back and forth between playing the sequence forward
// Whether an animation should play forward, backward, or alternate back and forth between playing the sequence forward
// and backward. Used only for animation script.
//
// Supported types: `int`, `string`.
@ -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.
// `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.
AnimationDirection = "animation-direction"
AnimationDirection PropertyName = "animation-direction"
// NormalAnimation is value of the "animation-direction" property.
// 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
type AnimatedProperty struct {
// Tag is the name of the property
Tag string
Tag PropertyName
// From is the initial value of the property
From any
// To is the final value of the property
@ -190,12 +190,12 @@ type AnimatedProperty struct {
}
type animationData struct {
propertyList
dataProperty
keyFramesName string
usageCounter int
view View
listener func(view View, animation Animation, event string)
oldListeners map[string][]func(View, string)
listener func(view View, animation Animation, event PropertyName)
oldListeners map[PropertyName][]func(View, PropertyName)
oldAnimation []Animation
}
@ -207,7 +207,7 @@ type Animation interface {
// Start starts the animation for the view specified by the first argument.
// 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()
// Pause pauses the animation
@ -215,7 +215,7 @@ type Animation interface {
// Resume resumes an animation that was stopped using the Pause method
Resume()
writeTransitionString(tag string, buffer *strings.Builder)
writeTransitionString(tag PropertyName, buffer *strings.Builder)
animationCSS(session Session) string
transitionCSS(buffer *strings.Builder, session Session)
hasAnimatedProperty() bool
@ -230,10 +230,11 @@ func parseAnimation(obj DataObject) Animation {
for i := 0; i < obj.PropertyCount(); i++ {
if node := obj.Property(i); node != nil {
tag := PropertyName(node.Tag())
if node.Type() == TextNode {
animation.Set(node.Tag(), node.Text())
animation.Set(tag, node.Text())
} else {
animation.Set(node.Tag(), node)
animation.Set(tag, node)
}
}
}
@ -251,6 +252,13 @@ func NewAnimation(params Params) 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 {
value := animation.getRaw(PropertyTag)
if value == nil {
@ -291,33 +299,28 @@ func (animation *animationData) unused(session Session) {
}
}
func (animation *animationData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeAnimationTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
if tag == Direction {
return AnimationDirection
}
return tag
}
func (animation *animationData) Set(tag string, value any) bool {
if value == nil {
animation.Remove(tag)
return true
}
switch tag = animation.normalizeTag(tag); tag {
func animationSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case ID:
if text, ok := value.(string); ok {
text = strings.Trim(text, " \t\n\r")
if text == "" {
delete(animation.properties, tag)
properties.setRaw(tag, nil)
} else {
animation.properties[tag] = text
properties.setRaw(tag, text)
}
return true
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return false
return nil
case PropertyTag:
switch value := value.(type) {
@ -340,8 +343,8 @@ func (animation *animationData) Set(tag string, value any) bool {
} else if value.To == nil {
ErrorLog("AnimatedProperty.To is nil")
} else {
animation.properties[tag] = []AnimatedProperty{value}
return true
properties.setRaw(tag, []AnimatedProperty{value})
return []PropertyName{tag}
}
case []AnimatedProperty:
@ -369,8 +372,8 @@ func (animation *animationData) Set(tag string, value any) bool {
}
}
if len(props) > 0 {
animation.properties[tag] = props
return true
properties.setRaw(tag, props)
return []PropertyName{tag}
} else {
ErrorLog("[]AnimatedProperty is empty")
}
@ -417,8 +420,8 @@ func (animation *animationData) Set(tag string, value any) bool {
switch value.Type() {
case ObjectNode:
if prop, ok := parseObject(value.Object()); ok {
animation.properties[tag] = []AnimatedProperty{prop}
return true
properties.setRaw(tag, []AnimatedProperty{prop})
return []PropertyName{tag}
}
case ArrayNode:
@ -433,8 +436,8 @@ func (animation *animationData) Set(tag string, value any) bool {
}
}
if len(props) > 0 {
animation.properties[tag] = props
return true
properties.setRaw(tag, props)
return []PropertyName{tag}
}
default:
@ -446,36 +449,28 @@ func (animation *animationData) Set(tag string, value any) bool {
}
case Duration:
return animation.setFloatProperty(tag, value, 0, math.MaxFloat64)
return setFloatProperty(properties, tag, value, 0, math.MaxFloat64)
case Delay:
return animation.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64)
return setFloatProperty(properties, tag, value, -math.MaxFloat64, math.MaxFloat64)
case TimingFunction:
if text, ok := value.(string); ok {
animation.properties[tag] = text
return true
properties.setRaw(tag, text)
return []PropertyName{tag}
}
case IterationCount:
return animation.setIntProperty(tag, value)
return setIntProperty(properties, tag, value)
case AnimationDirection:
return animation.setEnumProperty(AnimationDirection, value, enumProperties[AnimationDirection].values)
return setEnumProperty(properties, AnimationDirection, value, enumProperties[AnimationDirection].values)
default:
ErrorLogF(`The "%s" property is not supported by Animation`, tag)
}
return false
}
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))
return nil
}
func (animation *animationData) String() string {
@ -488,7 +483,7 @@ func (animation *animationData) String() string {
if tag != PropertyTag {
if value, ok := animation.properties[tag]; ok && value != nil {
buffer.WriteString("\n\t")
buffer.WriteString(tag)
buffer.WriteString(string(tag))
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, "\t")
buffer.WriteRune(',')
@ -497,7 +492,7 @@ func (animation *animationData) String() string {
}
writeProperty := func(prop AnimatedProperty, indent string) {
buffer.WriteString(prop.Tag)
buffer.WriteString(string(prop.Tag))
buffer.WriteString("{\n")
buffer.WriteString(indent)
buffer.WriteString("from = ")
@ -512,7 +507,7 @@ func (animation *animationData) String() string {
tag := strconv.Itoa(key) + "%"
buffer.WriteString(tag)
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent)
writePropertyValue(buffer, PropertyName(tag), value, indent)
}
buffer.WriteString("\n")
buffer.WriteString(indent[1:])
@ -522,7 +517,7 @@ func (animation *animationData) String() string {
if props := animation.animatedProperties(); len(props) > 0 {
buffer.WriteString("\n\t")
buffer.WriteString(PropertyTag)
buffer.WriteString(string(PropertyTag))
buffer.WriteString(" = ")
if len(props) > 1 {
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) {
buffer.WriteString(tag)
func (animation *animationData) writeTransitionString(tag PropertyName, buffer *strings.Builder) {
buffer.WriteString(string(tag))
buffer.WriteString("{")
lead := " "
writeFloatProperty := func(name string) bool {
writeFloatProperty := func(name PropertyName) bool {
if value := animation.getRaw(name); value != nil {
buffer.WriteString(lead)
buffer.WriteString(name)
buffer.WriteString(string(name))
buffer.WriteString(" = ")
writePropertyValue(buffer, name, value, "")
lead = ", "
@ -633,7 +628,7 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string
if value := animation.getRaw(TimingFunction); value != nil {
if timingFunction, ok := value.(string); ok && timingFunction != "" {
buffer.WriteString(lead)
buffer.WriteString(TimingFunction)
buffer.WriteString(string(TimingFunction))
buffer.WriteString(" = ")
if strings.ContainsAny(timingFunction, " ,()") {
buffer.WriteRune('"')
@ -767,7 +762,7 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string {
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 {
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, "ontransitioncancel", "transitionCancelEvent(this, event)")
if prevAnimation, ok := view.transitions[tag]; ok {
view.singleTransition[tag] = prevAnimation
} else {
view.singleTransition[tag] = nil
transitions := getTransitionProperty(view)
var prevAnimation Animation = nil
if transitions != nil {
if prev, ok := transitions[tag]; ok {
prevAnimation = prev
}
}
view.transitions[tag] = animation
view.updateTransitionCSS()
view.singleTransition[tag] = prevAnimation
setTransition(view, tag, animation)
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
session.finishUpdateScript(htmlID)
result := view.Set(tag, value)
if !result {
delete(view.singleTransition, tag)
view.updateTransitionCSS()
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
}
return result
}
func (style *viewStyle) animationCSS(session Session) string {
if value := style.getRaw(AnimationTag); value != nil {
func animationCSS(properties Properties, session Session) string {
if value := properties.getRaw(AnimationTag); value != nil {
if animations, ok := value.([]Animation); ok {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
@ -820,78 +818,154 @@ func (style *viewStyle) animationCSS(session Session) string {
return ""
}
func (style *viewStyle) transitionCSS(session Session) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
func transitionCSS(properties Properties, session Session) string {
if transitions := getTransitionProperty(properties); len(transitions) > 0 {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
convert := map[string]string{
CellHeight: "grid-template-rows",
CellWidth: "grid-template-columns",
Row: "grid-row",
Column: "grid-column",
Clip: "clip-path",
Shadow: "box-shadow",
ColumnSeparator: "column-rule",
FontName: "font",
TextSize: "font-size",
TextLineThickness: "text-decoration-thickness",
}
for tag, animation := range style.transitions {
if buffer.Len() > 0 {
buffer.WriteString(", ")
convert := map[PropertyName]string{
CellHeight: "grid-template-rows",
CellWidth: "grid-template-columns",
Row: "grid-row",
Column: "grid-column",
Clip: "clip-path",
Shadow: "box-shadow",
ColumnSeparator: "column-rule",
FontName: "font",
TextSize: "font-size",
TextLineThickness: "text-decoration-thickness",
}
if cssTag, ok := convert[tag]; ok {
buffer.WriteString(cssTag)
} else {
buffer.WriteString(tag)
for tag, animation := range transitions {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
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() {
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 {
if style.transitions != nil {
if anim, ok := style.transitions[tag]; ok {
func (style *viewStyle) Transition(tag PropertyName) Animation {
if transitions := getTransitionProperty(style); transitions != nil {
if anim, ok := transitions[tag]; ok {
return anim
}
}
return nil
}
func (style *viewStyle) Transitions() map[string]Animation {
result := map[string]Animation{}
for tag, animation := range style.transitions {
func (style *viewStyle) Transitions() map[PropertyName]Animation {
result := map[PropertyName]Animation{}
for tag, animation := range getTransitionProperty(style) {
result[tag] = animation
}
return result
}
func (style *viewStyle) SetTransition(tag string, animation Animation) {
if animation == nil {
delete(style.transitions, tag)
} else {
style.transitions[tag] = animation
func (style *viewStyle) SetTransition(tag PropertyName, animation Animation) {
setTransition(style, style.normalize(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) {
view.viewStyle.SetTransition(tag, animation)
if view.created {
view.session.updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.session))
func setTransition(properties Properties, tag PropertyName, animation Animation) {
transitions := getTransitionProperty(properties)
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:
// true - success,
// 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 {
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.
// 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] != "" {
view = ViewByID(view, subviewID[0])
}
@ -915,12 +989,12 @@ func GetTransitions(view View, subviewID ...string) map[string]Animation {
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.
// 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 != "" {
view = ViewByID(view, subviewID)
}
@ -934,7 +1008,7 @@ func GetTransition(view View, subviewID, tag string) Animation {
// 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)
func AddTransition(view View, subviewID, tag string, animation Animation) bool {
func AddTransition(view View, subviewID string, tag PropertyName, animation Animation) bool {
if tag != "" {
if subviewID != "" {
view = ViewByID(view, subviewID)

View File

@ -1,7 +1,5 @@
package rui
import "strings"
// Constants which describe values for view's animation events properties
const (
// TransitionRunEvent is the constant for "transition-run-event" property tag.
@ -20,7 +18,7 @@ const (
// `func(view rui.View)`,
// `func(propertyName string)`,
// `func()`.
TransitionRunEvent = "transition-run-event"
TransitionRunEvent PropertyName = "transition-run-event"
// TransitionStartEvent is the constant for "transition-start-event" property tag.
//
@ -38,7 +36,7 @@ const (
// `func(view rui.View)`,
// `func(propertyName string)`,
// `func()`.
TransitionStartEvent = "transition-start-event"
TransitionStartEvent PropertyName = "transition-start-event"
// TransitionEndEvent is the constant for "transition-end-event" property tag.
//
@ -56,13 +54,13 @@ const (
// `func(view rui.View)`,
// `func(propertyName string)`,
// `func()`.
TransitionEndEvent = "transition-end-event"
TransitionEndEvent PropertyName = "transition-end-event"
// TransitionCancelEvent is the constant for "transition-cancel-event" property tag.
//
// Used by `View`.
// Is fired when a transition is cancelled. The transition is cancelled when: * A new property transition has begun. * The
// "visibility" property is set to "gone". * The transition is stopped before it has run to completion, e.g. by moving the
// Is fired when a transition is cancelled. The transition is cancelled when: * A new property transition has begun. * The
// "visibility" property is set to "gone". * The transition is stopped before it has run to completion, e.g. by moving the
// mouse off a hover-transitioning view.
//
// General listener format:
@ -76,12 +74,12 @@ const (
// `func(view rui.View)`,
// `func(propertyName string)`,
// `func()`.
TransitionCancelEvent = "transition-cancel-event"
TransitionCancelEvent PropertyName = "transition-cancel-event"
// AnimationStartEvent is the constant for "animation-start-event" property tag.
//
// Used by `View`.
// Fired when an animation has started. If there is an "animation-delay", this event will fire once the delay period has
// Fired when an animation has started. If there is an "animation-delay", this event will fire once the delay period has
// expired.
//
// General listener format:
@ -95,12 +93,12 @@ const (
// `func(view rui.View)`,
// `func(animationId string)`,
// `func()`.
AnimationStartEvent = "animation-start-event"
AnimationStartEvent PropertyName = "animation-start-event"
// AnimationEndEvent is the constant for "animation-end-event" property tag.
//
// Used by `View`.
// Fired when an animation has completed. If the animation aborts before reaching completion, such as if the element is
// Fired when an animation has completed. If the animation aborts before reaching completion, such as if the element is
// removed or the animation is removed from the element, the "animation-end-event" is not fired.
//
// General listener format:
@ -114,14 +112,14 @@ const (
// `func(view rui.View)`,
// `func(animationId string)`,
// `func()`.
AnimationEndEvent = "animation-end-event"
AnimationEndEvent PropertyName = "animation-end-event"
// AnimationCancelEvent is the constant for "animation-cancel-event" property tag.
//
// Used by `View`.
// Fired when an animation unexpectedly aborts. In other words, any time it stops running without sending the
// "animation-end-event". This might happen when the animation-name is changed such that the animation is removed, or when
// the animating view is hidden. Therefore, either directly or because any of its containing views are hidden. The event
// Fired when an animation unexpectedly aborts. In other words, any time it stops running without sending the
// "animation-end-event". This might happen when the animation-name is changed such that the animation is removed, or when
// the animating view is hidden. Therefore, either directly or because any of its containing views are hidden. The event
// is not supported by all browsers.
//
// General listener format:
@ -135,12 +133,12 @@ const (
// `func(view rui.View)`,
// `func(animationId string)`,
// `func()`.
AnimationCancelEvent = "animation-cancel-event"
AnimationCancelEvent PropertyName = "animation-cancel-event"
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
//
// Used by `View`.
// Fired when an iteration of an animation ends, and another one begins. This event does not occur at the same time as the
// Fired when an iteration of an animation ends, and another one begins. This event does not occur at the same time as the
// animation end event, and therefore does not occur for animations with an "iteration-count" of one.
//
// General listener format:
@ -154,128 +152,106 @@ const (
// `func(view rui.View)`,
// `func(animationId string)`,
// `func()`.
AnimationIterationEvent = "animation-iteration-event"
AnimationIterationEvent PropertyName = "animation-iteration-event"
)
var transitionEvents = map[string]struct{ jsEvent, jsFunc string }{
TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"},
TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"},
TransitionEndEvent: {jsEvent: "ontransitionend", jsFunc: "transitionEndEvent"},
TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"},
}
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)")
/*
func setTransitionListener(properties Properties, tag PropertyName, value any) bool {
if listeners, ok := valueToEventListeners[View, string](value); ok {
if len(listeners) == 0 {
properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
} else {
return false
return true
}
return true
notCompatibleType(tag, value)
return false
}
func (view *viewData) removeTransitionListener(tag string) {
func (view *viewData) removeTransitionListener(tag PropertyName) {
delete(view.properties, tag)
if view.created {
if js, ok := transitionEvents[tag]; ok {
if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
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 listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
}
}
}
*/
func (view *viewData) handleTransitionEvents(tag string, data DataObject) {
if property, ok := data.PropertyValue("property"); ok {
func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) {
if propertyName, ok := data.PropertyValue("property"); ok {
property := PropertyName(propertyName)
if tag == TransitionEndEvent || tag == TransitionCancelEvent {
if animation, ok := view.singleTransition[property]; ok {
delete(view.singleTransition, property)
if animation != nil {
view.transitions[property] = animation
} else {
delete(view.transitions, property)
}
view.updateTransitionCSS()
setTransition(view, tag, animation)
session := view.session
session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, session))
}
}
for _, listener := range getEventListeners[View, string](view, nil, tag) {
for _, listener := range getEventListeners[View, PropertyName](view, nil, tag) {
listener(view, property)
}
}
}
var animationEvents = map[string]struct{ jsEvent, jsFunc string }{
AnimationStartEvent: {jsEvent: "onanimationstart", jsFunc: "animationStartEvent"},
AnimationEndEvent: {jsEvent: "onanimationend", jsFunc: "animationEndEvent"},
AnimationIterationEvent: {jsEvent: "onanimationiteration", jsFunc: "animationIterationEvent"},
AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"},
}
func (view *viewData) setAnimationListener(tag string, value any) bool {
listeners, ok := valueToEventListeners[View, string](value)
if !ok {
/*
func setAnimationListener(properties Properties, tag PropertyName, value any) bool {
if listeners, ok := valueToEventListeners[View, string](value); ok {
if len(listeners) == 0 {
properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
return true
}
notCompatibleType(tag, value)
return false
}
if listeners == nil {
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) {
func (view *viewData) removeAnimationListener(tag PropertyName) {
delete(view.properties, tag)
if view.created {
if js, ok := animationEvents[tag]; ok {
if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
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 listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
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 {
id := ""
if name, ok := data.PropertyValue("name"); ok {

View File

@ -1,6 +1,6 @@
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 {
ErrorLog("nil View in animation.Start() function")
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)) {
var listeners []func(View, string) = nil
setListeners := func(event PropertyName, listener func(View, PropertyName)) {
var listeners []func(View, PropertyName) = 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
}
}
@ -48,7 +48,7 @@ func (animation *animationData) Start(view View, listener func(view View, animat
func (animation *animationData) finish() {
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 {
animation.view.Set(event, listeners)
} else {
@ -63,7 +63,7 @@ func (animation *animationData) finish() {
animation.view.Set(AnimationTag, "")
}
animation.oldListeners = map[string][]func(View, string){}
animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
animation.view = 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 {
animation.listener(animation.view, animation, AnimationStartEvent)
}
}
func (animation *animationData) onAnimationEnd(view View, _ string) {
func (animation *animationData) onAnimationEnd(view View, _ PropertyName) {
if animation.view != nil {
animationView := animation.view
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 {
animation.listener(animation.view, animation, AnimationIterationEvent)
}
}
func (animation *animationData) onAnimationCancel(view View, _ string) {
func (animation *animationData) onAnimationCancel(view View, _ PropertyName) {
if animation.view != nil {
animationView := animation.view
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) {
const element = document.getElementById(elementId);
if (element) {

View File

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

View File

@ -78,7 +78,7 @@ type BackgroundElement interface {
}
type backgroundElement struct {
propertyList
dataProperty
}
type backgroundImage struct {
@ -91,24 +91,16 @@ func createBackground(obj DataObject) BackgroundElement {
switch obj.Tag() {
case "image":
image := new(backgroundImage)
image.properties = map[string]any{}
result = image
result = NewBackgroundImage(nil)
case "linear-gradient":
gradient := new(backgroundLinearGradient)
gradient.properties = map[string]any{}
result = gradient
result = NewBackgroundLinearGradient(nil)
case "radial-gradient":
gradient := new(backgroundRadialGradient)
gradient.properties = map[string]any{}
result = gradient
result = NewBackgroundRadialGradient(nil)
case "conic-gradient":
gradient := new(backgroundConicGradient)
gradient.properties = map[string]any{}
result = gradient
result = NewBackgroundConicGradient(nil)
default:
return nil
@ -118,7 +110,7 @@ func createBackground(obj DataObject) BackgroundElement {
for i := 0; i < count; i++ {
if node := obj.Property(i); node.Type() == TextNode {
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
func NewBackgroundImage(params Params) BackgroundElement {
result := new(backgroundImage)
result.properties = map[string]any{}
result.init()
for tag, value := range params {
result.Set(tag, value)
}
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 {
return "image"
}
@ -148,8 +148,8 @@ func (image *backgroundImage) Clone() BackgroundElement {
return result
}
func (image *backgroundImage) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeBackgroundImageTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case "source":
tag = Source
@ -167,21 +167,6 @@ func (image *backgroundImage) normalizeTag(tag string) string {
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 {
if src, ok := imageProperty(image, Source, session); ok && src != "" {
buffer := allocStringBuilder()
@ -252,7 +237,7 @@ func (image *backgroundImage) cssStyle(session Session) 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,
Width,
Height,
@ -267,3 +252,76 @@ func (image *backgroundImage) writeString(buffer *strings.Builder, indent string
func (image *backgroundImage) String() string {
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
func NewBackgroundConicGradient(params Params) BackgroundElement {
result := new(backgroundConicGradient)
result.properties = map[string]any{}
result.init()
for tag, value := range params {
result.Set(tag, value)
}
@ -48,7 +48,6 @@ func (point *BackgroundGradientAngle) String() string {
case AngleUnit:
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 {
return "conic-gradient"
}
@ -127,8 +135,8 @@ func (image *backgroundConicGradient) Clone() BackgroundElement {
return result
}
func (gradient *backgroundConicGradient) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeConicGradientTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case "x-center":
tag = CenterX
@ -140,18 +148,50 @@ func (gradient *backgroundConicGradient) normalizeTag(tag string) string {
return tag
}
func (gradient *backgroundConicGradient) Set(tag string, value any) bool {
tag = gradient.normalizeTag(tag)
func backgroundConicGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
switch tag {
case CenterX, CenterY, Repeating, From:
return gradient.propertyList.Set(tag, value)
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 false
return propertiesSet(properties, tag, value)
}
func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) {
@ -216,57 +256,6 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
}
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 {
points := []BackgroundGradientAngle{}
@ -339,7 +328,7 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) 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,
CenterX,
CenterY,

View File

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

458
border.go
View File

@ -38,7 +38,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted 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.
//
@ -53,7 +53,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted 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.
//
@ -68,7 +68,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted 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.
//
@ -83,7 +83,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
// `3`(`DottedLine`) or "dotted" - Dotted 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.
//
@ -94,7 +94,7 @@ const (
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// See `SizeUnit` description for more details.
LeftWidth = "left-width"
LeftWidth PropertyName = "left-width"
// 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.
// See `SizeUnit` description for more details.
RightWidth = "right-width"
RightWidth PropertyName = "right-width"
// 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.
// See `SizeUnit` description for more details.
TopWidth = "top-width"
TopWidth PropertyName = "top-width"
// 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.
// See `SizeUnit` description for more details.
BottomWidth = "bottom-width"
BottomWidth PropertyName = "bottom-width"
// 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.
// See `Color` description for more details.
LeftColor = "left-color"
LeftColor PropertyName = "left-color"
// 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.
// See `Color` description for more details.
RightColor = "right-color"
RightColor PropertyName = "right-color"
// 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.
// See `Color` description for more details.
TopColor = "top-color"
TopColor PropertyName = "top-color"
// 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.
// See `Color` description for more details.
BottomColor = "bottom-color"
BottomColor PropertyName = "bottom-color"
)
// 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(session Session) ViewBorders
delete(tag string)
deleteTag(tag PropertyName) bool
cssStyle(builder cssBuilder, session Session)
cssWidth(builder cssBuilder, session Session)
cssColor(builder cssBuilder, session Session)
@ -193,12 +193,12 @@ type BorderProperty interface {
}
type borderProperty struct {
propertyList
dataProperty
}
func newBorderProperty(value any) BorderProperty {
border := new(borderProperty)
border.properties = map[string]any{}
border.init()
if value != nil {
switch value := value.(type) {
@ -270,9 +270,10 @@ func newBorderProperty(value any) BorderProperty {
// "width" (Width). Determines the line thickness (SizeUnit).
func NewBorder(params Params) BorderProperty {
border := new(borderProperty)
border.properties = map[string]any{}
border.init()
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,
LeftWidth, RightWidth, TopWidth, BottomWidth,
LeftColor, RightColor, TopColor, BottomColor} {
@ -284,8 +285,37 @@ func NewBorder(params Params) BorderProperty {
return border
}
func (border *borderProperty) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func (border *borderProperty) init() {
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 {
case BorderLeft, CellBorderLeft:
return Left
@ -352,23 +382,23 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
buffer.WriteString("_{ ")
comma := false
write := func(tag string, value any) {
write := func(tag PropertyName, value any) {
if comma {
buffer.WriteString(", ")
}
buffer.WriteString(tag)
buffer.WriteString(string(tag))
buffer.WriteString(" = ")
writePropertyValue(buffer, BorderStyle, value, indent)
comma = true
}
for _, tag := range []string{Style, Width, ColorTag} {
for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := border.properties[tag]; ok {
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]
width, okWidth := border.properties[side+"-"+Width]
color, okColor := border.properties[side+"-"+ColorTag]
@ -378,7 +408,7 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
comma = false
}
buffer.WriteString(side)
buffer.WriteString(string(side))
buffer.WriteString(" = _{ ")
if okStyle {
write(Style, style)
@ -401,164 +431,96 @@ func (border *borderProperty) String() string {
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 {
result := true
for _, side := range []string{Top, Right, Bottom, Left} {
if node := obj.PropertyByTag(side); node != nil {
if node.Type() == ObjectNode {
if !border.setSingleBorderObject(side, node.Object()) {
for i := 0; i < obj.PropertyCount(); i++ {
if node := obj.Property(i); node != nil {
tag := PropertyName(node.Tag())
switch node.Type() {
case TextNode:
if borderSet(border, tag, node.Text()) == nil {
result = false
}
} else {
notCompatibleType(side, node)
result = false
}
}
}
if text, ok := obj.PropertyValue(Style); ok {
values := split4Values(text)
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) {
case ObjectNode:
if borderSet(border, tag, node.Object()) == nil {
result = false
}
}
default:
notCompatibleType(Style, text)
default:
result = false
}
} else {
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
}
func (border *borderProperty) Remove(tag string) {
tag = border.normalizeTag(tag)
func borderRemove(properties Properties, tag PropertyName) []PropertyName {
result := []PropertyName{}
removeTag := func(t PropertyName) {
if properties.getRaw(t) != nil {
properties.setRaw(t, nil)
result = append(result, t)
}
}
switch tag {
case Style:
for _, t := range []string{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
delete(border.properties, t)
for _, t := range []PropertyName{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
removeTag(t)
}
case Width:
for _, t := range []string{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
delete(border.properties, t)
for _, t := range []PropertyName{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
removeTag(t)
}
case ColorTag:
for _, t := range []string{tag, TopColor, RightColor, BottomColor, LeftColor} {
delete(border.properties, t)
for _, t := range []PropertyName{tag, TopColor, RightColor, BottomColor, LeftColor} {
removeTag(t)
}
case Left, Right, Top, Bottom:
border.Remove(tag + "-style")
border.Remove(tag + "-width")
border.Remove(tag + "-color")
removeTag(tag + "-style")
removeTag(tag + "-width")
removeTag(tag + "-color")
case LeftStyle, RightStyle, TopStyle, BottomStyle:
delete(border.properties, tag)
if style, ok := border.properties[Style]; ok && style != nil {
for _, t := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
removeTag(tag)
if style := properties.getRaw(Style); style != nil {
for _, t := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
if t != tag {
if _, ok := border.properties[t]; !ok {
border.properties[t] = style
if properties.getRaw(t) == nil {
properties.setRaw(t, style)
result = append(result, t)
}
}
}
}
case LeftWidth, RightWidth, TopWidth, BottomWidth:
delete(border.properties, tag)
if width, ok := border.properties[Width]; ok && width != nil {
for _, t := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
removeTag(tag)
if width := properties.getRaw(Width); width != nil {
for _, t := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
if t != tag {
if _, ok := border.properties[t]; !ok {
border.properties[t] = width
if properties.getRaw(t) == nil {
properties.setRaw(t, width)
result = append(result, t)
}
}
}
}
case LeftColor, RightColor, TopColor, BottomColor:
delete(border.properties, tag)
if color, ok := border.properties[ColorTag]; ok && color != nil {
for _, t := range []string{TopColor, RightColor, BottomColor, LeftColor} {
removeTag(tag)
if color := properties.getRaw(ColorTag); color != nil {
for _, t := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
if t != tag {
if _, ok := border.properties[t]; !ok {
border.properties[t] = color
if properties.getRaw(t) == nil {
properties.setRaw(t, color)
result = append(result, t)
}
}
}
@ -567,80 +529,118 @@ func (border *borderProperty) Remove(tag string) {
default:
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
}
return result
}
func (border *borderProperty) Set(tag string, value any) bool {
if value == nil {
border.Remove(tag)
return true
}
func borderSet(properties Properties, tag PropertyName, value any) []PropertyName {
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 {
case Style:
if border.setEnumProperty(Style, value, enumProperties[BorderStyle].values) {
for _, side := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
delete(border.properties, side)
if result := setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values); result != nil {
for _, side := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
}
return true
return result
}
case Width:
if border.setSizeProperty(Width, value) {
for _, side := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
delete(border.properties, side)
if result := setSizeProperty(properties, Width, value); result != nil {
for _, side := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
}
return true
return result
}
case ColorTag:
if border.setColorProperty(ColorTag, value) {
for _, side := range []string{TopColor, RightColor, BottomColor, LeftColor} {
delete(border.properties, side)
if result := setColorProperty(properties, ColorTag, value); result != nil {
for _, side := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
if value := properties.getRaw(side); value != nil {
properties.setRaw(side, nil)
result = append(result, side)
}
}
return true
return result
}
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:
return border.setSizeProperty(tag, value)
return setSizeProperty(properties, tag, value)
case LeftColor, RightColor, TopColor, BottomColor:
return border.setColorProperty(tag, value)
return setColorProperty(properties, tag, value)
case Left, Right, Top, Bottom:
switch value := value.(type) {
case string:
if obj := ParseDataText(value); obj != nil {
return border.setSingleBorderObject(tag, obj)
return setSingleBorderObject(tag, obj)
}
case DataObject:
return border.setSingleBorderObject(tag, value)
return setSingleBorderObject(tag, value)
case BorderProperty:
result := []PropertyName{}
styleTag := tag + "-" + Style
if style := value.Get(styleTag); value != nil {
border.properties[styleTag] = style
properties.setRaw(styleTag, style)
result = append(result, styleTag)
}
colorTag := tag + "-" + ColorTag
if color := value.Get(colorTag); value != nil {
border.properties[colorTag] = color
properties.setRaw(colorTag, color)
result = append(result, colorTag)
}
widthTag := tag + "-" + Width
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:
border.properties[tag+"-"+Style] = value.Style
border.properties[tag+"-"+Width] = value.Width
border.properties[tag+"-"+ColorTag] = value.Color
return true
properties.setRaw(tag+"-"+Style, value.Style)
properties.setRaw(tag+"-"+Width, value.Width)
properties.setRaw(tag+"-"+ColorTag, value.Color)
return []PropertyName{tag + "-" + Style, tag + "-" + Width, tag + "-" + ColorTag}
}
fallthrough
@ -648,105 +648,119 @@ func (border *borderProperty) Set(tag string, value any) bool {
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
}
return false
return nil
}
func (border *borderProperty) Get(tag string) any {
tag = border.normalizeTag(tag)
if result, ok := border.properties[tag]; ok {
func borderGet(properties Properties, tag PropertyName) any {
if result := properties.getRaw(tag); result != nil {
return result
}
switch tag {
case Left, Right, Top, Bottom:
result := newBorderProperty(nil)
if style, ok := border.properties[tag+"-"+Style]; ok {
if style := properties.getRaw(tag + "-" + Style); style != nil {
result.Set(Style, style)
} else if style, ok := border.properties[Style]; ok {
} else if style := properties.getRaw(Style); style != nil {
result.Set(Style, style)
}
if width, ok := border.properties[tag+"-"+Width]; ok {
if width := properties.getRaw(tag + "-" + Width); width != nil {
result.Set(Width, width)
} else if width, ok := border.properties[Width]; ok {
} else if width := properties.getRaw(Width); width != nil {
result.Set(Width, width)
}
if color, ok := border.properties[tag+"-"+ColorTag]; ok {
if color := properties.getRaw(tag + "-" + ColorTag); color != nil {
result.Set(ColorTag, color)
} else if color, ok := border.properties[ColorTag]; ok {
} else if color := properties.getRaw(ColorTag); color != nil {
result.Set(ColorTag, color)
}
return result
case LeftStyle, RightStyle, TopStyle, BottomStyle:
if style, ok := border.properties[tag]; ok {
if style := properties.getRaw(tag); style != nil {
return style
}
return border.properties[Style]
return properties.getRaw(Style)
case LeftWidth, RightWidth, TopWidth, BottomWidth:
if width, ok := border.properties[tag]; ok {
if width := properties.getRaw(tag); width != nil {
return width
}
return border.properties[Width]
return properties.getRaw(Width)
case LeftColor, RightColor, TopColor, BottomColor:
if color, ok := border.properties[tag]; ok {
if color := properties.getRaw(tag); color != nil {
return color
}
return border.properties[ColorTag]
return properties.getRaw(ColorTag)
}
return nil
}
func (border *borderProperty) delete(tag string) {
tag = border.normalizeTag(tag)
remove := []string{}
func (border *borderProperty) deleteTag(tag PropertyName) bool {
result := false
removeTags := func(tags []PropertyName) {
for _, tag := range tags {
if border.getRaw(tag) != nil {
border.setRaw(tag, nil)
result = true
}
}
}
switch tag {
case Style:
remove = []string{Style, LeftStyle, RightStyle, TopStyle, BottomStyle}
removeTags([]PropertyName{Style, LeftStyle, RightStyle, TopStyle, BottomStyle})
case Width:
remove = []string{Width, LeftWidth, RightWidth, TopWidth, BottomWidth}
removeTags([]PropertyName{Width, LeftWidth, RightWidth, TopWidth, BottomWidth})
case ColorTag:
remove = []string{ColorTag, LeftColor, RightColor, TopColor, BottomColor}
removeTags([]PropertyName{ColorTag, LeftColor, RightColor, TopColor, BottomColor})
case Left, Right, Top, Bottom:
if border.Get(Style) != nil {
border.properties[tag+"-"+Style] = 0
remove = []string{tag + "-" + ColorTag, tag + "-" + Width}
result = true
removeTags([]PropertyName{tag + "-" + ColorTag, tag + "-" + Width})
} else {
remove = []string{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width}
removeTags([]PropertyName{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width})
}
case LeftStyle, RightStyle, TopStyle, BottomStyle:
if border.Get(Style) != nil {
border.properties[tag] = 0
} else {
remove = []string{tag}
if border.getRaw(tag) != nil {
if border.Get(Style) != nil {
border.properties[tag] = 0
result = true
} else {
removeTags([]PropertyName{tag})
}
}
case LeftWidth, RightWidth, TopWidth, BottomWidth:
if border.Get(Width) != nil {
border.properties[tag] = AutoSize()
} else {
remove = []string{tag}
if border.getRaw(tag) != nil {
if border.Get(Width) != nil {
border.properties[tag] = AutoSize()
result = true
} else {
removeTags([]PropertyName{tag})
}
}
case LeftColor, RightColor, TopColor, BottomColor:
if border.Get(ColorTag) != nil {
border.properties[tag] = 0
} else {
remove = []string{tag}
if border.getRaw(tag) != nil {
if border.Get(ColorTag) != nil {
border.properties[tag] = 0
result = true
} else {
removeTags([]PropertyName{tag})
}
}
}
for _, tag := range remove {
delete(border.properties, tag)
}
return result
}
func (border *borderProperty) ViewBorders(session Session) ViewBorders {
@ -755,7 +769,7 @@ func (border *borderProperty) ViewBorders(session Session) ViewBorders {
defWidth, _ := sizeProperty(border, Width, session)
defColor, _ := colorProperty(border, ColorTag, session)
getBorder := func(prefix string) ViewBorder {
getBorder := func(prefix PropertyName) ViewBorder {
var result ViewBorder
var ok bool
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 &&
borders.Top.Style == borders.Left.Style &&
borders.Top.Style == borders.Bottom.Style {
builder.add(BorderStyle, values[borders.Top.Style])
builder.add(string(BorderStyle), values[borders.Top.Style])
} 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])
}
}
@ -870,11 +884,25 @@ func (border *ViewBorders) AllTheSame() bool {
border.Top.Width.Equal(border.Bottom.Width)
}
func getBorder(style Properties, tag string) BorderProperty {
if value := style.Get(tag); value != nil {
func getBorderProperty(properties Properties, tag PropertyName) BorderProperty {
if value := properties.getRaw(tag); value != nil {
if border, ok := value.(BorderProperty); ok {
return border
}
}
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 {
propertyList
dataProperty
}
// NewBoundsProperty creates the new BoundsProperty object.
// The following SizeUnit properties can be used: "left" (Left), "right" (Right), "top" (Top), and "bottom" (Bottom).
func NewBoundsProperty(params Params) BoundsProperty {
bounds := new(boundsPropertyData)
bounds.properties = map[string]any{}
bounds.init()
if params != nil {
for _, tag := range []string{Top, Right, Bottom, Left} {
if value, ok := params[tag]; ok {
bounds.Set(tag, value)
for _, tag := range bounds.supportedProperties {
if value, ok := params[tag]; ok && value != nil {
bounds.set(bounds, tag, value)
}
}
}
return bounds
}
func (bounds *boundsPropertyData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func (bounds *boundsPropertyData) init() {
bounds.dataProperty.init()
bounds.normalize = normalizeBoundsTag
bounds.supportedProperties = []PropertyName{Top, Right, Bottom, Left}
}
func normalizeBoundsTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case MarginTop, PaddingTop, CellPaddingTop,
"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) {
buffer.WriteString("_{ ")
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 comma {
buffer.WriteString(", ")
}
buffer.WriteString(tag)
buffer.WriteString(string(tag))
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent)
comma = true
@ -78,38 +85,6 @@ func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent st
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 {
top, _ := sizeProperty(bounds, Top, session)
right, _ := sizeProperty(bounds, Right, session)
@ -141,7 +116,7 @@ func (bounds *Bounds) SetAll(value SizeUnit) {
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()
if size, ok := sizeProperty(properties, tag, session); ok {
bounds.Top = size
@ -216,11 +191,11 @@ func (bounds *Bounds) String() 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() {
builder.add(tag, bounds.Top.cssString("0", session))
builder.add(string(tag), bounds.Top.cssString("0", session))
} else {
builder.addValues(tag, " ",
builder.addValues(string(tag), " ",
bounds.Top.cssString("0", session),
bounds.Right.cssString("0", session),
bounds.Bottom.cssString("0", session),
@ -234,8 +209,8 @@ func (bounds *Bounds) cssString(session Session) string {
return builder.finish()
}
func (properties *propertyList) setBounds(tag string, value any) bool {
if !properties.setSimpleProperty(tag, value) {
func setBoundsProperty(properties Properties, tag PropertyName, value any) []PropertyName {
if !setSimpleProperty(properties, tag, value) {
switch value := value.(type) {
case string:
if strings.Contains(value, ",") {
@ -247,88 +222,119 @@ func (properties *propertyList) setBounds(tag string, value any) bool {
case 4:
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]) {
notCompatibleType(tag, value)
return false
return nil
}
}
properties.properties[tag] = bounds
return true
properties.setRaw(tag, bounds)
return []PropertyName{tag}
default:
notCompatibleType(tag, value)
return false
return nil
}
}
return properties.setSizeProperty(tag, value)
return setSizeProperty(properties, tag, value)
case SizeUnit:
properties.properties[tag] = value
properties.setRaw(tag, value)
case float32:
properties.properties[tag] = Px(float64(value))
properties.setRaw(tag, Px(float64(value)))
case float64:
properties.properties[tag] = Px(value)
properties.setRaw(tag, Px(value))
case Bounds:
bounds := NewBoundsProperty(nil)
if value.Top.Type != Auto {
bounds.Set(Top, value.Top)
bounds.setRaw(Top, value.Top)
}
if value.Right.Type != Auto {
bounds.Set(Right, value.Right)
bounds.setRaw(Right, value.Right)
}
if value.Bottom.Type != Auto {
bounds.Set(Bottom, value.Bottom)
bounds.setRaw(Bottom, value.Bottom)
}
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:
properties.properties[tag] = value
properties.setRaw(tag, value)
case DataObject:
bounds := NewBoundsProperty(nil)
for _, tag := range []string{Top, Right, Bottom, Left} {
if text, ok := value.PropertyValue(tag); ok {
for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
if text, ok := value.PropertyValue(string(tag)); ok {
if !bounds.Set(tag, text) {
notCompatibleType(tag, value)
return false
return nil
}
}
}
properties.properties[tag] = bounds
properties.setRaw(tag, bounds)
default:
if n, ok := isInt(value); ok {
properties.properties[tag] = Px(float64(n))
properties.setRaw(tag, Px(float64(n)))
} else {
notCompatibleType(tag, value)
return false
return nil
}
}
}
return true
return []PropertyName{tag}
}
func (properties *propertyList) boundsProperty(tag string) BoundsProperty {
if value, ok := properties.properties[tag]; ok {
func removeBoundsPropertySide(properties Properties, mainTag, sideTag PropertyName) []PropertyName {
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) {
case string:
bounds := NewBoundsProperty(nil)
for _, t := range []string{Top, Right, Bottom, Left} {
for _, t := range []PropertyName{Top, Right, Bottom, Left} {
bounds.Set(t, value)
}
return bounds
case SizeUnit:
bounds := NewBoundsProperty(nil)
for _, t := range []string{Top, Right, Bottom, Left} {
for _, t := range []PropertyName{Top, Right, Bottom, Left} {
bounds.Set(t, value)
}
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) {
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) {
func getBounds(properties Properties, tag PropertyName, session Session) (Bounds, bool) {
if value := properties.Get(tag); value != nil {
switch value := value.(type) {
case string:

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ const (
// `func(newColor rui.Color)`,
// `func(picker rui.ColorPicker)`,
// `func()`.
ColorChangedEvent = "color-changed"
ColorChangedEvent PropertyName = "color-changed"
// 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.
// See `Color` description for more details.
ColorPickerValue = "color-picker-value"
ColorPickerValue PropertyName = "color-picker-value"
)
// ColorPicker represent a ColorPicker view
@ -46,8 +46,6 @@ type ColorPicker interface {
type colorPickerData struct {
viewData
dataList
colorChangedListeners []func(ColorPicker, Color, Color)
}
// NewColorPicker create new ColorPicker object and return it
@ -59,125 +57,69 @@ func NewColorPicker(session Session, params Params) ColorPicker {
}
func newColorPicker(session Session) View {
return NewColorPicker(session, nil)
return new(colorPickerData)
}
func (picker *colorPickerData) init(session Session) {
picker.viewData.init(session)
picker.tag = "ColorPicker"
picker.hasHtmlDisabled = true
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.properties[Padding] = Px(0)
picker.dataListInit()
picker.normalize = normalizeColorPickerTag
picker.set = colorPickerSet
picker.changed = colorPickerPropertyChanged
}
func (picker *colorPickerData) String() string {
return getViewString(picker, nil)
}
func (picker *colorPickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeColorPickerTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case Value, ColorTag:
return ColorPickerValue
}
return picker.normalizeDataListTag(tag)
return normalizeDataListTag(tag)
}
func (picker *colorPickerData) Remove(tag string) {
picker.remove(picker.normalizeTag(tag))
}
func (picker *colorPickerData) remove(tag string) {
func colorPickerSet(view View, tag PropertyName, value any) []PropertyName {
switch tag {
case ColorChangedEvent:
if len(picker.colorChangedListeners) > 0 {
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.propertyChangedEvent(tag)
}
return setEventWithOldListener[ColorPicker, Color](view, tag, value)
case ColorPickerValue:
oldColor := GetColorPickerValue(picker)
delete(picker.properties, ColorPickerValue)
picker.colorChanged(oldColor)
oldColor := GetColorPickerValue(view)
result := setColorProperty(view, ColorPickerValue, value)
if result != nil {
view.setRaw("old-color", oldColor)
}
return result
case DataList:
if len(picker.dataList.dataList) > 0 {
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 setDataList(view, value, "")
}
return viewSet(view, tag, value)
}
func colorPickerPropertyChanged(view View, tag PropertyName) {
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:
oldColor := GetColorPickerValue(picker)
if picker.setColorProperty(ColorPickerValue, value) {
picker.colorChanged(oldColor)
return true
}
color := GetColorPickerValue(view)
view.Session().callFunc("setInputValue", view.htmlID(), color.rgbString())
case DataList:
return picker.setDataList(picker, value, picker.created)
if listeners := GetColorChangedListeners(view); len(listeners) > 0 {
oldColor := Color(0)
if value := view.getRaw("old-color"); value != nil {
oldColor = value.(Color)
}
for _, listener := range listeners {
listener(view, color, oldColor)
}
}
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 {
@ -185,7 +127,10 @@ func (picker *colorPickerData) htmlTag() string {
}
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) {
@ -200,20 +145,23 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder
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 {
case "textChanged":
if text, ok := data.PropertyValue("text"); ok {
oldColor := GetColorPickerValue(picker)
if color, ok := StringToColor(text); ok {
oldColor := GetColorPickerValue(picker)
picker.properties[ColorPickerValue] = color
if color != oldColor {
for _, listener := range picker.colorChangedListeners {
for _, listener := range GetColorChangedListeners(picker) {
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 {
return value
}
for _, tag := range []string{ColorPickerValue, Value, ColorTag} {
for _, tag := range []PropertyName{ColorPickerValue, Value, ColorTag} {
if value := valueFromStyle(view, tag); value != nil {
if result, ok := valueToColor(value, view.Session()); ok {
return result

View File

@ -2,7 +2,6 @@ package rui
import (
"strconv"
"strings"
)
// Constants for [ColumnLayout] specific properties and events
@ -10,7 +9,7 @@ const (
// ColumnCount is the constant for "column-count" property tag.
//
// Used by `ColumnLayout`.
// Specifies number of columns into which the content is break. Values less than zero are not valid. If this property
// Specifies number of columns into which the content is break. Values less than zero are not valid. If this property
// value is 0 then the number of columns is calculated based on the "column-width" property.
//
// Supported types: `int`, `string`.
@ -18,7 +17,7 @@ const (
// Values:
// `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.
ColumnCount = "column-count"
ColumnCount PropertyName = "column-count"
// 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.
// See `SizeUnit` description for more details.
ColumnWidth = "column-width"
ColumnWidth PropertyName = "column-width"
// 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.
// See `SizeUnit` description for more details.
ColumnGap = "column-gap"
ColumnGap PropertyName = "column-gap"
// 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.
// 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.
//
@ -66,7 +65,7 @@ const (
// `2`(`DashedLine`) or "dashed" - Dashed line as a separator.
// `3`(`DottedLine`) or "dotted" - Dotted 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.
//
@ -77,7 +76,7 @@ const (
//
// Internal type is `SizeUnit`, other types converted to it during assignment.
// 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.
//
@ -88,7 +87,7 @@ const (
//
// Internal type is `Color`, other types converted to it during assignment.
// See `Color` description for more details.
ColumnSeparatorColor = "column-separator-color"
ColumnSeparatorColor PropertyName = "column-separator-color"
// ColumnFill is the constant for "column-fill" property tag.
//
@ -100,12 +99,12 @@ const (
// Values:
// `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.
ColumnFill = "column-fill"
ColumnFill PropertyName = "column-fill"
// ColumnSpanAll is the constant for "column-span-all" property tag.
//
// Used by `ColumnLayout`.
// Property used in views placed inside the column layout container. Makes it possible for a view to span across all
// Property used in views placed inside the column layout container. Makes it possible for a view to span across all
// columns. Default value is `false`.
//
// Supported types: `bool`, `int`, `string`.
@ -113,7 +112,7 @@ const (
// Values:
// `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.
ColumnSpanAll = "column-span-all"
ColumnSpanAll PropertyName = "column-span-all"
)
// ColumnLayout represent a ColumnLayout view
@ -134,22 +133,20 @@ func NewColumnLayout(session Session, params Params) ColumnLayout {
}
func newColumnLayout(session Session) View {
return NewColumnLayout(session, nil)
return new(columnLayoutData)
}
// Init initialize fields of ColumnLayout by default values
func (ColumnLayout *columnLayoutData) init(session Session) {
ColumnLayout.viewsContainerData.init(session)
ColumnLayout.tag = "ColumnLayout"
//ColumnLayout.systemClass = "ruiColumnLayout"
func (columnLayout *columnLayoutData) init(session Session) {
columnLayout.viewsContainerData.init(session)
columnLayout.tag = "ColumnLayout"
columnLayout.normalize = normalizeColumnLayoutTag
columnLayout.changed = columnLayoutPropertyChanged
//columnLayout.systemClass = "ruiColumnLayout"
}
func (columnLayout *columnLayoutData) String() string {
return getViewString(columnLayout, nil)
}
func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeColumnLayoutTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case Gap:
return ColumnGap
@ -157,62 +154,28 @@ func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
return tag
}
func (columnLayout *columnLayoutData) Get(tag string) any {
return columnLayout.get(columnLayout.normalizeTag(tag))
}
func (columnLayout *columnLayoutData) Remove(tag string) {
columnLayout.remove(columnLayout.normalizeTag(tag))
}
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", "")
func columnLayoutPropertyChanged(view View, tag PropertyName) {
switch tag {
case ColumnSeparator:
css := ""
session := view.Session()
if value := view.getRaw(ColumnSeparator); value != nil {
separator := value.(ColumnSeparatorProperty)
css = separator.cssValue(view.Session())
}
}
}
session.updateCSSProperty(view.htmlID(), "column-rule", css)
func (columnLayout *columnLayoutData) Set(tag string, value any) bool {
return columnLayout.set(columnLayout.normalizeTag(tag), value)
}
func (columnLayout *columnLayoutData) set(tag string, value any) bool {
if value == nil {
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")
}
case ColumnCount:
session := view.Session()
if count, ok := intProperty(view, tag, session, 0); ok && count > 0 {
session.updateCSSProperty(view.htmlID(), string(ColumnCount), strconv.Itoa(count))
} else {
session.updateCSSProperty(view.htmlID(), string(ColumnCount), "auto")
}
default:
viewsContainerPropertyChanged(view, tag)
}
return true
}
// 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 {
propertyList
dataProperty
}
func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
if value == nil {
separator := new(columnSeparatorProperty)
separator.properties = map[string]any{}
separator.init()
return separator
}
@ -35,17 +35,18 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
case DataObject:
separator := new(columnSeparatorProperty)
separator.properties = map[string]any{}
for _, tag := range []string{Style, Width, ColorTag} {
if val, ok := value.PropertyValue(tag); ok && val != "" {
separator.set(tag, value)
separator.init()
for _, tag := range []PropertyName{Style, Width, ColorTag} {
if val, ok := value.PropertyValue(string(tag)); ok && val != "" {
propertiesSet(separator, tag, value)
}
}
return separator
case ViewBorder:
separator := new(columnSeparatorProperty)
separator.properties = map[string]any{
separator.init()
separator.properties = map[PropertyName]any{
Style: value.Style,
Width: value.Width,
ColorTag: value.Color,
@ -67,9 +68,9 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
// "width" (Width). Determines the line thickness (SizeUnit).
func NewColumnSeparator(params Params) ColumnSeparatorProperty {
separator := new(columnSeparatorProperty)
separator.properties = map[string]any{}
separator.init()
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 {
separator.Set(tag, value)
}
@ -78,8 +79,14 @@ func NewColumnSeparator(params Params) ColumnSeparatorProperty {
return separator
}
func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func (separator *columnSeparatorProperty) init() {
separator.dataProperty.init()
separator.normalize = normalizeVolumnSeparatorTag
separator.supportedProperties = []PropertyName{Style, Width, ColorTag}
}
func normalizeVolumnSeparatorTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case ColumnSeparatorStyle, "separator-style":
return Style
@ -97,12 +104,12 @@ func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
func (separator *columnSeparatorProperty) writeString(buffer *strings.Builder, indent string) {
buffer.WriteString("_{ ")
comma := false
for _, tag := range []string{Style, Width, ColorTag} {
for _, tag := range []PropertyName{Style, Width, ColorTag} {
if value, ok := separator.properties[tag]; ok {
if comma {
buffer.WriteString(", ")
}
buffer.WriteString(tag)
buffer.WriteString(string(tag))
buffer.WriteString(" = ")
writePropertyValue(buffer, BorderStyle, value, indent)
comma = true
@ -116,47 +123,12 @@ func (separator *columnSeparatorProperty) String() string {
return runStringWriter(separator)
}
func (separator *columnSeparatorProperty) Remove(tag string) {
switch tag = separator.normalizeTag(tag); tag {
case Style, Width, ColorTag:
delete(separator.properties, tag)
default:
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
func getColumnSeparatorProperty(properties Properties) ColumnSeparatorProperty {
if val := properties.getRaw(ColumnSeparator); val != nil {
if separator, ok := val.(ColumnSeparatorProperty); ok {
return separator
}
}
}
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
}

View File

@ -36,6 +36,9 @@ func InitCustomView(customView CustomView, tag string, session Session, params P
return true
}
func (customView *CustomViewData) init(session Session) {
}
// SuperView returns a super view
func (customView *CustomViewData) SuperView() View {
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.
// 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)
}
func (customView *CustomViewData) getRaw(tag string) any {
func (customView *CustomViewData) getRaw(tag PropertyName) any {
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)
}
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.
// 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
func (customView *CustomViewData) Set(tag string, value any) bool {
func (customView *CustomViewData) Set(tag PropertyName, value any) bool {
return customView.superView.Set(tag, value)
}
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
// Return "true" if the value has been set, in the opposite case "false" are returned and
// a description of the error is written to the log
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)
}
@ -88,20 +98,24 @@ func (customView *CustomViewData) SetParams(params Params) bool {
}
// 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)
}
// 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)
}
// AllTags returns an array of the set properties
func (customView *CustomViewData) AllTags() []string {
func (customView *CustomViewData) AllTags() []PropertyName {
return customView.superView.AllTags()
}
func (customView *CustomViewData) empty() bool {
return customView.superView.empty()
}
// Clear removes all properties
func (customView *CustomViewData) 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)
}
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)
}
@ -210,6 +224,10 @@ func (customView *CustomViewData) htmlProperties(self View, buffer *strings.Buil
customView.superView.htmlProperties(customView.superView, buffer)
}
func (customView *CustomViewData) htmlDisabledProperty() bool {
return customView.superView.htmlDisabledProperty()
}
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
customView.superView.cssStyle(customView.superView, builder)
}
@ -274,9 +292,9 @@ func (customView *CustomViewData) ViewIndex(view View) int {
return -1
}
func (customView *CustomViewData) exscludeTags() []string {
func (customView *CustomViewData) exscludeTags() []PropertyName {
if customView.superView != nil {
exsclude := []string{}
exsclude := []PropertyName{}
for tag, value := range customView.defaultParams {
if value == customView.superView.getRaw(tag) {
exsclude = append(exsclude, tag)
@ -290,7 +308,10 @@ func (customView *CustomViewData) exscludeTags() []string {
// String convert internal representation of a [CustomViewData] into a string.
func (customView *CustomViewData) String() string {
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 + " { }"
}
@ -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.
func (customView *CustomViewData) Transition(tag string) Animation {
func (customView *CustomViewData) Transition(tag PropertyName) Animation {
if customView.superView != nil {
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.
func (customView *CustomViewData) Transitions() map[string]Animation {
func (customView *CustomViewData) Transitions() map[PropertyName]Animation {
if customView.superView != nil {
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
// removes the transition animation of the property if "animation" argument is nil.
// 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 {
customView.superView.SetTransition(tag, animation)
}

View File

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

View File

@ -1,6 +1,11 @@
package rui
import "strings"
import (
"fmt"
"strconv"
"strings"
"time"
)
const (
// DataList is the constant for "data-list" property tag.
@ -10,8 +15,8 @@ const (
// Usage in `ColorPicker`:
// List of pre-defined colors.
//
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// `uint64`.
//
// Internal type is `[]string`, other types converted to it during assignment.
@ -25,12 +30,12 @@ const (
// `[]any` - this array must contain only types which were listed in Types section.
//
// Usage in `DatePicker`:
// List of predefined dates. If we set this property, date picker may have a drop-down menu with a list of these values.
// Some browsers may ignore this property, such as Safari for macOS. The value of this property must be an array of
// List of predefined dates. If we set this property, date picker may have a drop-down menu with a list of these values.
// Some browsers may ignore this property, such as Safari for macOS. The value of this property must be an array of
// strings in the format "YYYY-MM-DD".
//
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// `uint64`.
//
// Internal type is `[]string`, other types converted to it during assignment.
@ -46,8 +51,8 @@ const (
// Usage in `EditView`:
// Array of recommended values.
//
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// `uint64`.
//
// Internal type is `[]string`, other types converted to it during assignment.
@ -63,8 +68,8 @@ const (
// Usage in `NumberPicker`:
// Specify an array of recommended values.
//
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]float`, `[]int`,
// `[]bool`, `[]any` containing elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8`
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]float`, `[]int`,
// `[]bool`, `[]any` containing elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8`
// … `int64`, `uint`, `uint8` … `uint64`.
//
// Internal type is `[]string`, other types converted to it during assignment.
@ -82,11 +87,11 @@ const (
// `[]any` - an array which may contain types listed in Types section above, each value will be converted to a `string` and wrapped to array.
//
// Usage in `TimePicker`:
// An array of recommended values. The value of this property must be an array of strings in the format "HH:MM:SS" or
// An array of recommended values. The value of this property must be an array of strings in the format "HH:MM:SS" or
// "HH:MM".
//
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
// `uint64`.
//
// Internal type is `[]string`, other types converted to it during assignment.
@ -98,23 +103,14 @@ const (
// `[]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.
// `[]any` - this array must contain only types which were listed in Types section.
DataList = "data-list"
DataList PropertyName = "data-list"
)
type dataList struct {
dataList []string
dataListHtml bool
}
func (list *dataList) dataListInit() {
list.dataList = []string{}
}
func (list *dataList) dataListID(view View) string {
func dataListID(view View) string {
return view.htmlID() + "-datalist"
}
func (list *dataList) normalizeDataListTag(tag string) string {
func normalizeDataListTag(tag PropertyName) PropertyName {
switch tag {
case "datalist":
return DataList
@ -123,6 +119,210 @@ func (list *dataList) normalizeDataListTag(tag string) string {
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 {
items, ok := anyToStringArray(value)
if !ok {
@ -133,7 +333,7 @@ func (list *dataList) setDataList(view View, value any, created bool) bool {
list.dataList = items
if created {
session := view.Session()
dataListID := list.dataListID(view)
dataListID := dataListID(view)
buffer := allocStringBuilder()
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) {
buffer.WriteString(`<datalist id="`)
buffer.WriteString(list.dataListID(view))
buffer.WriteString(dataListID(view))
buffer.WriteString(`">`)
list.dataListItemsHtml(buffer)
buffer.WriteString(`</datalist>`)
@ -185,10 +385,11 @@ func (list *dataList) dataListItemsHtml(buffer *strings.Builder) {
func (list *dataList) dataListHtmlProperties(view View, buffer *strings.Builder) {
if len(list.dataList) > 0 {
buffer.WriteString(` list="`)
buffer.WriteString(list.dataListID(view))
buffer.WriteString(dataListID(view))
buffer.WriteString(`"`)
}
}
*/
// 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.
@ -198,11 +399,7 @@ func GetDataList(view View, subviewID ...string) []string {
}
if view != nil {
if value := view.Get(DataList); value != nil {
if list, ok := value.([]string); ok {
return list
}
}
return getDataListProperty(view)
}
return []string{}

View File

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

View File

@ -13,7 +13,7 @@ const (
//
// `string` - Summary as a text.
// `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.
//
@ -25,7 +25,7 @@ const (
// Values:
// `true` or `1` or "true", "yes", "on", "1" - Content is visible.
// `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
@ -46,19 +46,21 @@ func NewDetailsView(session Session, params Params) DetailsView {
}
func newDetailsView(session Session) View {
return NewDetailsView(session, nil)
return new(detailsViewData)
}
// Init initialize fields of DetailsView by default values
func (detailsView *detailsViewData) init(session Session) {
detailsView.viewsContainerData.init(session)
detailsView.tag = "DetailsView"
detailsView.set = detailsView.setFunc
detailsView.changed = detailsViewPropertyChanged
//detailsView.systemClass = "ruiDetailsView"
}
func (detailsView *detailsViewData) Views() []View {
views := detailsView.viewsContainerData.Views()
if summary := detailsView.get(Summary); summary != nil {
if summary := detailsView.Get(Summary); summary != nil {
switch summary := summary.(type) {
case View:
return append([]View{summary}, views...)
@ -67,94 +69,53 @@ func (detailsView *detailsViewData) Views() []View {
return views
}
func (detailsView *detailsViewData) Remove(tag string) {
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
}
func (detailsView *detailsViewData) setFunc(self View, tag PropertyName, value any) []PropertyName {
switch tag {
case Summary:
switch value := value.(type) {
case string:
detailsView.properties[Summary] = value
detailsView.setRaw(Summary, value)
case View:
detailsView.properties[Summary] = value
detailsView.setRaw(Summary, value)
value.setParentID(detailsView.htmlID())
case DataObject:
if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
detailsView.properties[Summary] = view
detailsView.setRaw(Summary, view)
view.setParentID(detailsView.htmlID())
} else {
return false
return nil
}
default:
notCompatibleType(tag, value)
return false
}
if detailsView.created {
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
return nil
}
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:
if !detailsView.setBoolProperty(tag, value) {
notCompatibleType(tag, value)
return false
}
if detailsView.created {
if IsDetailsExpanded(detailsView) {
detailsView.session.updateProperty(detailsView.htmlID(), "open", "")
} else {
detailsView.session.removeProperty(detailsView.htmlID(), "open")
}
if IsDetailsExpanded(view) {
view.Session().updateProperty(view.htmlID(), "open", "")
} else {
view.Session().removeProperty(view.htmlID(), "open")
}
case NotTranslate:
if !detailsView.viewData.set(tag, value) {
return false
}
if detailsView.created {
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
}
updateInnerHTML(view.htmlID(), view.Session())
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 {
@ -190,11 +151,13 @@ func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Buil
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 n, ok := dataIntProperty(data, "open"); ok {
detailsView.properties[Expanded] = (n != 0)
detailsView.propertyChangedEvent(Expanded)
if listener, ok := detailsView.changeListener[Current]; ok {
listener(detailsView, Current)
}
}
return true
}

View File

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

View File

@ -26,7 +26,7 @@ const (
// `func(newText string)`,
// `func(editView rui.EditView)`,
// `func()`.
EditTextChangedEvent = "edit-text-changed"
EditTextChangedEvent PropertyName = "edit-text-changed"
// EditViewType is the constant for "edit-view-type" property tag.
//
@ -43,7 +43,7 @@ const (
// `4`(`URLText`) or "url" - Internet address input editor.
// `5`(`PhoneText`) or "phone" - Phone number 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.
//
@ -51,12 +51,12 @@ const (
// Regular expression to limit editing of a text.
//
// Supported types: `string`.
EditViewPattern = "edit-view-pattern"
EditViewPattern PropertyName = "edit-view-pattern"
// Spellcheck is the constant for "spellcheck" property tag.
//
// Used by `EditView`.
// Enable or disable spell checker. Available in `SingleLineText` and `MultiLineText` types of edit view. Default value is
// Enable or disable spell checker. Available in `SingleLineText` and `MultiLineText` types of edit view. Default value is
// `false`.
//
// Supported types: `bool`, `int`, `string`.
@ -64,7 +64,7 @@ const (
// Values:
// `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.
Spellcheck = "spellcheck"
Spellcheck PropertyName = "spellcheck"
)
// 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(text string)
textChanged(newText, oldText string)
}
type editViewData struct {
viewData
dataList
textChangeListeners []func(EditView, string, string)
}
// NewEditView create new EditView object and return it
@ -114,27 +113,24 @@ func NewEditView(session Session, params Params) EditView {
}
func newEditView(session Session) View {
return NewEditView(session, nil)
return new(editViewData) // NewEditView(session, nil)
}
func (edit *editViewData) init(session Session) {
edit.viewData.init(session)
edit.hasHtmlDisabled = true
edit.textChangeListeners = []func(EditView, string, string){}
edit.tag = "EditView"
edit.dataListInit()
}
func (edit *editViewData) String() string {
return getViewString(edit, nil)
edit.normalize = normalizeEditViewTag
edit.set = editViewSet
edit.changed = editViewPropertyChanged
}
func (edit *editViewData) Focusable() bool {
return true
}
func (edit *editViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
func normalizeEditViewTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case Type, "edit-type":
return EditViewType
@ -149,279 +145,109 @@ func (edit *editViewData) normalizeTag(tag string) string {
return EditWrap
}
return edit.normalizeDataListTag(tag)
return normalizeDataListTag(tag)
}
func (edit *editViewData) Remove(tag string) {
edit.remove(edit.normalizeTag(tag))
}
func (edit *editViewData) remove(tag string) {
_, exists := edit.properties[tag]
func editViewSet(view View, tag PropertyName, value any) []PropertyName {
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:
if exists {
oldText := GetText(edit)
delete(edit.properties, tag)
if oldText != "" {
edit.textChanged("", oldText)
if edit.created {
edit.session.callFunc("setInputValue", edit.htmlID(), "")
if text, ok := value.(string); ok {
old := ""
if val := view.getRaw(Text); val != nil {
if txt, ok := val.(string); ok {
old = txt
}
}
view.setRaw("old-text", old)
view.setRaw(tag, text)
return []PropertyName{tag}
}
case EditViewPattern:
if exists {
oldText := GetEditViewPattern(edit)
delete(edit.properties, tag)
if oldText != "" {
if edit.created {
edit.session.removeProperty(edit.htmlID(), Pattern)
}
edit.propertyChangedEvent(tag)
}
}
notCompatibleType(tag, value)
return nil
case EditViewType:
if exists {
oldType := GetEditViewType(edit)
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)
}
}
case Hint:
if text, ok := value.(string); ok {
return setStringPropertyValue(view, tag, strings.Trim(text, " \t\n"))
}
notCompatibleType(tag, value)
return nil
case DataList:
if len(edit.dataList.dataList) > 0 {
edit.setDataList(edit, []string{}, true)
}
setDataList(view, value, "")
default:
edit.viewData.remove(tag)
case EditTextChangedEvent:
return setEventWithOldListener[EditView, string](view, tag, value)
}
return viewSet(view, tag, value)
}
func (edit *editViewData) Set(tag string, value any) bool {
return edit.set(edit.normalizeTag(tag), value)
}
func (edit *editViewData) set(tag string, value any) bool {
if value == nil {
edit.remove(tag)
return true
}
func editViewPropertyChanged(view View, tag PropertyName) {
session := view.Session()
switch tag {
case Text:
if text, ok := value.(string); ok {
oldText := GetText(edit)
edit.properties[Text] = text
if text = GetText(edit); oldText != text {
edit.textChanged(text, oldText)
if edit.created {
edit.session.callFunc("setInputValue", edit.htmlID(), text)
text := GetText(view)
session.callFunc("setInputValue", view.htmlID(), text)
if edit, ok := view.(EditView); ok {
old := ""
if val := view.getRaw("old-text"); val != nil {
if txt, ok := val.(string); ok {
old = txt
}
}
return true
edit.textChanged(text, old)
}
return false
case Hint:
if text, ok := value.(string); ok {
oldText := GetHint(edit)
edit.properties[Hint] = text
if text = GetHint(edit); oldText != text {
if edit.created {
if text != "" {
edit.session.updateProperty(edit.htmlID(), "placeholder", text)
} else {
edit.session.removeProperty(edit.htmlID(), "placeholder")
}
}
edit.propertyChangedEvent(tag)
}
return true
if text := GetHint(view); text != "" {
session.updateProperty(view.htmlID(), "placeholder", text)
} else {
session.removeProperty(view.htmlID(), "placeholder")
}
return false
case MaxLength:
oldMaxLength := GetMaxLength(edit)
if edit.setIntProperty(MaxLength, value) {
if maxLength := GetMaxLength(edit); maxLength != oldMaxLength {
if edit.created {
if maxLength > 0 {
edit.session.updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength))
} else {
edit.session.removeProperty(edit.htmlID(), "maxlength")
}
}
edit.propertyChangedEvent(tag)
}
return true
if maxLength := GetMaxLength(view); maxLength > 0 {
session.updateProperty(view.htmlID(), "maxlength", strconv.Itoa(maxLength))
} else {
session.removeProperty(view.htmlID(), "maxlength")
}
return false
case ReadOnly:
if edit.setBoolProperty(ReadOnly, value) {
if edit.created {
if IsReadOnly(edit) {
edit.session.updateProperty(edit.htmlID(), ReadOnly, "")
} else {
edit.session.removeProperty(edit.htmlID(), ReadOnly)
}
}
edit.propertyChangedEvent(tag)
return true
if IsReadOnly(view) {
session.updateProperty(view.htmlID(), "readonly", "")
} else {
session.removeProperty(view.htmlID(), "readonly")
}
return false
case Spellcheck:
if edit.setBoolProperty(Spellcheck, value) {
if edit.created {
edit.session.updateProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit))
}
edit.propertyChangedEvent(tag)
return true
}
return false
session.updateProperty(view.htmlID(), "spellcheck", IsSpellcheck(view))
case EditViewPattern:
oldText := GetEditViewPattern(edit)
if text, ok := value.(string); ok {
edit.properties[EditViewPattern] = text
if text = GetEditViewPattern(edit); oldText != text {
if edit.created {
if text != "" {
edit.session.updateProperty(edit.htmlID(), Pattern, text)
} else {
edit.session.removeProperty(edit.htmlID(), Pattern)
}
}
edit.propertyChangedEvent(tag)
}
return true
if text := GetEditViewPattern(view); text != "" {
session.updateProperty(view.htmlID(), "pattern", text)
} else {
session.removeProperty(view.htmlID(), "pattern")
}
return false
case EditViewType:
oldType := GetEditViewType(edit)
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
updateInnerHTML(view.parentHTMLID(), session)
case EditWrap:
oldWrap := IsEditViewWrap(edit)
if edit.setBoolProperty(EditWrap, value) {
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)
}
}
return true
if wrap := IsEditViewWrap(view); wrap {
session.updateProperty(view.htmlID(), "wrap", "soft")
} else {
session.updateProperty(view.htmlID(), "wrap", "off")
}
return false
case DataList:
return edit.setDataList(edit, value, edit.created)
updateInnerHTML(view.htmlID(), session)
case EditTextChangedEvent:
listeners, ok := valueToEventWithOldListeners[EditView, string](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(EditView, string, string){}
}
edit.textChangeListeners = listeners
edit.propertyChangedEvent(tag)
return true
default:
viewPropertyChanged(view, tag)
}
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) {
@ -432,21 +258,24 @@ func (edit *editViewData) AppendText(text string) {
textValue += text
edit.properties[Text] = textValue
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
edit.session.callFunc("appendToInputValue", edit.htmlID(), text)
edit.textChanged(textValue, oldText)
return
}
}
edit.set(Text, text)
edit.setRaw(Text, text)
} else {
edit.set(Text, GetText(edit)+text)
edit.setRaw(Text, GetText(edit)+text)
}
}
func (edit *editViewData) textChanged(newText, oldText string) {
for _, listener := range edit.textChangeListeners {
for _, listener := range GetTextChangedListeners(edit) {
listener(edit, newText, oldText)
}
edit.propertyChangedEvent(Text)
if listener, ok := edit.changeListener[Text]; ok {
listener(edit, Text)
}
}
func (edit *editViewData) htmlTag() string {
@ -462,7 +291,9 @@ func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
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) {
@ -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 {
case "textChanged":
oldText := GetText(edit)
if text, ok := data.PropertyValue("text"); ok {
edit.properties[Text] = text
if text := GetText(edit); text != oldText {
edit.setRaw(Text, text)
if 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(files []rui.FileInfo)`,
// `func()`.
FileSelectedEvent = "file-selected-event"
FileSelectedEvent PropertyName = "file-selected-event"
// Accept is the constant for "accept" property tag.
//
@ -39,7 +39,7 @@ const (
// Conversion rules:
// `string` - may contain single value of multiple separated by comma(`,`).
// `[]string` - an array of acceptable file extensions or MIME types.
Accept = "accept"
Accept PropertyName = "accept"
// Multiple is the constant for "multiple" property tag.
//
@ -51,7 +51,7 @@ const (
// Values:
// `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.
Multiple = "multiple"
Multiple PropertyName = "multiple"
)
// FileInfo describes a file which selected in the FilePicker view
@ -82,9 +82,8 @@ type FilePicker interface {
type filePickerData struct {
viewData
files []FileInfo
fileSelectedListeners []func(FilePicker, []FileInfo)
loader map[int]func(FileInfo, []byte)
files []FileInfo
loader map[int]func(FileInfo, []byte)
}
func (file *FileInfo) initBy(node DataValue) {
@ -115,7 +114,7 @@ func NewFilePicker(session Session, params Params) FilePicker {
}
func newFilePicker(session Session) View {
return NewFilePicker(session, nil)
return new(filePickerData) // NewFilePicker(session, nil)
}
func (picker *filePickerData) init(session Session) {
@ -124,11 +123,9 @@ func (picker *filePickerData) init(session Session) {
picker.hasHtmlDisabled = true
picker.files = []FileInfo{}
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 {
@ -153,62 +150,27 @@ func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []by
}
}
func (picker *filePickerData) Remove(tag string) {
picker.remove(strings.ToLower(tag))
}
func filePickerSet(view View, tag PropertyName, value any) []PropertyName {
func (picker *filePickerData) remove(tag string) {
switch tag {
case FileSelectedEvent:
if len(picker.fileSelectedListeners) > 0 {
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
picker.propertyChangedEvent(tag)
setAccept := func(value string) []PropertyName {
if value != "" {
view.setRaw(tag, value)
} else if view.getRaw(tag) != nil {
view.setRaw(tag, nil)
} else {
return []PropertyName{}
}
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
return []PropertyName{Accept}
}
switch tag {
case FileSelectedEvent:
listeners, ok := valueToEventListeners[FilePicker, []FileInfo](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(FilePicker, []FileInfo){}
}
picker.fileSelectedListeners = listeners
picker.propertyChangedEvent(tag)
return true
return setViewEventListener[FilePicker, []FileInfo](view, tag, value)
case Accept:
switch value := value.(type) {
case string:
value = strings.Trim(value, " \t\n")
if value == "" {
picker.remove(Accept)
} else {
picker.properties[Accept] = value
}
return setAccept(strings.Trim(value, " \t\n"))
case []string:
buffer := allocStringBuilder()
@ -222,29 +184,27 @@ func (picker *filePickerData) set(tag string, value any) bool {
buffer.WriteString(val)
}
}
if buffer.Len() == 0 {
picker.remove(Accept)
} else {
picker.properties[Accept] = buffer.String()
}
default:
notCompatibleType(tag, value)
return false
return setAccept(buffer.String())
}
notCompatibleType(tag, value)
return nil
}
if picker.created {
if css := picker.acceptCSS(); css != "" {
picker.session.updateProperty(picker.htmlID(), "accept", css)
} else {
picker.session.removeProperty(picker.htmlID(), "accept")
}
return viewSet(view, tag, value)
}
func filePickerPropertyChanged(view View, tag PropertyName) {
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:
return picker.viewData.set(tag, value)
viewPropertyChanged(view, tag)
}
}
@ -252,10 +212,10 @@ func (picker *filePickerData) htmlTag() string {
return "input"
}
func (picker *filePickerData) acceptCSS() string {
accept, ok := stringProperty(picker, Accept, picker.Session())
func acceptPropertyCSS(view View) string {
accept, ok := stringProperty(view, Accept, view.Session())
if !ok {
if value := valueFromStyle(picker, Accept); value != nil {
if value := valueFromStyle(view, Accept); value != nil {
accept, ok = value.(string)
}
}
@ -282,7 +242,7 @@ func (picker *filePickerData) acceptCSS() string {
func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) {
picker.viewData.htmlProperties(self, buffer)
if accept := picker.acceptCSS(); accept != "" {
if accept := acceptPropertyCSS(picker); accept != "" {
buffer.WriteString(` accept="`)
buffer.WriteString(accept)
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 {
case "fileSelected":
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
for _, listener := range picker.fileSelectedListeners {
for _, listener := range GetFileSelectedListeners(picker) {
listener(picker, files)
}
}

View File

@ -17,7 +17,7 @@ const (
//
// Allowed listener formats:
// `func()`.
FocusEvent = "focus-event"
FocusEvent PropertyName = "focus-event"
// LostFocusEvent is the constant for "lost-focus-event" property tag.
//
@ -32,136 +32,18 @@ const (
//
// Allowed listener formats:
// `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) {