-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #149 from ImJimmi/animations
Animations
- Loading branch information
Showing
75 changed files
with
3,789 additions
and
470 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,49 +4,108 @@ on: | |
pull_request: | ||
branches: [main] | ||
|
||
run-name: "Test Runner - #${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }}" | ||
|
||
env: | ||
BUILD_TYPE: Debug | ||
CMAKE_BUILD_DIRECTORY: build | ||
|
||
defaults: | ||
run: | ||
shell: bash | ||
|
||
jobs: | ||
test-windows: | ||
name: Test Windows | ||
runs-on: windows-latest | ||
run-tests: | ||
strategy: | ||
matrix: | ||
platform: [macos-latest, windows-latest] | ||
include: | ||
- platform: macos-latest | ||
platform-name: macOS | ||
package-manager: brew | ||
additional-cmake-options: -DJIVE_ENABLE_COVERAGE=ON | ||
- platform: windows-latest | ||
platform-name: Windows | ||
platform-shell: cmd | ||
package-manager: choco | ||
|
||
name: Test ${{ matrix.platform-name }} | ||
runs-on: ${{ matrix.platform }} | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
submodules: "recursive" | ||
|
||
- name: Configure CMake | ||
run: cmake -Bbuild -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DJIVE_BUILD_TEST_RUNNER=ON | ||
|
||
- name: Build | ||
run: cmake --build build --config ${{env.BUILD_TYPE}} | ||
|
||
- name: Test | ||
run: cd build && ctest -j14 -C Debug -T test --output-on-failure | ||
|
||
test-macos: | ||
name: Test macOS | ||
runs-on: macos-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Install Ninja | ||
run: ${{ matrix.package-manager }} install ninja | ||
- name: Setup devcmd | ||
if: runner.os == 'Windows' | ||
uses: ilammy/[email protected] | ||
|
||
- name: CMake Generate | ||
run: | | ||
cmake \ | ||
-B ${{ env.CMAKE_BUILD_DIRECTORY }} \ | ||
-G Ninja \ | ||
-D CMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ | ||
-D JIVE_BUILD_TEST_RUNNER=ON \ | ||
-D JIVE_BUILD_DEMO_RUNNER=ON \ | ||
${{ matrix.additional-cmake-options }} | ||
- name: CMake Build | ||
run: | | ||
cmake \ | ||
--build ${{ env.CMAKE_BUILD_DIRECTORY }} \ | ||
--config ${{ env.BUILD_TYPE }} | ||
- name: Run CTest | ||
run: | | ||
cd ${{ env.CMAKE_BUILD_DIRECTORY }} | ||
ctest \ | ||
--output-on-failure \ | ||
--extra-verbose \ | ||
-j14 \ | ||
-C ${{ env.BUILD_TYPE }} \ | ||
-T test \ | ||
-O ctest.log | ||
- name: Install lcov | ||
if: runner.os == 'macOS' | ||
working-directory: ${{github.workspace}}/build | ||
run: brew install lcov | ||
|
||
- name: Generate Coverage Report | ||
if: runner.os == 'macOS' | ||
working-directory: ${{github.workspace}}/build | ||
run: | | ||
lcov \ | ||
--directory . \ | ||
--capture \ | ||
--output-file coverage.info \ | ||
--ignore-errors inconsistent \ | ||
--ignore-errors gcov \ | ||
--ignore-errors range \ | ||
--ignore-errors source | ||
- name: Upload Coverage Report | ||
if: runner.os == 'macOS' | ||
uses: codecov/codecov-action@v4 | ||
with: | ||
submodules: "recursive" | ||
|
||
- name: Configure CMake | ||
run: cmake -Bbuild -GXcode -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DJIVE_BUILD_TEST_RUNNER=ON -DJIVE_ENABLE_COVERAGE=ON -DJIVE_ENABLE_SANITISERS=ON | ||
|
||
- name: Build | ||
run: cmake --build build --config ${{env.BUILD_TYPE}} | ||
|
||
- name: Test | ||
run: cd build && ctest -j14 -C Debug -T test --output-on-failure | ||
fail_ci_if_error: true | ||
files: ./build/coverage.info | ||
flags: unittests | ||
token: ${{ secrets.CODECOV_TOKEN }} | ||
verbose: true | ||
|
||
- name: Stage Artifacts | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: "${{ matrix.platform-name }} #${{ github.event.pull_request.number }}" | ||
path: ${{ env.CMAKE_BUILD_DIRECTORY }}/ctest.log | ||
if-no-files-found: ignore | ||
retention-days: 7 | ||
overwrite: true | ||
|
||
verify-formatting: | ||
name: Verify Formatting | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Animations | ||
|
||
![Animations example from the Demo Runner](./assets/animations.gif) | ||
|
||
JIVE offers a powerful animations API driven largely by its `Property` type, with lots of other utilities that make building dynamic, modern UIs a breeze. | ||
|
||
JIVE borrows the [CSS `transition`](https://www.w3schools.com/css/css3_transitions.asp) syntax, and aims to support all the same properties as CSS transitions do. | ||
|
||
## Animating a `jive::Property` | ||
|
||
`jive::Property` is a powerful class that represents a property in a `juce::ValueTree`, or a `jive::Object` (derived from `juce::DynamicObject`). With the introduction of animations in JIVE 1.2, properties can now be animated by setting a `"transition"` property on the same source. | ||
|
||
The `"transition"` properties use the same syntax as the `transition` property in CSS, and so can be defined by a string like so: | ||
|
||
```cpp | ||
juce::ValueTree state{ | ||
"MyState", | ||
{ | ||
{ "value", 100 }, | ||
{ "transition", "value 500ms ease-in" }, | ||
}, | ||
}; | ||
``` | ||
|
||
Here, we've specified a transition for the `"value"` property, saying that when changed, it should transition to its new value over 500ms, using an ease-in curve. | ||
|
||
Multiple transitions can be specified using a comma-separated list: | ||
|
||
```cpp | ||
juce::ValueTree state{ | ||
"MyState", | ||
{ | ||
{ "value", 100 }, | ||
{ "alpha", 0.3 }, | ||
{ "transition", "value 500ms ease-in, alpha 4s 1s" }, | ||
}, | ||
}; | ||
``` | ||
|
||
This time, we've specified an additional transition for the `"alpha"` property, which has a delay of 1s, followed by a duration of 4s. | ||
|
||
If we now change the `"value"` property, we still get the same behaviour as we would _without_ a transition defined, however we can also querty the current state of its transition like so: | ||
|
||
```cpp | ||
juce::ValueTree state{ | ||
"MyState", | ||
{ | ||
{ "value", 100 }, | ||
{ "transition", "value 500ms" }, | ||
}, | ||
}; | ||
jive::Property<float> value{ state, "value" }; | ||
expect(value.get() == 100.0f); // true | ||
expect(value.calculateCurrent() == 100.0f); // true | ||
|
||
value = 350.0f; | ||
expect(value.get() == 350.0f); // true - .get() returns the target | ||
// value, not the current | ||
// interpolated value | ||
expect(value.calculateCurrent() == 100.0f); // true - no time has passed so the | ||
// current value is still 100 | ||
|
||
// Wait 250ms... | ||
expect(value.get() == 350.0f); // true | ||
expect(value.calculateCurrent() == 225.0f) // true - half the animation duration | ||
// has passed, so the value is 50% | ||
// to its target | ||
``` | ||
The `jive::Property::onValueChanged()` callback is invoked once when the target value changes. The `jive::Property::onTransitionProgressed()` callback is called repeatedly while a value is transitioning, so this would be a good place to call `calculateCurrent()`. | ||
JIVE even supports more complex CSS transition definitions, like `cubic-bezier()`, allowing for highly customised transitions. | ||
```cpp | ||
juce::ValueTree state{ | ||
"MyState", | ||
{ | ||
{ "value", 100 }, | ||
{ "transition", "value 1s cubic-bezier(0.17, 0.67, 0.83, 0.67)" }, | ||
}, | ||
}; | ||
``` | ||
|
||
## Interpolation | ||
|
||
`jive::Property::calculateCurrent()` will calculate the current interpolated value between the source value, and the target value. This calculation calls `jive::interpolate()` to interpolate between the given values. | ||
|
||
Custom and/or non-numeric types can be interpolated by writing an implementation of `jive::Interpolate<>` for the given type. For example: | ||
|
||
```cpp | ||
namespace jive | ||
{ | ||
template <> | ||
struct Interpolate<MyCustomType> | ||
{ | ||
MyCustomType operator()(const MyCustomType& start, | ||
const MyCustomType& end, | ||
double proportion) const | ||
{ | ||
// ... | ||
} | ||
} | ||
}; | ||
|
||
jive::Property<MyCustomType> property; | ||
property.calculateCurrent(); // Calls jive::Interpolate<MyCustomType> | ||
``` | ||
|
||
## Easing | ||
|
||
As well as the built-in easing functions, named in accordance with the standard CSS easing functions, more complex transitions with custom easing functions can be defined by constructing a `jive::Transition` object. For example, a bouncing transition could be defined like so: | ||
|
||
```cpp | ||
jive::Transition transition; | ||
transition.duration = juce::RelativeTime::seconds(1.5); | ||
transition.timingFunction = [](double x) { | ||
// https://easings.net/#easeOutBounce | ||
static constexpr auto n1 = 7.5625; | ||
static constexpr auto d1 = 2.75; | ||
|
||
if (x < (1.0 / d1)) | ||
return n1 * x * x; | ||
|
||
if (x < (2.0 / d1)) | ||
return n1 * (x -= (1.5 / d1)) * x + 0.75; | ||
|
||
if (x < (2.5 / d1)) | ||
return n1 * (x -= (2.25 / d1)) * x + 0.9375; | ||
|
||
return n1 * (x -= (2.625 / d1)) * x + 0.984375; | ||
}; | ||
|
||
jive::Transitions::ReferenceCountedPointer transitions = new jive::Transitions{}; | ||
(*transition)["value"] = transition; | ||
|
||
juce::ValueTree state{ | ||
"State", | ||
{ | ||
{ "value", 123 }, | ||
{ "transition", transitions }, | ||
}, | ||
} | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.