Skip to content

Commit

Permalink
Merge pull request #149 from ImJimmi/animations
Browse files Browse the repository at this point in the history
Animations
  • Loading branch information
ImJimmi authored Jul 25, 2024
2 parents 05b58b7 + a669d8a commit ba7d7cb
Show file tree
Hide file tree
Showing 75 changed files with 3,789 additions and 470 deletions.
39 changes: 0 additions & 39 deletions .github/workflows/demo-runner.yml

This file was deleted.

115 changes: 87 additions & 28 deletions .github/workflows/test-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(JIVE
VERSION 1.0.0
VERSION 1.2.0
)
configure_file(version.txt.in version.txt)

Expand Down
143 changes: 143 additions & 0 deletions docs/Animations.md
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 },
},
}
```
Binary file added docs/assets/animations.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 12 additions & 3 deletions jive_components/canvases/jive_BackgroundCanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ namespace jive

void BackgroundCanvas::paint(juce::Graphics& g)
{
const auto bounds = getLocalBounds().toFloat();

g.reduceClipRegion(shape);

const auto bounds = getLocalBounds().toFloat();
const auto backgroundScale = 1.0f - ((borderWidth / 2.0f) / getWidth());
apply(background, g, bounds);
g.fillAll();
g.fillPath(shape,
juce::AffineTransform::scale(backgroundScale,
backgroundScale,
bounds.getCentreX(),
bounds.getCentreY()));

apply(borderFill, g, bounds);
g.strokePath(shape, juce::PathStrokeType{ borderWidth * 2.0f });
g.strokePath(shape,
juce::PathStrokeType{
borderWidth * 2.0f,
});
}

void BackgroundCanvas::resized()
Expand Down
Loading

0 comments on commit ba7d7cb

Please sign in to comment.