Skip to content

Commit

Permalink
UI Infrastructure: Animation API (#77)
Browse files Browse the repository at this point in the history
* Start adding an animation API + tests

* Get tests green

* Add useAnimations

* Clean up formatting

* Skip the translation transform for now

* Factor clamp / interpolate to Math

* Fix up formatting

* Remove placeholder test

* Fix example compilation
  • Loading branch information
bryphe committed Dec 4, 2018
1 parent 3da5c01 commit e6e61d0
Show file tree
Hide file tree
Showing 24 changed files with 689 additions and 221 deletions.
181 changes: 103 additions & 78 deletions examples/Autocomplete.re
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,53 @@ module Glfw = Reglfw.Glfw;
/* Define our app state */

type item = {
name: string,
description: string
name: string,
description: string,
};

type state = {
text: string,
items: list(item)
text: string,
items: list(item),
};

let createItem = (name, description) => {
let ret: item = {name, description}
ret;
}
let ret: item = {name, description};
ret;
};

let initialState: state = {
text: "",
items: [
createItem("Item 1", "Item 1 Description"),
createItem("Item 2", "Item 2 Description"),
createItem("Item 3", "Item 3 Description")
]
text: "",
items: [
createItem("Item 1", "Item 1 Description"),
createItem("Item 2", "Item 2 Description"),
createItem("Item 3", "Item 3 Description"),
],
};

/* Define the operations we allow on our app state */

type action =
| UpdateText(string)
| SetItems(list(item))
/* These actions should be baked into an input control! */
| Backspace
| ClearWord;
| UpdateText(string)
| SetItems(list(item))
/* These actions should be baked into an input control! */
| Backspace
| ClearWord;

/* And the results of those actions on our app state, via a reducer */

let reducer = (s: state, a: action) => {
switch (a) {
| UpdateText(t) => { ...s, text: s.text ++ t}
| SetItems(i) => { ...s, items: i }
| Backspace => {
let length = String.length(s.text);
if (length > 0) {
{ ...s, text: String.sub(s.text, 0, length - 1) }
} else {
s
}
}
| ClearWord => { ...s, text: "" }
let reducer = (s: state, a: action) =>
switch (a) {
| UpdateText(t) => {...s, text: s.text ++ t}
| SetItems(i) => {...s, items: i}
| Backspace =>
let length = String.length(s.text);
if (length > 0) {
{...s, text: String.sub(s.text, 0, length - 1)};
} else {
s;
};
};
| ClearWord => {...s, text: ""}
};

/* Helper method... There's no string.indexOf equivalent in the OCaml stdlib, surprisingly! */
let contains = (s1, s2) => {
Expand All @@ -69,82 +67,109 @@ let contains = (s1, s2) => {
) {
| Not_found => false
};
}
};

/* A selector to get the set of items, based on our filter text */
let filterItems = (filterText: string, items: list(item)) => {
let ft = String.lowercase_ascii(filterText);
let ft = String.lowercase_ascii(filterText);

let f = (i) => {
let normalizedName = String.lowercase_ascii(i.name);
let normalizedDescription = String.lowercase_ascii(i.description);
contains(normalizedName, ft) || contains(normalizedDescription, ft)
};
let f = i => {
let normalizedName = String.lowercase_ascii(i.name);
let normalizedDescription = String.lowercase_ascii(i.description);

contains(normalizedName, ft) || contains(normalizedDescription, ft);
};

List.filter(f, items);
List.filter(f, items);
};

