diff --git a/examples/Calculator.re b/examples/Calculator.re
new file mode 100644
index 000000000..0a00e53a2
--- /dev/null
+++ b/examples/Calculator.re
@@ -0,0 +1,392 @@
+open Revery;
+open Revery.Core;
+open Revery.Core.Events;
+open Revery.Core.Window;
+open Revery.UI;
+open Revery.UI.Components;
+
+module Row = (
+ val component((render, ~children, ()) =>
+ render(
+ () => {
+ let style =
+ Style.make(
+ ~flexDirection=LayoutTypes.Row,
+ ~alignItems=LayoutTypes.AlignStretch,
+ ~justifyContent=LayoutTypes.JustifyCenter,
+ ~flexGrow=1,
+ (),
+ );
+ ...children ;
+ },
+ ~children,
+ )
+ )
+);
+
+module Column = (
+ val component((render, ~children, ()) =>
+ render(
+ () => {
+ let style =
+ Style.make(
+ ~flexDirection=LayoutTypes.Column,
+ ~alignItems=LayoutTypes.AlignStretch,
+ ~justifyContent=LayoutTypes.JustifyCenter,
+ ~backgroundColor=Colors.darkGrey,
+ ~flexGrow=1,
+ (),
+ );
+ ...children ;
+ },
+ ~children,
+ )
+ )
+);
+
+module Button = (
+ val component((render, ~fontFamily="Roboto-Regular.ttf", ~contents: string, ~onClick, ~children, ()) =>
+ render(
+ () => {
+ let clickableStyle =
+ Style.make(
+ ~position=LayoutTypes.Relative,
+ ~backgroundColor=Colors.lightGrey,
+ ~justifyContent=LayoutTypes.JustifyCenter,
+ ~alignItems=LayoutTypes.AlignCenter,
+ ~flexGrow=1,
+ /* Min width */
+ ~width=150,
+ ~margin=10,
+ (),
+ );
+ let viewStyle =
+ Style.make(
+ ~position=LayoutTypes.Relative,
+ ~justifyContent=LayoutTypes.JustifyCenter,
+ ~alignItems=LayoutTypes.AlignCenter,
+ (),
+ );
+ let textStyle =
+ Style.make(
+ ~color=Colors.black,
+ ~fontFamily,
+ ~fontSize=32,
+ (),
+ );
+
+
+
+ contents
+
+ ;
+ },
+ ~children,
+ )
+ )
+);
+
+module Display = (
+ val component((render, ~display: string, ~curNum: string, ~children, ()) =>
+ render(
+ () => {
+ let viewStyle =
+ Style.make(
+ ~backgroundColor=Colors.white,
+ ~height=120,
+ ~flexDirection=LayoutTypes.Column,
+ ~alignItems=LayoutTypes.AlignStretch,
+ ~justifyContent=LayoutTypes.JustifyFlexStart,
+ ~flexGrow=2,
+ (),
+ );
+ let displayStyle =
+ Style.make(
+ ~color=Colors.black,
+ ~fontFamily="Roboto-Regular.ttf",
+ ~fontSize=20,
+ ~margin=15,
+ (),
+ );
+ let numStyle =
+ Style.make(
+ ~color=Colors.black,
+ ~fontFamily="Roboto-Regular.ttf",
+ ~fontSize=32,
+ ~margin=15,
+ (),
+ );
+
+
+ display
+ curNum
+ ;
+ },
+ ~children,
+ )
+ )
+);
+
+type operator = [ | `Nop | `Add | `Sub | `Mul | `Div];
+
+let showFloat = float => {
+ let string = string_of_float(float);
+ if (String.length(string) > 1 && string.[String.length(string) - 1] == '.') {
+ String.sub(string, 0, String.length(string) - 1);
+ } else {
+ string;
+ };
+};
+type state = {
+ operator, /* Current operator being applied */
+ result: float, /* The actual numerical result */
+ display: string, /* The equation displayed */
+ number: string /* Current number being typed */
+};
+
+type action =
+ | BackspaceKeyPressed
+ | ClearKeyPressed(bool) /* true = AC pressed */
+ | DotKeyPressed
+ | NumberKeyPressed(string)
+ | OperationKeyPressed(operator)
+ | PlusMinusKeyPressed
+ | ResultKeyPressed;
+
+let eval = (state, newOp) => {
+ /* Figure out what the string for the next operation will be */
+ let newOpString =
+ switch (newOp) {
+ | `Nop => ""
+ | `Add => "+"
+ | `Sub => "-"
+ | `Mul => "×"
+ | `Div => "÷"
+ };
+ /* Split the current display on ! and get everything after (to clear errors) */
+ let partitionedDisplay = String.split_on_char('!', state.display);
+ let display =
+ List.nth(partitionedDisplay, List.length(partitionedDisplay) - 1);
+ let (newDisplay, newResult) =
+ switch (state.operator) {
+ | #operator when state.number == "" => (
+ "Error: Can't evaluate binary operator without input!",
+ state.result,
+ )
+ | `Nop => (state.number ++ newOpString, float_of_string(state.number))
+ | `Add => (
+ display ++ state.number ++ newOpString,
+ state.result +. float_of_string(state.number),
+ )
+ | `Sub => (
+ display ++ state.number ++ newOpString,
+ state.result -. float_of_string(state.number),
+ )
+ | `Mul => (
+ display ++ state.number ++ newOpString,
+ state.result *. float_of_string(state.number),
+ )
+ | `Div =>
+ if (float_of_string(state.number) != 0.) {
+ (
+ display ++ state.number ++ newOpString,
+ state.result /. float_of_string(state.number),
+ );
+ } else {
+ ("Error: Divide by zero!", state.result);
+ }
+ };
+ (newResult, newDisplay);
+};
+
+let reducer = (state, action) =>
+ switch (action) {
+ | BackspaceKeyPressed =>
+ state.number == "" ?
+ state :
+ {
+ ...state,
+ number: String.sub(state.number, 0, String.length(state.number) - 1),
+ }
+ | ClearKeyPressed(ac) =>
+ ac ?
+ {operator: `Nop, result: 0., display: "", number: ""} :
+ {...state, number: ""}
+ | DotKeyPressed =>
+ String.contains(state.number, '.') ?
+ state : {...state, number: state.number ++ "."}
+ | NumberKeyPressed(n) => {...state, number: state.number ++ n}
+ | OperationKeyPressed(o) =>
+ let (result, display) = eval(state, o);
+ {operator: o, result, display, number: ""};
+ | PlusMinusKeyPressed =>
+ if (state.number != "" && state.number.[0] == '-') {
+ {
+ ...state,
+ number: String.sub(state.number, 1, String.length(state.number) - 1),
+ };
+ } else {
+ {...state, number: "-" ++ state.number};
+ }
+ | ResultKeyPressed =>
+ let (result, display) = eval(state, `Nop);
+ {operator: `Nop, result, display, number: showFloat(result)};
+ };
+
+module Calculator = (
+ val component((render, ~window, ~children, ()) =>
+ render(
+ () => {
+ let ({display, number, _}, dispatch) =
+ useReducer(
+ reducer,
+ {operator: `Nop, result: 0., display: "", number: ""},
+ );
+
+ let respondToKeys = e => switch(e.key) {
+ | Key.KEY_BACKSPACE =>
+ dispatch(BackspaceKeyPressed)
+
+ | Key.KEY_C when e.ctrlKey => dispatch(ClearKeyPressed(true))
+ | Key.KEY_C => dispatch(ClearKeyPressed(false))
+
+ /* + key */
+ | Key.KEY_EQUAL when e.shiftKey => dispatch(OperationKeyPressed(`Add))
+ | Key.KEY_MINUS when e.ctrlKey => dispatch(PlusMinusKeyPressed)
+ | Key.KEY_MINUS => dispatch(OperationKeyPressed(`Sub))
+ /* * key */
+ | Key.KEY_8 when e.shiftKey => dispatch(OperationKeyPressed(`Mul))
+ | Key.KEY_SLASH => dispatch(OperationKeyPressed(`Div))
+ | Key.KEY_PERIOD => dispatch(DotKeyPressed)
+ | Key.KEY_EQUAL => dispatch(ResultKeyPressed)
+
+ | Key.KEY_0 => dispatch(NumberKeyPressed("0"))
+ | Key.KEY_1 => dispatch(NumberKeyPressed("1"))
+ | Key.KEY_2 => dispatch(NumberKeyPressed("2"))
+ | Key.KEY_3 => dispatch(NumberKeyPressed("3"))
+ | Key.KEY_4 => dispatch(NumberKeyPressed("4"))
+ | Key.KEY_5 => dispatch(NumberKeyPressed("5"))
+ | Key.KEY_6 => dispatch(NumberKeyPressed("6"))
+ | Key.KEY_7 => dispatch(NumberKeyPressed("7"))
+ | Key.KEY_8 => dispatch(NumberKeyPressed("8"))
+ | Key.KEY_9 => dispatch(NumberKeyPressed("9"))
+
+ | _ => ()
+ };
+ /* TODO: Pretty sure this isn't supposed to go in the render() function.
+ Seems to cause lag the more times we re-render, so I guess this is
+ subscribing a ton of times and never unsubscribing. */
+ useEffect(() => {
+ let unsubscribe = Event.subscribe(window.onKeyDown, respondToKeys);
+ unsubscribe;
+ });
+
+
+
+
+
+
+ dispatch(NumberKeyPressed("7"))}
+ />
+ dispatch(NumberKeyPressed("8"))}
+ />
+ dispatch(NumberKeyPressed("9"))}
+ />
+ dispatch(OperationKeyPressed(`Div))}
+ />
+
+
+ dispatch(NumberKeyPressed("4"))}
+ />
+ dispatch(NumberKeyPressed("5"))}
+ />
+ dispatch(NumberKeyPressed("6"))}
+ />
+ dispatch(OperationKeyPressed(`Mul))}
+ />
+
+
+ dispatch(NumberKeyPressed("1"))}
+ />
+ dispatch(NumberKeyPressed("2"))}
+ />
+ dispatch(NumberKeyPressed("3"))}
+ />
+ dispatch(OperationKeyPressed(`Sub))}
+ />
+
+
+ dispatch(DotKeyPressed)} />
+ dispatch(NumberKeyPressed("0"))}
+ />
+ dispatch(ResultKeyPressed)}
+ />
+ dispatch(OperationKeyPressed(`Add))}
+ />
+
+ ;
+ },
+ ~children,
+ )
+ )
+);
+
+let init = app => {
+ let window = App.createWindow(app, "Revery Calculator");
+
+ let render = () => {
+ ;
+ };
+
+ UI.start(window, render);
+};
+
+App.start(init);
diff --git a/examples/FontAwesome5FreeSolid.otf b/examples/FontAwesome5FreeSolid.otf
new file mode 100644
index 000000000..80c80dae6
Binary files /dev/null and b/examples/FontAwesome5FreeSolid.otf differ
diff --git a/examples/Hello.re b/examples/Hello.re
index 6c5305f6c..415855d16 100644
--- a/examples/Hello.re
+++ b/examples/Hello.re
@@ -130,10 +130,8 @@ module SimpleButton = (
);
let textContent = "Click me: " ++ string_of_int(count);
-
-
- textContent
-
+
+ textContent
;
},
~children,
diff --git a/examples/dune b/examples/dune
index 7ff834265..a05804c97 100644
--- a/examples/dune
+++ b/examples/dune
@@ -1,8 +1,8 @@
(executables
- (names Hello Autocomplete Flexbox Border Boxshadow DefaultButton Overflow)
+ (names Hello Autocomplete Flexbox Border Boxshadow DefaultButton Calculator Overflow)
(preprocess (pps lwt_ppx))
(package Revery)
- (public_names Hello Autocomplete Flexbox Border Boxshadow DefaultButton Overflow)
+ (public_names Hello Autocomplete Flexbox Border Boxshadow DefaultButton Calculator Overflow)
(js_of_ocaml)
(libraries
js_of_ocaml
@@ -13,4 +13,4 @@
(install
(section bin)
(package Revery)
- (files Roboto-Regular.ttf binary.dat index.html Hello.bc.js gl-matrix-min.js outrun-logo.png))
+ (files Roboto-Regular.ttf FontAwesome5FreeSolid.otf binary.dat index.html Hello.bc.js gl-matrix-min.js outrun-logo.png))
diff --git a/src/UI/Hooks.re b/src/UI/Hooks.re
index 10494e0d0..488df568f 100644
--- a/src/UI/Hooks.re
+++ b/src/UI/Hooks.re
@@ -2,6 +2,8 @@
open UiReact;
+let useEffect = useEffect;
+
let useAnimation =
(v: Animated.animationValue, opts: Animated.animationOptions) => {
let (currentV, _set) = useState(v);
diff --git a/src/UI/Style.re b/src/UI/Style.re
index 4d8b70c05..c5a0e340d 100644
--- a/src/UI/Style.re
+++ b/src/UI/Style.re
@@ -80,6 +80,87 @@ type t = {
cursor: option(MouseCursors.t),
};
+let extend = (
+ style: t,
+ ~backgroundColor=style.backgroundColor,
+ ~color=style.color,
+ ~width=style.width,
+ ~height=style.height,
+ ~flexBasis=style.flexBasis,
+ ~flexDirection=style.flexDirection,
+ ~flexGrow=style.flexGrow,
+ ~flexShrink=style.flexShrink,
+ ~alignItems=style.alignItems,
+ ~justifyContent=style.justifyContent,
+ ~position=style.position,
+ ~top=style.top,
+ ~bottom=style.bottom,
+ ~left=style.left,
+ ~right=style.right,
+ ~fontFamily=style.fontFamily,
+ ~fontSize=style.fontSize,
+ ~marginTop=style.marginTop,
+ ~marginLeft=style.marginLeft,
+ ~marginRight=style.marginRight,
+ ~marginBottom=style.marginBottom,
+ ~margin=style.margin,
+ ~marginVertical=style.marginVertical,
+ ~marginHorizontal=style.marginHorizontal,
+ ~borderTop=style.borderTop,
+ ~borderLeft=style.borderLeft,
+ ~borderRight=style.borderRight,
+ ~borderBottom=style.borderBottom,
+ ~border=style.border,
+ ~borderHorizontal=style.borderHorizontal,
+ ~borderVertical=style.borderVertical,
+ ~transform=style.transform,
+ ~opacity=style.opacity,
+ ~overflow=style.overflow,
+ ~boxShadow=style.boxShadow,
+ ~cursor = ?,
+ _unit: unit,
+) => {
+ let ret: t = {
+ backgroundColor,
+ color,
+ width,
+ height,
+ flexBasis,
+ flexDirection,
+ flexGrow,
+ flexShrink,
+ justifyContent,
+ alignItems,
+ position,
+ top,
+ bottom,
+ left,
+ right,
+ fontFamily,
+ fontSize,
+ transform,
+ marginTop,
+ marginLeft,
+ marginRight,
+ marginBottom,
+ margin,
+ marginVertical,
+ marginHorizontal,
+ borderTop,
+ borderLeft,
+ borderRight,
+ borderBottom,
+ border,
+ borderHorizontal,
+ borderVertical,
+ opacity,
+ overflow,
+ boxShadow,
+ cursor,
+ };
+ ret;
+}
+
let make =
(
~backgroundColor: Color.t=Colors.transparentBlack,
diff --git a/src/UI_Components/Clickable.re b/src/UI_Components/Clickable.re
index fd5f7262b..4fba34b14 100644
--- a/src/UI_Components/Clickable.re
+++ b/src/UI_Components/Clickable.re
@@ -13,7 +13,7 @@ let noop = () => ();
/* The 'include' here makes the component available at the top level */
include (
- val component((render, ~onClick: clickFunction=noop, ~children, ()) =>
+ val component((render, ~style=Style.defaultStyle, ~onClick: clickFunction=noop, ~children, ()) =>
render(
() => {
let (opacity, setOpacity) = useState(0.8);
@@ -30,10 +30,14 @@ include (
onClick();
};
- let style =
- Style.make(~opacity, ~cursor=MouseCursors.pointer, ());
+ let style2 = Style.extend(
+ style,
+ ~opacity,
+ ~cursor=MouseCursors.pointer,
+ ()
+ );
- ...children ;
+ ...children ;
},
~children,
)