diff --git a/docs/tutorial/Tutorial-Part-I.md b/docs/tutorial/Tutorial-Part-I.md index a0b0e53..8a929b6 100644 --- a/docs/tutorial/Tutorial-Part-I.md +++ b/docs/tutorial/Tutorial-Part-I.md @@ -434,6 +434,357 @@ renders as: ```css repeating-radial-gradient(yellow, green); ``` +#### Transforms +Css transforms are a collection of functions that allow you to shape elements in particular ways. +##### Rotation: `rotate()` `rotateX()` `rotateY()` `rotateZ()` `rotate3d()` + +The library provides support for rotation functions, used in animations to move an element around a central point. The rotate expressions are built instantiating `CssRotate` or `CssRotate3D` for 3D rotations. + +Lets see a basic working rotation example: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + animation: 'spin 5s linear infinite'; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style + transform: (CssRotate by: 0 deg); + background: #blue ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | + style + transform: (CssRotate by: 360 deg); + background: #red ] ] + forKeyframesNamed: 'spin'; + build +``` +Evaluates to: +```css +div +{ + animation: spin 5s linear infinite; + width: 100px; + height: 100px; +} + +@keyframes spin +{ + 0% + { + transform: rotate(0deg); + background: blue; + } + + 100% + { + transform: rotate(360deg); + background: red; + } +} +``` + +##### Translation: `translate()` `translateX()` `translateY()` `translateZ()` `translate3d()` + +The library supports `translate` functions, used to mode the position of an element. To translate an element, instantiate `CssTranslate`. + +Lets see an example: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animation: 'animation 5s linear infinite'; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style + transform: (CssTranslate by: 100 px) ] ] + forKeyframesNamed: 'animation'; + build. +``` +Evaluates to: +```css +div +{ + background: blue; + animation: animation 5s linear infinite; + width: 100px; + height: 100px; +} + +@keyframes animation +{ + 0% + { + transform: translate(100px); + } +} +``` + +##### Scale: `scale()` `scaleX()` `scaleY()` `scaleZ()` `scale3d()` + +RenoirSt supports scaling functions. You can use them by building an instance of `CssScale`. + +An example can be: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animation: 'scale 5s linear infinite'; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style transform: (CssScale by: 2) ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | + style transform: (CssScale by: 4) ] ] + forKeyframesNamed: 'scale'; + build. +``` +Evaluating to: +```css +div +{ + background: blue; + animation: scale 5s linear infinite; + width: 100px; + height: 100px; +} + +@keyframes scale +{ + 0% + { + transform: scale(2); + } + + 100% + { + transform: scale(4); + } +} +``` + +##### Skew `skew()` `skewX()` `skewY()` + +The library supports the `skew` function, performing a shear transformation (also known as a shear mapping or a transvection), which displaces each point of an element by a given angle in each direction. To build skew functions, instantiate `CssSkew`. + +An example: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animation: 'skewAnimation 5s linear infinite'; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | style transform: (CssSkew by: 45 deg) ] ] + forKeyframesNamed: 'skewAnimation'; + build +``` +Evaluates to: +```css +div +{ + background: blue; + animation: skewAnimation 5s linear infinite; + width: 100px; + height: 100px; +} + +@keyframes skewAnimation +{ + 0% + { + transform: skew(45deg); + } +} +``` + +##### Aditional Functions for Transformation +###### Perspective + +The library supports the `perspective` function by instantiating `CssPerspective`. +This function is used to set a perspective when doing a transformation on the Z axis. + +If we extend the previous example to translate in a 3D space, it would be like: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animation: 'animation 5s linear infinite'; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style + transform: + {(CssPerspective of: 200 px) . + (CssTranslate + onXAxisBy: 100 px + onYAxisBy: 100 px + andZAxisBy: 100 px )} ] ] + forKeyframesNamed: 'animation'; + build. +``` +Evaluating to: +```css +div +{ + background: blue; + animation: animation 5s linear infinite; + width: 100px; + height: 100px; +} + +@keyframes animation +{ + 0% + { + transform: perspective(200px) translate3d(100px, 100px, 100px); + } +} +``` + +#### Easing Functions + +##### Steps + +The library also supports the `steps` function, used in the timing parameter of animation keyframes called `animation-timing-function`. Steps are a timing function that allows us to break an animation or transition into segments. + +A usage example can be: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animationName: 'scale'; + animationDuration: 5 s; + animationTimingFunction: (CssSteps by: 2); + animationIterationCount: #infinite; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style transform: (CssScale by: 2) ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | + style transform: (CssScale by: 4) ] ] + forKeyframesNamed: 'scale'; + build. +``` +Evaluating to: +```css +div +{ + background: blue; + animation-name: scale; + animation-duration: 5s; + animation-timing-function: steps(2); + animation-iteration-count: infinite; + width: 100px; + height: 100px; +} + +@keyframes scale +{ + 0% + { + transform: scale(2); + } + + 100% + { + transform: scale(4); + } +} +``` + +##### Cubic Bezier + +Renoir supports the `cubic-bezier` function, that can be used with the `transition-timing-function` property to control how a transition will change speed over its duration. It also works with the `animation-timing-function` for keyframes. To create your own cubic bezier timing function build an instance with `CssCubicBezier`. + +Here's an example: +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + background: #blue; + animationName: 'scale'; + animationDuration: 5 s; + animationTimingFunction: ( + CssCubicBezier + firstXAxis: 0.63 + firstYAxis: 0.05 + secondXAxis: 0.43 + secondYAxis: 1.7); + animationIterationCount: #infinite; + width: 100 px; + height: 100 px ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style transform: (CssScale by: 2) ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | + style transform: (CssScale by: 4) ] ] + forKeyframesNamed: 'scale'; + build. +``` +Evaluating to: +```css +div +{ + background: blue; + animation-name: scale; + animation-duration: 5s; + animation-timing-function: cubic-bezier(0.63, 0.05, 0.43, 1.7); + animation-iteration-count: infinite; + width: 100px; + height: 100px; +} + +@keyframes scale +{ + 0% + { + transform: scale(2); + } + + 100% + { + transform: scale(4); + } +} +``` #### Box Shadows diff --git a/docs/tutorial/Tutorial-Part-III.md b/docs/tutorial/Tutorial-Part-III.md index 61d5d9d..14eec36 100644 --- a/docs/tutorial/Tutorial-Part-III.md +++ b/docs/tutorial/Tutorial-Part-III.md @@ -36,6 +36,142 @@ Note that the important properties must be created by sending the messages to th ##### References: - http://www.w3.org/TR/CSS2/cascade.html#important-rules +## Keyframes + +The `@keyframes` rule specifies the animation code. +The animation is created by gradually changing from one set of CSS styles to another. +During the animation, you can change the set of CSS styles many times. +Specify when the style change will happen in percent, or with the keywords "from" and "to", which is the same as 0% and 100%. 0% is the beginning of the animation, 100% is when the animation is complete. + +**Tip:** For best browser support, you should always define both the 0% and the 100% selectors. + +A basic keyframe rule consists of specifying just a keyframe with some style rule: +```smalltalk +CascadingStyleSheetBuilder new + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | style backgroundColor: #red ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | style backgroundColor: #blue ] ] + forKeyframesNamed: 'example'; + build +``` +To use keyframes in the library just send the message `declare:forKeyframesNamed:` to the builder. The first closure is evaluated with an instance of a `CascadingStyleSheetBuilder`. The second parameter is to give a name to your keyframe rule. + +The style can be built with either the `animation:` shorthand (`animation: name duration timing-function delay iteration-count direction fill-mode play-state`) or with separate animation styles, such as: +- `animationName:` +- `animationDuration:` +- `animationTimingFunction:` +- `animationIterationCount:` +- `animationDirection:` +- `animationPlayState:` +- `animationDelay:` +- `animationFillMode:` + +For example, a more complex animation can be written: + +```smalltalk +CascadingStyleSheetBuilder new + declareRuleSetFor: [ :selector | selector div ] + with: [ :style | + style + animation: 'spin 5s linear infinite'; + "replacing... + animationName: 'spin'; + animationDuration: 5000 ms; + animationTimingFunction: 'linear'; + animationIterationCount: 'infinite';" + + maxHeight: 80 vh; + fontSize: #larger; + background: '#f00'; + width: 100 px; + height: 100 px; + position: 'relative' ]; + + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | + style + transform: (CssRotate by: 0 deg); + background: '#f00' ]; + declareKeyframeRuleSetAt: 25 percent + with: [ :style | + style + transform: (CssRotate by: 90 deg); + background: '#f99' ]; + declareKeyframeRuleSetAt: 50 percent + with: [ :style | + style + transform: (CssRotate by: 180 deg); + background: '#b88' ]; + declareKeyframeRuleSetAt: 75 percent + with: [ :style | + style + transform: (CssRotate by: 270 deg); + background: '#a66' ]; + declareKeyframeRuleSetAt: 100 percent + with: [ :style | + style + transform: (CssRotate by: 360 deg); + background: '#f00' ] ] + forKeyframesNamed: 'spin' +``` +Evaluating to: + +```css +div +{ + animation: spin 5s linear infinite; + max-height: 80vh; + font-size: larger; + background: #f00; + width: 100px; + height: 100px; + position: relative; +} + +@keyframes spin +{ + 0% + { + transform: rotate(0deg); + background: #f00; + } + + 25% + { + transform: rotate(90deg); + background: #f99; + } + + 50% + { + transform: rotate(180deg); + background: #b88; + } + + 75% + { + transform: rotate(270deg); + background: #a66; + } + + 100% + { + transform: rotate(360deg); + background: #f00; + } +} +``` + +**Note:** The `!important` rule is ignored in a keyframe + +##### References: +- https://drafts.csswg.org/css-animations/ + ## Media Queries A `@media` rule specifies the target media types of a set of statements. The `@media` construct allows style sheet rules that apply to various media in the same style sheet. Style rules outside of `@media` rules apply to all media types that the style sheet applies to. At-rules inside `@media` are invalid in CSS2.1. diff --git a/docs/tutorial/Tutorial-TOC.md b/docs/tutorial/Tutorial-TOC.md index cbdc576..ea65517 100644 --- a/docs/tutorial/Tutorial-TOC.md +++ b/docs/tutorial/Tutorial-TOC.md @@ -16,6 +16,7 @@ Tutorial - Selector Groups - [Part III](Tutorial-Part-III.md ) - Important Rules + - Keyframes - Media Queries - Vendor specific extensions - Font Face rules diff --git a/source/RenoirSt-Tests/CascadingStyleSheetBuilderTest.class.st b/source/RenoirSt-Tests/CascadingStyleSheetBuilderTest.class.st index 84aeebc..b3317bb 100644 --- a/source/RenoirSt-Tests/CascadingStyleSheetBuilderTest.class.st +++ b/source/RenoirSt-Tests/CascadingStyleSheetBuilderTest.class.st @@ -58,6 +58,24 @@ CascadingStyleSheetBuilderTest >> testBuildingSimpleStyleSheetWithSomeComments [ self assert: builder build printString equals: '/*Example CSS*/#oop{color: red;}' expandMacros ] +{ #category : #Tests } +CascadingStyleSheetBuilderTest >> testBuildingStyleSheetWithKeyframes [ + + | builder | + + builder := CascadingStyleSheetBuilder new. + + builder + declareRuleSetFor: [ :selector | selector class: 'xxx' ] with: [ :style | style margin: 12 pt ]; + declare: [ :cssBuilder | + cssBuilder + declareKeyframeRuleSetAt: 0 percent + with: [ :style | style color: #blue ] ] + forKeyframesNamed: 'test'. + + self assert: builder build printString equals: '.xxx{margin: 12pt;}@keyframes test{0%%{color: blue;}}' expandMacros +] + { #category : #Tests } CascadingStyleSheetBuilderTest >> testBuildingStyleSheetWithMediaQuery [ diff --git a/source/RenoirSt-Tests/CssCubicBezierTest.class.st b/source/RenoirSt-Tests/CssCubicBezierTest.class.st new file mode 100644 index 0000000..4de237c --- /dev/null +++ b/source/RenoirSt-Tests/CssCubicBezierTest.class.st @@ -0,0 +1,18 @@ +Class { + #name : #CssCubicBezierTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Easing' +} + +{ #category : #tests } +CssCubicBezierTest >> testCubicBezier [ + + | cubicBezierExpression | + cubicBezierExpression := + CssCubicBezier + firstXAxis: 0.63 + firstYAxis: 0.05 + secondXAxis: 0.43 + secondYAxis: 1.7. + self assert: cubicBezierExpression printString equals: 'cubic-bezier(0.63, 0.05, 0.43, 1.7)' +] diff --git a/source/RenoirSt-Tests/CssDeclarationBlockTest.class.st b/source/RenoirSt-Tests/CssDeclarationBlockTest.class.st index 1ea8d6b..46bc883 100644 --- a/source/RenoirSt-Tests/CssDeclarationBlockTest.class.st +++ b/source/RenoirSt-Tests/CssDeclarationBlockTest.class.st @@ -163,6 +163,23 @@ CssDeclarationBlockTest >> testPrintStringOfGeneratedContentProperties [ assert: [ :style | style quotes: {'"<"' . '">"'} ] rendersProperty: 'quotes' withValue: '"<" ">"' ] +{ #category : #Tests } +CssDeclarationBlockTest >> testPrintStringOfKeyframesProperties [ + + self + assert: [ :style | style animationName: 'testAnimation' ] rendersProperty: 'animation-name' withValue: 'testAnimation'; + assert: [ :style | style animationDuration: '5s' ] rendersProperty: 'animation-duration' withValue: '5s'; + assert: [ :style | style animationTimingFunction: 'linear' ] rendersProperty: 'animation-timing-function' withValue: 'linear'; + assert: [ :style | style animationDelay: '5s' ] rendersProperty: 'animation-delay' withValue: '5s'; + assert: [ :style | style animationIterationCount: 2 ] rendersProperty: 'animation-iteration-count' withValue: '2'; + assert: [ :style | style animationDirection: 'normal' ] rendersProperty: 'animation-direction' withValue: 'normal'; + assert: [ :style | style animationFillMode: 'forwards' ] rendersProperty: 'animation-fill-mode' withValue: 'forwards'; + assert: [ :style | style animationPlayState: 'running' ] rendersProperty: 'animation-play-state' withValue: 'running'; + + assert: [ :style | style animation: 'test 5s ease 0s infinite normal backwards running' ] rendersProperty: 'animation' withValue: + 'test 5s ease 0s infinite normal backwards running' +] + { #category : #Tests } CssDeclarationBlockTest >> testPrintStringOfMarginProperties [ @@ -297,7 +314,8 @@ CssDeclarationBlockTest >> testPrintStringOfVisualFormattingProperties [ assert: [ :style | style right: 4 cm ] rendersProperty: 'right' withValue: '4cm'; assert: [ :style | style top: 4 cm ] rendersProperty: 'top' withValue: '4cm'; assert: [ :style | style unicodeBidi: #normal ] rendersProperty: 'unicode-bidi' withValue: 'normal'; - assert: [ :style | style zIndex: 4 ] rendersProperty: 'z-index' withValue: '4' + assert: [ :style | style zIndex: 4 ] rendersProperty: 'z-index' withValue: '4'; + assert: [ :style | style transform: #'rotate(0deg)' ] rendersProperty: 'transform' withValue: 'rotate(0deg)' ] { #category : #Tests } diff --git a/source/RenoirSt-Tests/CssKeyframesRuleTest.class.st b/source/RenoirSt-Tests/CssKeyframesRuleTest.class.st new file mode 100644 index 0000000..6d48169 --- /dev/null +++ b/source/RenoirSt-Tests/CssKeyframesRuleTest.class.st @@ -0,0 +1,15 @@ +Class { + #name : #CssKeyframesRuleTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Keyframes' +} + +{ #category : #tests } +CssKeyframesRuleTest >> testPrintStringOfSimpleKeyframes [ + + | styleSheet keyframes | + + styleSheet := CascadingStyleSheet withAll: #(). + keyframes := CssKeyframesRule named: #test enabling: styleSheet. + self assert: keyframes printString equals: '@keyframes test{}' expandMacros +] diff --git a/source/RenoirSt-Tests/CssPerspectiveTest.class.st b/source/RenoirSt-Tests/CssPerspectiveTest.class.st new file mode 100644 index 0000000..fab9dc7 --- /dev/null +++ b/source/RenoirSt-Tests/CssPerspectiveTest.class.st @@ -0,0 +1,14 @@ +Class { + #name : #CssPerspectiveTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssPerspectiveTest >> testPerspective [ + + | perspectiveExpression | + perspectiveExpression := CssPerspective of: 100 px. + + self assert: perspectiveExpression printString equals: 'perspective(100px)' +] diff --git a/source/RenoirSt-Tests/CssRotate3DTest.class.st b/source/RenoirSt-Tests/CssRotate3DTest.class.st new file mode 100644 index 0000000..c6180f2 --- /dev/null +++ b/source/RenoirSt-Tests/CssRotate3DTest.class.st @@ -0,0 +1,14 @@ +Class { + #name : #CssRotate3DTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssRotate3DTest >> testRotateOnAllAxis [ + + | rotateExpression | + rotateExpression := CssRotate3D onXAxis: 1 yAxis: 0 zAxis: 0 by: 60 deg. + + self assert: rotateExpression printString equals: 'rotate3d(1, 0, 0, 60deg)' +] diff --git a/source/RenoirSt-Tests/CssRotateTest.class.st b/source/RenoirSt-Tests/CssRotateTest.class.st new file mode 100644 index 0000000..cb0f31b --- /dev/null +++ b/source/RenoirSt-Tests/CssRotateTest.class.st @@ -0,0 +1,41 @@ +Class { + #name : #CssRotateTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssRotateTest >> testRotateOnXAxis [ + + | rotateExpression | + rotateExpression := CssRotate onXAxisBy: 70 deg. + + self assert: rotateExpression printString equals: 'rotateX(70deg)' +] + +{ #category : #tests } +CssRotateTest >> testRotateOnYAxis [ + + | rotateExpression | + rotateExpression := CssRotate onYAxisBy: 60 deg. + + self assert: rotateExpression printString equals: 'rotateY(60deg)' +] + +{ #category : #tests } +CssRotateTest >> testRotateOnZAxis [ + + | rotateExpression | + rotateExpression := CssRotate onZAxisBy: 60 deg. + + self assert: rotateExpression printString equals: 'rotateZ(60deg)' +] + +{ #category : #tests } +CssRotateTest >> testSimpleRotate [ + + | rotateExpression | + rotateExpression := CssRotate by: 270 deg. + + self assert: rotateExpression printString equals: 'rotate(270deg)' +] diff --git a/source/RenoirSt-Tests/CssScaleTest.class.st b/source/RenoirSt-Tests/CssScaleTest.class.st new file mode 100644 index 0000000..3a85926 --- /dev/null +++ b/source/RenoirSt-Tests/CssScaleTest.class.st @@ -0,0 +1,59 @@ +Class { + #name : #CssScaleTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssScaleTest >> testScaleOnAllAxes [ + + | scaleExpression | + scaleExpression := CssScale onXAxisBy: 2 onYAxisBy: 1 andZAxisBy: 2. + + self assert: scaleExpression printString equals: 'scale3d(2, 1, 2)' +] + +{ #category : #tests } +CssScaleTest >> testScaleOnXAndYAxes [ + + | scaleExpression | + scaleExpression := CssScale onXAxisBy: 2 andYAxisBy: 1. + + self assert: scaleExpression printString equals: 'scale(2, 1)' +] + +{ #category : #tests } +CssScaleTest >> testScaleOnXAxis [ + + | scaleExpression | + scaleExpression := CssScale onlyOnXAxisBy: 2. + + self assert: scaleExpression printString equals: 'scaleX(2)' +] + +{ #category : #tests } +CssScaleTest >> testScaleOnYAxis [ + + | scaleExpression | + scaleExpression := CssScale onlyOnYAxisBy: 2. + + self assert: scaleExpression printString equals: 'scaleY(2)' +] + +{ #category : #tests } +CssScaleTest >> testScaleOnZAxis [ + + | scaleExpression | + scaleExpression := CssScale onlyOnZAxisBy: 2. + + self assert: scaleExpression printString equals: 'scaleZ(2)' +] + +{ #category : #tests } +CssScaleTest >> testSimpleScale [ + + | scaleExpression | + scaleExpression := CssScale by: 2. + + self assert: scaleExpression printString equals: 'scale(2)' +] diff --git a/source/RenoirSt-Tests/CssSkewTest.class.st b/source/RenoirSt-Tests/CssSkewTest.class.st new file mode 100644 index 0000000..a2724d7 --- /dev/null +++ b/source/RenoirSt-Tests/CssSkewTest.class.st @@ -0,0 +1,41 @@ +Class { + #name : #CssSkewTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssSkewTest >> testSimpleSkew [ + + | skewExpression | + skewExpression := CssSkew by: 45 deg. + + self assert: skewExpression printString equals: 'skew(45deg)' +] + +{ #category : #tests } +CssSkewTest >> testSkewOnX [ + + | skewExpression | + skewExpression := CssSkew onlyOnXAxisBy: 45 deg. + + self assert: skewExpression printString equals: 'skewX(45deg)' +] + +{ #category : #tests } +CssSkewTest >> testSkewOnXandYAxes [ + + | skewExpression | + skewExpression := CssSkew onXAxisBy: 45 deg andYAxisBy: 50 deg. + + self assert: skewExpression printString equals: 'skew(45deg, 50deg)' +] + +{ #category : #tests } +CssSkewTest >> testSkewOnY [ + + | skewExpression | + skewExpression := CssSkew onlyOnYAxisBy: 45 deg. + + self assert: skewExpression printString equals: 'skewY(45deg)' +] diff --git a/source/RenoirSt-Tests/CssStepsTest.class.st b/source/RenoirSt-Tests/CssStepsTest.class.st new file mode 100644 index 0000000..824b970 --- /dev/null +++ b/source/RenoirSt-Tests/CssStepsTest.class.st @@ -0,0 +1,23 @@ +Class { + #name : #CssStepsTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Easing' +} + +{ #category : #tests } +CssStepsTest >> testSimpleSteps [ + + | translateExpression | + translateExpression := CssSteps by: 1. + + self assert: translateExpression printString equals: 'steps(1)' +] + +{ #category : #tests } +CssStepsTest >> testStepsWithDirection [ + + | translateExpression | + translateExpression := CssSteps by: 1 direction: #start. + + self assert: translateExpression printString equals: 'steps(1, start)' +] diff --git a/source/RenoirSt-Tests/CssTranslateTest.class.st b/source/RenoirSt-Tests/CssTranslateTest.class.st new file mode 100644 index 0000000..c98e250 --- /dev/null +++ b/source/RenoirSt-Tests/CssTranslateTest.class.st @@ -0,0 +1,59 @@ +Class { + #name : #CssTranslateTest, + #superclass : #TestCase, + #category : #'RenoirSt-Tests-Transformation' +} + +{ #category : #tests } +CssTranslateTest >> testSimpleTranslate [ + + | translateExpression | + translateExpression := CssTranslate by: 100 px. + + self assert: translateExpression printString equals: 'translate(100px)' +] + +{ #category : #tests } +CssTranslateTest >> testTranslateForAllAxes [ + + | translateExpression | + translateExpression := CssTranslate onXAxisBy: 100 px onYAxisBy: 100 px andZAxisBy: 100 px. + + self assert: translateExpression printString equals: 'translate3d(100px, 100px, 100px)' +] + +{ #category : #tests } +CssTranslateTest >> testTranslateForXandYAxes [ + + | translateExpression | + translateExpression := CssTranslate onXAxisBy: 100 px andYAxisBy: 100 px. + + self assert: translateExpression printString equals: 'translate(100px, 100px)' +] + +{ #category : #tests } +CssTranslateTest >> testTranslateOnXAxis [ + + | translateExpression | + translateExpression := CssTranslate onlyOnXAxisBy: 100 px. + + self assert: translateExpression printString equals: 'translateX(100px)' +] + +{ #category : #tests } +CssTranslateTest >> testTranslateOnYAxis [ + + | translateExpression | + translateExpression := CssTranslate onlyOnYAxisBy: 100 px. + + self assert: translateExpression printString equals: 'translateY(100px)' +] + +{ #category : #tests } +CssTranslateTest >> testTranslateOnZAxis [ + + | translateExpression | + translateExpression := CssTranslate onlyOnZAxisBy: 100 px. + + self assert: translateExpression printString equals: 'translateZ(100px)' +] diff --git a/source/RenoirSt/CascadingStyleSheetBuilder.class.st b/source/RenoirSt/CascadingStyleSheetBuilder.class.st index 1d7fb66..70e54c7 100644 --- a/source/RenoirSt/CascadingStyleSheetBuilder.class.st +++ b/source/RenoirSt/CascadingStyleSheetBuilder.class.st @@ -28,6 +28,16 @@ CascadingStyleSheetBuilder >> comment: aCommentText [ self addStatement: (CssComment for: aCommentText) ] +{ #category : #Configuring } +CascadingStyleSheetBuilder >> declare: aSubStyleSheetBlock forKeyframesNamed: aName [ + + | styleSheetBuilder | + + styleSheetBuilder := self class new. + aSubStyleSheetBlock value: styleSheetBuilder. + self addStatement: (CssKeyframesRule named: aName enabling: styleSheetBuilder build) +] + { #category : #Configuring } CascadingStyleSheetBuilder >> declare: aSubStyleSheetBlock forMediaMatching: aMediaQueryBlock [ @@ -53,6 +63,16 @@ CascadingStyleSheetBuilder >> declareFontFaceRuleWith: aDeclarationAction [ self addStatement: (CssRuleSet selector: '@font-face' declarations: declarationBlock) ] +{ #category : #Configuring } +CascadingStyleSheetBuilder >> declareKeyframeRuleSetAt: aPercentage with: aStyleSheetBlock [ + + | declarationBlock | + + declarationBlock := CssDeclarationBlock new. + aStyleSheetBlock cull: declarationBlock. + self addStatement: (CssRuleSet selector: aPercentage declarations: declarationBlock comment: '') +] + { #category : #Configuring } CascadingStyleSheetBuilder >> declareRuleSetFor: aSelectorBlock with: aDeclarationAction [ diff --git a/source/RenoirSt/CssCubicBezier.class.st b/source/RenoirSt/CssCubicBezier.class.st new file mode 100644 index 0000000..9c48b8c --- /dev/null +++ b/source/RenoirSt/CssCubicBezier.class.st @@ -0,0 +1,35 @@ +Class { + #name : #CssCubicBezier, + #superclass : #CssFunction, + #instVars : [ + 'values' + ], + #category : #'RenoirSt-Easing' +} + +{ #category : #'Instance Creation' } +CssCubicBezier class >> firstXAxis: firstXAxisNumber firstYAxis: firstYAxisNumber secondXAxis: secondXAxisNumber secondYAxis: secondYAxisNumber [ + ^ self new initializeFirstXAxis: firstXAxisNumber firstYAxis: firstYAxisNumber secondXAxis: secondXAxisNumber secondYAxis: secondYAxisNumber +] + +{ #category : #private } +CssCubicBezier >> cssFunctionParametersContentOn: aWriteStream [ + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssCubicBezier >> functionName [ + ^ 'cubic-bezier' +] + +{ #category : #Initialization } +CssCubicBezier >> initializeFirstXAxis: firstXAxisNumber firstYAxis: firstYAxisNumber secondXAxis: secondXAxisNumber secondYAxis: secondYAxisNumber [ + + values := OrderedCollection with: firstXAxisNumber with: firstYAxisNumber + with: secondXAxisNumber with: secondYAxisNumber +] diff --git a/source/RenoirSt/CssDeclarationBlock.class.st b/source/RenoirSt/CssDeclarationBlock.class.st index 7965e87..a8c9104 100644 --- a/source/RenoirSt/CssDeclarationBlock.class.st +++ b/source/RenoirSt/CssDeclarationBlock.class.st @@ -11,6 +11,66 @@ Class { #category : #'RenoirSt-Common' } +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animation: aCssValue [ + + "Values must follow the guidelines specified in https://drafts.csswg.org/css-animations/#animation + being like: + animation: name duration timing-function delay iteration-count direction fill-mode play-state + + Always specify the animation-duration property, otherwise the duration is 0, and will never be played." + + self propertyAt: 'animation' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationDelay: aCssValue [ + + self propertyAt: 'animation-delay' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationDirection: aCssValue [ + + self propertyAt: 'animation-direction' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationDuration: aCssValue [ + + self propertyAt: 'animation-duration' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationFillMode: aCssValue [ + + self propertyAt: 'animation-fill-mode' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationIterationCount: aCssValue [ + + self propertyAt: 'animation-iteration-count' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationName: aCssValue [ + + self propertyAt: 'animation-name' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationPlayState: aCssValue [ + + self propertyAt: 'animation-play-state' put: aCssValue +] + +{ #category : #'keyframes properties' } +CssDeclarationBlock >> animationTimingFunction: aCssValue [ + + self propertyAt: 'animation-timing-function' put: aCssValue +] + { #category : #'background properties' } CssDeclarationBlock >> background: aCssValue [ @@ -787,6 +847,12 @@ CssDeclarationBlock >> top: aCssValue [ self propertyAt: 'top' put: aCssValue ] +{ #category : #'visual formatting properties' } +CssDeclarationBlock >> transform: aCssValue [ + + self propertyAt: 'transform' put: aCssValue +] + { #category : #'visual formatting properties' } CssDeclarationBlock >> unicodeBidi: aCssValue [ diff --git a/source/RenoirSt/CssKeyframesRule.class.st b/source/RenoirSt/CssKeyframesRule.class.st new file mode 100644 index 0000000..a993046 --- /dev/null +++ b/source/RenoirSt/CssKeyframesRule.class.st @@ -0,0 +1,45 @@ +Class { + #name : #CssKeyframesRule, + #superclass : #CssObject, + #instVars : [ + 'keyframeName', + 'styleSheet' + ], + #category : #'RenoirSt-Keyframes' +} + +{ #category : #'Instance Creation' } +CssKeyframesRule class >> named: aName enabling: aCascadingStyleSheet [ + + ^ self new initializeNamed: aName enabling: aCascadingStyleSheet +] + +{ #category : #Printing } +CssKeyframesRule >> cssContentOn: aStream [ + aStream + nextPutAll: '@keyframes'; + space. + keyframeName cssContentOn: aStream. + aStream + newLine; + nextPut: ${. + self cssStatementsContentOn: aStream. + aStream + newLine; + nextPut: $} +] + +{ #category : #Printing } +CssKeyframesRule >> cssStatementsContentOn: aStream [ + | tabStream | + tabStream := IndentOnNewLineWriteStream on: aStream. + tabStream newLine. + styleSheet cssContentOn: tabStream +] + +{ #category : #initialization } +CssKeyframesRule >> initializeNamed: aName enabling: aCascadingStyleSheet [ + + keyframeName := aName. + styleSheet := aCascadingStyleSheet +] diff --git a/source/RenoirSt/CssPerspective.class.st b/source/RenoirSt/CssPerspective.class.st new file mode 100644 index 0000000..b8ec0f6 --- /dev/null +++ b/source/RenoirSt/CssPerspective.class.st @@ -0,0 +1,31 @@ +Class { + #name : #CssPerspective, + #superclass : #CssFunction, + #instVars : [ + 'value' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssPerspective class >> of: aCssMeasure [ + + ^ self new initializeOf: aCssMeasure +] + +{ #category : #private } +CssPerspective >> cssFunctionParametersContentOn: aWriteStream [ + + ^ value cssContentOn: aWriteStream +] + +{ #category : #private } +CssPerspective >> functionName [ + ^ 'perspective' +] + +{ #category : #Initialization } +CssPerspective >> initializeOf: aCssMeasure [ + + value := aCssMeasure +] diff --git a/source/RenoirSt/CssRotate.class.st b/source/RenoirSt/CssRotate.class.st new file mode 100644 index 0000000..e57d05b --- /dev/null +++ b/source/RenoirSt/CssRotate.class.st @@ -0,0 +1,55 @@ +Class { + #name : #CssRotate, + #superclass : #CssFunction, + #instVars : [ + 'rotationDegrees', + 'function' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssRotate class >> by: aCssMeasure [ + ^ self by: aCssMeasure usingFunction: 'rotate' +] + +{ #category : #'private - Instance Creation' } +CssRotate class >> by: aCssMeasure usingFunction: aFunctionName [ + ^ self new initializeBy: aCssMeasure usingFunction: aFunctionName +] + +{ #category : #'Instance Creation' } +CssRotate class >> onXAxisBy: aCssMeasure [ + + ^ self by: aCssMeasure usingFunction: 'rotateX' +] + +{ #category : #'Instance Creation' } +CssRotate class >> onYAxisBy: aCssMeasure [ + ^ self by: aCssMeasure usingFunction: 'rotateY' +] + +{ #category : #'Instance Creation' } +CssRotate class >> onZAxisBy: aCssMeasure [ + + ^ self by: aCssMeasure usingFunction: 'rotateZ' +] + +{ #category : #private } +CssRotate >> cssFunctionParametersContentOn: aWriteStream [ + + rotationDegrees cssContentOn: aWriteStream. +] + +{ #category : #private } +CssRotate >> functionName [ + + ^function +] + +{ #category : #initialization } +CssRotate >> initializeBy: aCssMeasure usingFunction: aFunctionName [ + + rotationDegrees := aCssMeasure. + function := aFunctionName +] diff --git a/source/RenoirSt/CssRotate3D.class.st b/source/RenoirSt/CssRotate3D.class.st new file mode 100644 index 0000000..0cc3c5a --- /dev/null +++ b/source/RenoirSt/CssRotate3D.class.st @@ -0,0 +1,39 @@ +Class { + #name : #CssRotate3D, + #superclass : #CssFunction, + #instVars : [ + 'values' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssRotate3D class >> onXAxis: anXAxisCoordinate yAxis: aYAxisCoordinate zAxis: aZAxisCoordinate by: aCssMeasure [ + + ^ self new initializeOnXAxis: anXAxisCoordinate yAxis: aYAxisCoordinate zAxis: aZAxisCoordinate by: aCssMeasure +] + +{ #category : #private } +CssRotate3D >> cssFunctionParametersContentOn: aWriteStream [ + + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssRotate3D >> functionName [ + ^ 'rotate3d' +] + +{ #category : #Initialization } +CssRotate3D >> initializeOnXAxis: anXAxisCoordinate yAxis: aYAxisCoordinate zAxis: aZAxisCoordinate by: aCssMeasure [ + values := OrderedCollection + with: anXAxisCoordinate + with: aYAxisCoordinate + with: aZAxisCoordinate + with: aCssMeasure +] diff --git a/source/RenoirSt/CssScale.class.st b/source/RenoirSt/CssScale.class.st new file mode 100644 index 0000000..948b6c1 --- /dev/null +++ b/source/RenoirSt/CssScale.class.st @@ -0,0 +1,94 @@ +Class { + #name : #CssScale, + #superclass : #CssFunction, + #instVars : [ + 'values', + 'function' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssScale class >> by: anInteger [ + ^ self onXAxisBy: anInteger andYAxisBy: '' +] + +{ #category : #'private - Instance Creation' } +CssScale class >> firstAxisValue: anInteger secondAxisValue: anotherInteger thirdAxisValue: aThirdInteger usingFunction: aFunctionName [ + ^ self new + initializeFirstAxisValue: anInteger + secondAxisValue: anotherInteger + andThirdAxisValue: aThirdInteger + usingFunction: aFunctionName +] + +{ #category : #'Instance Creation' } +CssScale class >> onXAxisBy: anInteger andYAxisBy: anotherInteger [ + ^ self + firstAxisValue: anInteger + secondAxisValue: anotherInteger + thirdAxisValue: '' + usingFunction: 'scale' +] + +{ #category : #'Instance Creation' } +CssScale class >> onXAxisBy: anInteger onYAxisBy: anotherInteger andZAxisBy: aThirdInteger [ + ^ self + firstAxisValue: anInteger + secondAxisValue: anotherInteger + thirdAxisValue: aThirdInteger + usingFunction: 'scale3d' +] + +{ #category : #'Instance Creation' } +CssScale class >> onlyOnXAxisBy: anInteger [ + ^ self + firstAxisValue: anInteger + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'scaleX' +] + +{ #category : #'Instance Creation' } +CssScale class >> onlyOnYAxisBy: anInteger [ + ^ self + firstAxisValue: anInteger + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'scaleY' +] + +{ #category : #'Instance Creation' } +CssScale class >> onlyOnZAxisBy: anInteger [ + ^ self + firstAxisValue: anInteger + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'scaleZ' +] + +{ #category : #private } +CssScale >> cssFunctionParametersContentOn: aWriteStream [ + + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssScale >> functionName [ + ^ function +] + +{ #category : #Initialization } +CssScale >> initializeFirstAxisValue: anInteger secondAxisValue: anotherInteger andThirdAxisValue: aThirdInteger usingFunction: aFunctionName [ + + values := OrderedCollection with: anInteger. + function := aFunctionName. + + anotherInteger asString isEmpty ifFalse: [ values add: anotherInteger ]. + aThirdInteger asString isEmpty ifFalse: [ values add: aThirdInteger ] +] diff --git a/source/RenoirSt/CssSkew.class.st b/source/RenoirSt/CssSkew.class.st new file mode 100644 index 0000000..73a64ea --- /dev/null +++ b/source/RenoirSt/CssSkew.class.st @@ -0,0 +1,56 @@ +Class { + #name : #CssSkew, + #superclass : #CssFunction, + #instVars : [ + 'values', + 'function' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssSkew class >> by: aCssMeasure [ + ^ self firstValue: aCssMeasure secondValue: '' usingFunction: 'skew' +] + +{ #category : #'private - Instance Creation' } +CssSkew class >> firstValue: aCssMeasure secondValue: anotherCssMeasure usingFunction: aFunctionName [ + ^ self new initializeFirstValue: aCssMeasure secondValue: anotherCssMeasure usingFunction: aFunctionName +] + +{ #category : #'Instance Creation' } +CssSkew class >> onXAxisBy: aCssMeasure andYAxisBy: anotherCssMeasure [ + ^ self firstValue: aCssMeasure secondValue: anotherCssMeasure usingFunction: 'skew' +] + +{ #category : #'Instance Creation' } +CssSkew class >> onlyOnXAxisBy: aCssMeasure [ + ^ self firstValue: aCssMeasure secondValue: '' usingFunction: 'skewX' +] + +{ #category : #'Instance Creation' } +CssSkew class >> onlyOnYAxisBy: aCssMeasure [ + ^ self firstValue: aCssMeasure secondValue: '' usingFunction: 'skewY' +] + +{ #category : #private } +CssSkew >> cssFunctionParametersContentOn: aWriteStream [ + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssSkew >> functionName [ + ^ function +] + +{ #category : #Initialization } +CssSkew >> initializeFirstValue: aCssMeasure secondValue: anotherCssMeasure usingFunction: aFunctionName [ + values := OrderedCollection with: aCssMeasure. + function := aFunctionName. + anotherCssMeasure asString isEmpty ifFalse: [ values add: anotherCssMeasure ] +] diff --git a/source/RenoirSt/CssSteps.class.st b/source/RenoirSt/CssSteps.class.st new file mode 100644 index 0000000..16ebce1 --- /dev/null +++ b/source/RenoirSt/CssSteps.class.st @@ -0,0 +1,44 @@ +Class { + #name : #CssSteps, + #superclass : #CssFunction, + #instVars : [ + 'values' + ], + #category : #'RenoirSt-Easing' +} + +{ #category : #'Instance Creation' } +CssSteps class >> by: anInteger [ + ^ self steps: anInteger headingTo: '' +] + +{ #category : #'Instance Creation' } +CssSteps class >> by: anInteger direction: aConstant [ + ^ self steps: anInteger headingTo: aConstant. +] + +{ #category : #'private - Instance Creation' } +CssSteps class >> steps: anInteger headingTo: aConstant [ + ^ self new initialize steps: anInteger headingTo: aConstant +] + +{ #category : #private } +CssSteps >> cssFunctionParametersContentOn: aWriteStream [ + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssSteps >> functionName [ + ^ 'steps' +] + +{ #category : #Initialization } +CssSteps >> steps: anInteger headingTo: aConstant [ + values := OrderedCollection with: anInteger. + aConstant isEmpty ifFalse: [ values add: aConstant ] +] diff --git a/source/RenoirSt/CssTranslate.class.st b/source/RenoirSt/CssTranslate.class.st new file mode 100644 index 0000000..add0a94 --- /dev/null +++ b/source/RenoirSt/CssTranslate.class.st @@ -0,0 +1,97 @@ +Class { + #name : #CssTranslate, + #superclass : #CssFunction, + #instVars : [ + 'values', + 'function' + ], + #category : #'RenoirSt-Transformation' +} + +{ #category : #'Instance Creation' } +CssTranslate class >> by: aCssMeasure [ + + ^ self onXAxisBy: aCssMeasure andYAxisBy: '' +] + +{ #category : #'private - Instance Creation' } +CssTranslate class >> firstAxisValue: aCssMeasure secondAxisValue: anotherCssMeasure thirdAxisValue: aThirdCssMeasure usingFunction: aFunctionName [ + + ^ self new + initializeFirstAxisValue: aCssMeasure + secondAxisValue: anotherCssMeasure + andThirdAxisValue: aThirdCssMeasure + usingFunction: aFunctionName +] + +{ #category : #'Instance Creation' } +CssTranslate class >> onXAxisBy: aCssMeasure andYAxisBy: anotherCssMeasure [ + ^ self + firstAxisValue: aCssMeasure + secondAxisValue: anotherCssMeasure + thirdAxisValue: '' + usingFunction: 'translate' +] + +{ #category : #'Instance Creation' } +CssTranslate class >> onXAxisBy: aCssMeasure onYAxisBy: anotherCssMeasure andZAxisBy: aThirdCssMeasure [ + ^ self + firstAxisValue: aCssMeasure + secondAxisValue: anotherCssMeasure + thirdAxisValue: aThirdCssMeasure + usingFunction: 'translate3d' +] + +{ #category : #'Instance Creation' } +CssTranslate class >> onlyOnXAxisBy: aCssMeasure [ + ^ self + firstAxisValue: aCssMeasure + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'translateX' +] + +{ #category : #'Instance Creation' } +CssTranslate class >> onlyOnYAxisBy: aCssMeasure [ + ^ self + firstAxisValue: aCssMeasure + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'translateY' +] + +{ #category : #'Instance Creation' } +CssTranslate class >> onlyOnZAxisBy: aCssMeasure [ + ^ self + firstAxisValue: aCssMeasure + secondAxisValue: '' + thirdAxisValue: '' + usingFunction: 'translateZ' +] + +{ #category : #private } +CssTranslate >> cssFunctionParametersContentOn: aWriteStream [ + + values + do: [ :value | value cssContentOn: aWriteStream ] + separatedBy: [ + aWriteStream + nextPut: $,; + space ] +] + +{ #category : #private } +CssTranslate >> functionName [ + + ^ function +] + +{ #category : #Initialization } +CssTranslate >> initializeFirstAxisValue: aCssMeasure secondAxisValue: anotherCssMeasure andThirdAxisValue: aThirdCssMeasure usingFunction: aFunctionName [ + + values := OrderedCollection with: aCssMeasure. + function := aFunctionName. + + anotherCssMeasure asString isEmpty ifFalse: [ values add: anotherCssMeasure ]. + aThirdCssMeasure asString isEmpty ifFalse: [ values add: aThirdCssMeasure ] +]