let init = app => {

let width = 400;
let height = 1;

let w = App.createWindow(app, "test", ~createOptions={
...Window.defaultCreateOptions,
decorated: false,
visible: false,
width,
height,
});
let w =
App.createWindow(
app,
"test",
~createOptions={
...Window.defaultCreateOptions,
decorated: false,
visible: false,
width,
height,
},
);

/* Figure out current monitor dimensions, so we can center it! */
let monitor = Monitor.getPrimaryMonitor();
let monitorSize = Monitor.getSize(monitor);

Window.setPos(w, (monitorSize.width - width) / 2, ((monitorSize.height - height) / 2));
Window.setPos(
w,
(monitorSize.width - width) / 2,
(monitorSize.height - height) / 2,
);
Window.show(w);

let ui = UI.create(w, ~createOptions={ autoSize: true });
let ui = UI.create(w, ~createOptions={autoSize: true});

let textHeaderStyle = Style.make(~backgroundColor=Colors.black, ~color=Colors.white, ~fontFamily="Roboto-Regular.ttf", ~fontSize=24, ~height=30, ());
let textHeaderStyle =
Style.make(
~backgroundColor=Colors.black,
~color=Colors.white,
~fontFamily="Roboto-Regular.ttf",
~fontSize=24,
~height=30,
(),
);

/* let smallerTextStyle = Style.make(~backgroundColor=Colors.black, ~color=Colors.white, ~fontFamily="Roboto-Regular.ttf", ~fontSize=12, ()); */

/* Listen to key press events, and coerce them into actions */
let _ = Event.subscribe(w.onKeyPress, (keyEvent) => {
App.dispatch(app, UpdateText(keyEvent.character));
});
let _ =
Event.subscribe(w.onKeyPress, keyEvent =>
App.dispatch(app, UpdateText(keyEvent.character))
);

/* Listen to key down events, and coerce them into actions, too */
let _ = Event.subscribe(w.onKeyDown, (keyEvent) => {
/* TODO: Can we implement this API w/o GLFW leaking through? */
if (keyEvent.key == Glfw.Key.GLFW_KEY_BACKSPACE) {
let _ =
Event.subscribe(w.onKeyDown, keyEvent =>
/* TODO: Can we implement this API w/o GLFW leaking through? */
if (keyEvent.key == Glfw.Key.GLFW_KEY_BACKSPACE) {
App.dispatch(app, Backspace);
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_H && keyEvent.ctrlKey) {
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_H && keyEvent.ctrlKey) {
App.dispatch(app, Backspace);
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_ESCAPE) {
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_ESCAPE) {
App.quit(0);
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_W && keyEvent.ctrlKey) {
App.dispatch(app, ClearWord)
}
});
} else if (keyEvent.key == Glfw.Key.GLFW_KEY_W && keyEvent.ctrlKey) {
App.dispatch(app, ClearWord);
}
);

/* Render function - where the magic happens! */
Window.setRenderCallback(w, () => {
let state = App.getState(app);

let filteredItems = filterItems(state.text, state.items);
let items = List.map((i) => <text style=(textHeaderStyle)>{i.name}</text>, filteredItems);

UI.render(ui,
<view style=(Style.make(~backgroundColor=Colors.blue,~width=width, ()))>
<view style=(Style.make(~height=50, ()))>
<text style=(textHeaderStyle)>{state.text ++ "|"}</text>
</view>
<view style=(Style.make(()))>...items</view>
</view>);
});
Window.setRenderCallback(
w,
() => {
let state = App.getState(app);

let filteredItems = filterItems(state.text, state.items);
let items =
List.map(
i => <text style=textHeaderStyle> {i.name} </text>,
filteredItems,
);

UI.render(
ui,
<view style={Style.make(~backgroundColor=Colors.blue, ~width, ())}>
<view style={Style.make(~height=50, ())}>
<text style=textHeaderStyle> {state.text ++ "|"} </text>
</view>
<view style={Style.make()}> ...items </view>
</view>,
);
},
);
};

App.startWithState(initialState, reducer, init);
127 changes: 111 additions & 16 deletions examples/Bin.re
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,123 @@ open Revery.Core;
open Revery.Math;
open Revery.UI;

