From 7f06a9d201228f22fcd5a6172ddefe2b11073c7f Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:52:47 +0300 Subject: [PATCH] Added "transform" property and Transform interface --- CHANGELOG.md | 1 + propertySet.go | 12 --- viewStyle.go | 10 +- viewStyleSet.go | 7 ++ viewTransform.go | 244 ++++++++++++++++++++++++++++++++++++++++++----- viewUtils.go | 10 +- 6 files changed, 245 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e18103..5255232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Added "mod", "rem", "round", "round-up", "round-down", and "round-to-zero" SizeFunc functions * Added ModSize, RemSize, RoundSize, RoundUpSize, RoundDownSize, and RoundToZeroSize functions * Added Start, Stop, Pause, and Resume methods to Animation interface +* Added "transform" property and Transform interface * Added OpenRawResource function # v0.16.0 diff --git a/propertySet.go b/propertySet.go index f858ab8..95fdc3a 100644 --- a/propertySet.go +++ b/propertySet.go @@ -31,9 +31,6 @@ func isPropertyInList(tag string, list []string) bool { } var angleProperties = []string{ - Rotate, - SkewX, - SkewY, From, } @@ -80,12 +77,6 @@ var intProperties = []string{ var floatProperties = map[string]struct{ min, max float64 }{ Opacity: {min: 0, max: 1}, - ScaleX: {min: -math.MaxFloat64, max: math.MaxFloat64}, - ScaleY: {min: -math.MaxFloat64, max: math.MaxFloat64}, - ScaleZ: {min: -math.MaxFloat64, max: math.MaxFloat64}, - RotateX: {min: 0, max: 1}, - RotateY: {min: 0, max: 1}, - RotateZ: {min: 0, max: 1}, NumberPickerMax: {min: -math.MaxFloat64, max: math.MaxFloat64}, NumberPickerMin: {min: -math.MaxFloat64, max: math.MaxFloat64}, NumberPickerStep: {min: -math.MaxFloat64, max: math.MaxFloat64}, @@ -147,9 +138,6 @@ var sizeProperties = map[string]string{ OriginX: OriginX, OriginY: OriginY, OriginZ: OriginZ, - TranslateX: TranslateX, - TranslateY: TranslateY, - TranslateZ: TranslateZ, Radius: Radius, RadiusX: RadiusX, RadiusY: RadiusY, diff --git a/viewStyle.go b/viewStyle.go index 5332638..122a92f 100644 --- a/viewStyle.go +++ b/viewStyle.go @@ -559,6 +559,13 @@ func (style *viewStyle) get(tag string) any { result[tag] = animation } return result + + case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, + TranslateX, TranslateY, TranslateZ: + if transform := style.transformProperty(); transform != nil { + return transform.Get(tag) + } + return nil } return style.propertyList.getRaw(tag) @@ -889,8 +896,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent finalTags := []string{ Perspective, PerspectiveOriginX, PerspectiveOriginY, BackfaceVisible, OriginX, OriginY, OriginZ, - TranslateX, TranslateY, TranslateZ, ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ, - SkewX, SkewY, Clip, Filter, BackdropFilter, Summary, Content, Transition} + TransformTag, Clip, Filter, BackdropFilter, Summary, Content, Transition} for _, tag := range finalTags { removeTag(tag) } diff --git a/viewStyleSet.go b/viewStyleSet.go index e5b569a..3e78cfc 100644 --- a/viewStyleSet.go +++ b/viewStyleSet.go @@ -386,6 +386,13 @@ func (style *viewStyle) set(tag string, value any) bool { style.properties[Outline] = NewOutlineProperty(Params{tag: value}) return true + case TransformTag: + return style.setTransform(value) + + case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, + TranslateX, TranslateY, TranslateZ: + return style.setTransformProperty(tag, value) + case Orientation: if text, ok := value.(string); ok { switch strings.ToLower(text) { diff --git a/viewTransform.go b/viewTransform.go index 6de024e..a88317a 100644 --- a/viewTransform.go +++ b/viewTransform.go @@ -1,74 +1,262 @@ package rui +import ( + "fmt" + "math" + "strings" +) + const ( // Perspective is the name of the SizeUnit property that determines the distance between the z = 0 plane // and the user in order to give a 3D-positioned element some perspective. Each 3D element // with z > 0 becomes larger; each 3D-element with z < 0 becomes smaller. // The default value is 0 (no 3D effects). Perspective = "perspective" + // PerspectiveOriginX is the name of the SizeUnit property that determines the x-coordinate of the position // at which the viewer is looking. It is used as the vanishing point by the Perspective property. // The default value is 50%. PerspectiveOriginX = "perspective-origin-x" + // PerspectiveOriginY is the name of the SizeUnit property that determines the y-coordinate of the position // at which the viewer is looking. It is used as the vanishing point by the Perspective property. // The default value is 50%. PerspectiveOriginY = "perspective-origin-y" + // BackfaceVisible is the name of the bool property that sets whether the back face of an element is // visible when turned towards the user. Values: // true - the back face is visible when turned towards the user (default value); // false - the back face is hidden, effectively making the element invisible when turned away from the user. BackfaceVisible = "backface-visibility" + // OriginX is the name of the SizeUnit property that determines the x-coordinate of the point around which // a view transformation is applied. // The default value is 50%. OriginX = "origin-x" + // OriginY is the name of the SizeUnit property that determines the y-coordinate of the point around which // a view transformation is applied. // The default value is 50%. OriginY = "origin-y" + // OriginZ is the name of the SizeUnit property that determines the z-coordinate of the point around which // a view transformation is applied. // The default value is 50%. OriginZ = "origin-z" + + // TransformTag is the name of the Transform property that specify the x-, y-, and z-axis translation values, + // the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate, + // the angle of the view rotation + TransformTag = "transform" + // TranslateX is the name of the SizeUnit property that specify the x-axis translation value // of a 2D/3D translation TranslateX = "translate-x" + // TranslateY is the name of the SizeUnit property that specify the y-axis translation value // of a 2D/3D translation TranslateY = "translate-y" + // TranslateZ is the name of the SizeUnit property that specify the z-axis translation value // of a 3D translation TranslateZ = "translate-z" + // ScaleX is the name of the float property that specify the x-axis scaling value of a 2D/3D scale // The default value is 1. ScaleX = "scale-x" + // ScaleY is the name of the float property that specify the y-axis scaling value of a 2D/3D scale // The default value is 1. ScaleY = "scale-y" + // ScaleZ is the name of the float property that specify the z-axis scaling value of a 3D scale // The default value is 1. ScaleZ = "scale-z" + // Rotate is the name of the AngleUnit property that determines the angle of the view rotation. // A positive angle denotes a clockwise rotation, a negative angle a counter-clockwise one. Rotate = "rotate" + // RotateX is the name of the float property that determines the x-coordinate of the vector denoting // the axis of rotation which could between 0 and 1. RotateX = "rotate-x" + // RotateY is the name of the float property that determines the y-coordinate of the vector denoting // the axis of rotation which could between 0 and 1. RotateY = "rotate-y" + // RotateZ is the name of the float property that determines the z-coordinate of the vector denoting // the axis of rotation which could between 0 and 1. RotateZ = "rotate-z" + // SkewX is the name of the AngleUnit property that representing the angle to use to distort // the element along the abscissa. The default value is 0. SkewX = "skew-x" + // SkewY is the name of the AngleUnit property that representing the angle to use to distort // the element along the ordinate. The default value is 0. SkewY = "skew-y" ) +// Transform interface specifies view transformation parameters: the x-, y-, and z-axis translation values, +// the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate, +// the angle of the view rotation. +// Valid property tags: TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"), +// ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"), +// RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y") +type Transform interface { + Properties + fmt.Stringer + stringWriter + transformCSS(session Session, transform3D bool) string +} + +type transformData struct { + propertyList +} + +func NewTransform(params Params) Transform { + transform := new(transformData) + transform.properties = map[string]any{} + for tag, value := range params { + transform.Set(tag, value) + } + return transform +} + +func (style *viewStyle) setTransform(value any) bool { + + setObject := func(obj DataObject) bool { + transform := NewTransform(nil) + ok := true + for i := 0; i < obj.PropertyCount(); i++ { + if prop := obj.Property(i); prop.Type() == TextNode { + if !transform.Set(prop.Tag(), prop.Text()) { + ok = false + } + } else { + ok = false + } + } + + if !ok && len(transform.AllTags()) == 0 { + return false + } + + style.properties[TransformTag] = transform + return true + } + + switch value := value.(type) { + case Transform: + style.properties[TransformTag] = value + + case DataObject: + return setObject(value) + + case DataNode: + if obj := value.Object(); obj != nil { + return setObject(obj) + } + notCompatibleType(TransformTag, value) + return false + + case string: + if obj := ParseDataText(value); obj != nil { + return setObject(obj) + } + notCompatibleType(TransformTag, value) + return false + } + + return false +} + +func (style *viewStyle) transformProperty() Transform { + if val, ok := style.properties[TransformTag]; ok { + if transform, ok := val.(Transform); ok { + return transform + } + } + return nil +} + +func (style *viewStyle) setTransformProperty(tag string, value any) bool { + switch tag { + case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, TranslateX, TranslateY, TranslateZ: + if transform := style.transformProperty(); transform != nil { + return transform.Set(tag, value) + } + + transform := NewTransform(nil) + if !transform.Set(tag, value) { + return false + } + + style.properties[TransformTag] = transform + return true + } + + ErrorLogF(`"Transform" interface does not support the "%s" property`, tag) + return false +} + +func (transform *transformData) String() string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + transform.writeString(buffer, "") + return buffer.String() +} + +func (transform *transformData) writeString(buffer *strings.Builder, indent string) { + buffer.WriteString("_{ ") + comma := false + for _, tag := range []string{SkewX, SkewY, TranslateX, TranslateY, TranslateZ, + ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ} { + if value, ok := transform.properties[tag]; ok { + if comma { + buffer.WriteString(", ") + } + buffer.WriteString(tag) + buffer.WriteString(" = ") + writePropertyValue(buffer, tag, value, indent) + comma = true + } + } + buffer.WriteString(" }") +} + +func (transform *transformData) Set(tag string, value any) bool { + return transform.set(strings.ToLower(tag), value) +} + +func (transform *transformData) set(tag string, value any) bool { + if value == nil { + _, exist := transform.properties[tag] + if exist { + delete(transform.properties, tag) + } + return exist + } + + switch tag { + + case RotateX, RotateY, RotateZ: + return transform.setFloatProperty(tag, value, 0, 1) + + case Rotate, SkewX, SkewY: + return transform.setAngleProperty(tag, value) + + case ScaleX, ScaleY, ScaleZ: + return transform.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64) + + case TranslateX, TranslateY, TranslateZ: + return transform.setSizeProperty(tag, value) + } + + return false +} + func getTransform3D(style Properties, session Session) bool { perspective, ok := sizeProperty(style, Perspective, session) return ok && perspective.Type != Auto && perspective.Value != 0 @@ -87,25 +275,25 @@ func getOrigin(style Properties, session Session) (SizeUnit, SizeUnit, SizeUnit) return x, y, z } -func getSkew(style Properties, session Session) (AngleUnit, AngleUnit, bool) { - skewX, okX := angleProperty(style, SkewX, session) - skewY, okY := angleProperty(style, SkewY, session) +func (transform *transformData) getSkew(session Session) (AngleUnit, AngleUnit, bool) { + skewX, okX := angleProperty(transform, SkewX, session) + skewY, okY := angleProperty(transform, SkewY, session) return skewX, skewY, okX || okY } -func getTranslate(style Properties, session Session) (SizeUnit, SizeUnit, SizeUnit) { - x, _ := sizeProperty(style, TranslateX, session) - y, _ := sizeProperty(style, TranslateY, session) - z, _ := sizeProperty(style, TranslateZ, session) +func (transform *transformData) getTranslate(session Session) (SizeUnit, SizeUnit, SizeUnit) { + x, _ := sizeProperty(transform, TranslateX, session) + y, _ := sizeProperty(transform, TranslateY, session) + z, _ := sizeProperty(transform, TranslateZ, session) return x, y, z } -func (style *viewStyle) transform(session Session) string { +func (transform *transformData) transformCSS(session Session, transform3D bool) string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - skewX, skewY, skewOK := getSkew(style, session) + skewX, skewY, skewOK := transform.getSkew(session) if skewOK { buffer.WriteString(`skew(`) buffer.WriteString(skewX.cssString()) @@ -114,12 +302,12 @@ func (style *viewStyle) transform(session Session) string { buffer.WriteRune(')') } - x, y, z := getTranslate(style, session) + x, y, z := transform.getTranslate(session) - scaleX, okScaleX := floatTextProperty(style, ScaleX, session, 1) - scaleY, okScaleY := floatTextProperty(style, ScaleY, session, 1) + scaleX, okScaleX := floatTextProperty(transform, ScaleX, session, 1) + scaleY, okScaleY := floatTextProperty(transform, ScaleY, session, 1) - if getTransform3D(style, session) { + if transform3D { if x.Type != Auto || y.Type != Auto || z.Type != Auto { if buffer.Len() > 0 { buffer.WriteRune(' ') @@ -133,7 +321,7 @@ func (style *viewStyle) transform(session Session) string { buffer.WriteRune(')') } - scaleZ, okScaleZ := floatTextProperty(style, ScaleZ, session, 1) + scaleZ, okScaleZ := floatTextProperty(transform, ScaleZ, session, 1) if okScaleX || okScaleY || okScaleZ { if buffer.Len() > 0 { buffer.WriteRune(' ') @@ -147,10 +335,10 @@ func (style *viewStyle) transform(session Session) string { buffer.WriteRune(')') } - if angle, ok := angleProperty(style, Rotate, session); ok { - rotateX, _ := floatTextProperty(style, RotateX, session, 1) - rotateY, _ := floatTextProperty(style, RotateY, session, 1) - rotateZ, _ := floatTextProperty(style, RotateZ, session, 1) + if angle, ok := angleProperty(transform, Rotate, session); ok { + rotateX, _ := floatTextProperty(transform, RotateX, session, 1) + rotateY, _ := floatTextProperty(transform, RotateY, session, 1) + rotateZ, _ := floatTextProperty(transform, RotateZ, session, 1) if buffer.Len() > 0 { buffer.WriteRune(' ') @@ -167,6 +355,7 @@ func (style *viewStyle) transform(session Session) string { } } else { + if x.Type != Auto || y.Type != Auto { if buffer.Len() > 0 { buffer.WriteRune(' ') @@ -189,7 +378,7 @@ func (style *viewStyle) transform(session Session) string { buffer.WriteRune(')') } - if angle, ok := angleProperty(style, Rotate, session); ok { + if angle, ok := angleProperty(transform, Rotate, session); ok { if buffer.Len() > 0 { buffer.WriteRune(' ') } @@ -203,7 +392,8 @@ func (style *viewStyle) transform(session Session) string { } func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) { - if getTransform3D(style, session) { + transform3D := getTransform3D(style, session) + if transform3D { if perspective, ok := sizeProperty(style, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 { builder.add(`perspective`, perspective.cssString("0", session)) } @@ -232,7 +422,9 @@ func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Sessio } } - builder.add(`transform`, style.transform(session)) + if transform := style.transformProperty(); transform != nil { + builder.add(`transform`, transform.transformCSS(session, transform3D)) + } } func (view *viewData) updateTransformProperty(tag string) bool { @@ -276,8 +468,14 @@ func (view *viewData) updateTransformProperty(tag string) bool { } session.updateCSSProperty(htmlID, "transform-origin", value) - case SkewX, SkewY, TranslateX, TranslateY, TranslateZ, ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ: - session.updateCSSProperty(htmlID, "transform", view.transform(session)) + case TransformTag, SkewX, SkewY, TranslateX, TranslateY, TranslateZ, + ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ: + if transform := view.transformProperty(); transform != nil { + transform3D := getTransform3D(view, session) + session.updateCSSProperty(htmlID, "transform", transform.transformCSS(session, transform3D)) + } else { + session.updateCSSProperty(htmlID, "transform", "") + } default: return false diff --git a/viewUtils.go b/viewUtils.go index fc152f8..17cc55e 100644 --- a/viewUtils.go +++ b/viewUtils.go @@ -629,7 +629,12 @@ func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) if view == nil { return AutoSize(), AutoSize(), AutoSize() } - return getTranslate(view, view.Session()) + + session := view.Session() + x, _ := sizeProperty(view, TranslateX, session) + y, _ := sizeProperty(view, TranslateY, session) + z, _ := sizeProperty(view, TranslateZ, session) + return x, y, z } // GetSkew returns a angles to use to distort the element along the abscissa (x-axis) @@ -642,7 +647,8 @@ func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) { if view == nil { return AngleUnit{Value: 0, Type: Radian}, AngleUnit{Value: 0, Type: Radian} } - x, y, _ := getSkew(view, view.Session()) + x, _ := angleProperty(view, SkewX, view.Session()) + y, _ := angleProperty(view, SkewY, view.Session()) return x, y }