let init = app => {
module Logo = (
val component((render, ~children, ()) =>
render(
() => {
let rotation =
useAnimation(
Animated.floatValue(0.),
{
toValue: 6.28,
duration: Seconds(8.),
delay: Seconds(1.0),
repeat: true,
},
);

let w = App.createWindow(app, "test");
let rotationY =
useAnimation(
Animated.floatValue(0.),
{
toValue: 6.28,
duration: Seconds(4.),
delay: Seconds(0.5),
repeat: true,
},
);

let ui = UI.create(w);
<image
src="outrun-logo.png"
style={Style.make(
~width=512,
~height=256,
~transform=[
RotateY(Angle.from_radians(rotationY)),
RotateX(Angle.from_radians(rotation)),
],
(),
)}
/>;
},
~children,
)
)
);

module AnimatedText = (
val component((render, ~delay, ~textContent, ~children, ()) =>
render(
() => {
let opacity: float =
useAnimation(
Animated.floatValue(0.),
{
toValue: 1.0,
duration: Seconds(1.),
delay: Seconds(delay),
repeat: false,
},
);

/* TODO: Transforms on text don't work yet */
/* let translate: float = */
/* useAnimation( */
/* Animated.floatValue(-100.), */
/* { */
/* toValue: 0., */
/* duration: Seconds(2.), */
/* delay: Seconds(delay), */
/* repeat: false, */
/* }, */
/* ); */

let textHeaderStyle = Style.make(~backgroundColor=Colors.red, ~color=Colors.white, ~fontFamily="Roboto-Regular.ttf", ~fontSize=24, ~marginHorizontal=12, ());
/* let containerStyle = Style.make(~transform=[TranslateY(translate)], ()); */

let smallerTextStyle = Style.make(~backgroundColor=Colors.red, ~color=Colors.white, ~opacity=0.5, ~fontFamily="Roboto-Regular.ttf", ~fontSize=18, ~marginVertical=24, ());
let textHeaderStyle =
Style.make(
~color=Colors.white,
~fontFamily="Roboto-Regular.ttf",
~fontSize=24,
~marginHorizontal=8,
~opacity,
/* ~transform=[TranslateY(translate)], */
(),
);

Window.setShouldRenderCallback(w, () => true);
<text style=textHeaderStyle> textContent </text>;
},
~children,
)
)
);

let init = app => {
let w = App.createWindow(app, "Welcome to Revery!");

let ui = UI.create(w);

Window.setRenderCallback(w, () => {
UI.render(ui,
<view style=(Style.make(~position=LayoutTypes.Absolute, ~bottom=50, ~top=50, ~left=50, ~right=50, ~backgroundColor=Colors.blue, ()))>
<view style=(Style.make(~position=LayoutTypes.Absolute, ~bottom=0, ~width=10, ~height=10, ~backgroundColor=Colors.red, ())) />
<image src="outrun-logo.png" style=(Style.make(~width=128, ~height=64, ~transform=[RotateX(Angle.from_radians(Time.getElapsedTime()))], ())) />
<text style=(textHeaderStyle)>"Hello World!"</text>
<text style=(smallerTextStyle)>"Welcome to revery"</text>
<view style=(Style.make(~width=25, ~height=25, ~opacity=sin(Time.getElapsedTime()), ~backgroundColor=Colors.green, ())) />
</view>);
});
Window.setRenderCallback(w, () =>
UI.render(
ui,
<view
style={Style.make(
~position=LayoutTypes.Absolute,
~bottom=0,
~top=0,
~left=0,
~right=0,
(),
)}>
<Logo />
<view style={Style.make(~flexDirection=Row,~alignItems=AlignFlexEnd, ())}>
<AnimatedText delay=0.0 textContent="Welcome" />
<AnimatedText delay=0.5 textContent="to" />
<AnimatedText delay=1. textContent="Revery" />
</view>
</view>,
)
);
};

App.start(init);

0 comments on commit e6e61d0

Please sign in to comment.