From aa2e57455eb7e6d81b88e5d4ac38fda7367a8e3a Mon Sep 17 00:00:00 2001 From: Eric P Date: Sat, 23 May 2020 12:49:10 -0700 Subject: [PATCH 1/9] Add isFocused --- src/UI/Focus.re | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/UI/Focus.re b/src/UI/Focus.re index b28f0d428..d2c021895 100644 --- a/src/UI/Focus.re +++ b/src/UI/Focus.re @@ -29,15 +29,15 @@ let focus = (node: Node.node) => { focused := Some({handler: node#handleEvent, id: node#getInternalId()}); }; +let isFocused = (node: Node.node) => + switch (focused^) { + | Some({id, _}) => node#getInternalId() === id + | None => false + }; + /* TODO perform checks if a node can be focused ? */ let dispatch = (node: Node.node) => - switch (focused^) { - | Some({id, _}) => - if (node#getInternalId() === id) { - (); - } else { - loseFocus(); - focus(node); - } - | None => focus(node) + if (!isFocused(node)) { + loseFocus(); + focus(node); }; From 7d5542f79b03fe8e3e570b118734940447fe76b2 Mon Sep 17 00:00:00 2001 From: Eric P Date: Sat, 23 May 2020 13:10:59 -0700 Subject: [PATCH 2/9] Check focus with Focus.isFocused --- src/UI_Components/Input.re | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/UI_Components/Input.re b/src/UI_Components/Input.re index 25cad9589..4e8ecaf1c 100644 --- a/src/UI_Components/Input.re +++ b/src/UI_Components/Input.re @@ -48,14 +48,11 @@ module Cursor = { }; type state = { - isFocused: bool, // TODO: Violates single source of truth value: string, cursorPosition: int, }; type action = - | Focus - | Blur | TextInput(string, int); let getStringParts = (index, str) => { @@ -108,9 +105,7 @@ let addCharacter = (word, char, index) => { let reducer = (action, state) => switch (action) { - | Focus => {...state, isFocused: true} - | Blur => {...state, isFocused: false} - | TextInput(value, cursorPosition) => {...state, value, cursorPosition} + | TextInput(value, cursorPosition) => {value, cursorPosition} }; module Constants = { @@ -212,7 +207,6 @@ let%component make = let%hook (state, dispatch) = Hooks.reducer( ~initialState={ - isFocused: false, value: Option.value(value, ~default=""), cursorPosition: Option.value(cursorPosition, ~default=0), }, @@ -248,8 +242,16 @@ let%component make = dimensions.width |> int_of_float; }; + let%hook clickableRef = Hooks.ref(None); + let isFocused = () => { + switch (clickableRef^) { + | Some(node) => Focus.isFocused(node) + | None => false + }; + }; + let%hook (cursorOpacity, resetCursor) = - Cursor.use(~interval=Time.ms(500), ~isFocused=state.isFocused); + Cursor.use(~interval=Time.ms(500), ~isFocused=isFocused()); let () = { let cursorOffset = @@ -275,14 +277,12 @@ let%component make = resetCursor(); onFocus(); Sdl2.TextInput.start(); - dispatch(Focus); }; let handleBlur = () => { resetCursor(); onBlur(); Sdl2.TextInput.stop(); - dispatch(Blur); }; // TODO:This ought to be in the reducer, but since reducer calls are deferred @@ -350,6 +350,13 @@ let%component make = }; }; + let handleRef = node => { + clickableRef := Some(node); + if (autofocus) { + Focus.focus(node); + }; + }; + let cursor = () => { let (startStr, _) = getStringParts(cursorPosition, value); let textWidth = measureTextWidth(startStr); @@ -392,7 +399,7 @@ let%component make = From 993d39e31ae161f8fd8c2fc383cda5d42c74c93f Mon Sep 17 00:00:00 2001 From: Eric P Date: Sun, 24 May 2020 11:54:11 -0700 Subject: [PATCH 3/9] Fix focus not blurring first --- src/UI/Focus.re | 8 ++++---- src/UI/Mouse.re | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/UI/Focus.re b/src/UI/Focus.re index d2c021895..9a6d2cf25 100644 --- a/src/UI/Focus.re +++ b/src/UI/Focus.re @@ -23,7 +23,7 @@ let loseFocus = () => { // If there is an active window, with text input active, turn off text input }; -let focus = (node: Node.node) => { +let focusWithoutBlur = (node: Node.node) => { Log.trace("focus()"); node#handleEvent(Focus); focused := Some({handler: node#handleEvent, id: node#getInternalId()}); @@ -31,13 +31,13 @@ let focus = (node: Node.node) => { let isFocused = (node: Node.node) => switch (focused^) { - | Some({id, _}) => node#getInternalId() === id + | Some({id, _}) => node#getInternalId() == id | None => false }; /* TODO perform checks if a node can be focused ? */ -let dispatch = (node: Node.node) => +let focus = (node: Node.node) => if (!isFocused(node)) { loseFocus(); - focus(node); + focusWithoutBlur(node); }; diff --git a/src/UI/Mouse.re b/src/UI/Mouse.re index 1796367d8..7cf9ee836 100644 --- a/src/UI/Mouse.re +++ b/src/UI/Mouse.re @@ -287,7 +287,7 @@ let dispatch = if (isMouseDownEv(eventToSend)) { switch (getFirstFocusable(node, mouseX, mouseY)) { - | Some(node) => Focus.dispatch(node) + | Some(node) => Focus.focus(node) | None => Focus.loseFocus() }; }; From 192444245ed97e90fe26b267159948fb5a3c6a3e Mon Sep 17 00:00:00 2001 From: Eric P Date: Sun, 24 May 2020 12:51:58 -0700 Subject: [PATCH 4/9] Switch from reducer to state hook --- src/UI_Components/Input.re | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/UI_Components/Input.re b/src/UI_Components/Input.re index 4e8ecaf1c..bf5dae299 100644 --- a/src/UI_Components/Input.re +++ b/src/UI_Components/Input.re @@ -103,11 +103,6 @@ let addCharacter = (word, char, index) => { (startStr ++ char ++ endStr, String.length(startStr) + 1); }; -let reducer = (action, state) => - switch (action) { - | TextInput(value, cursorPosition) => {value, cursorPosition} - }; - module Constants = { let defaultHeight = 50; let defaultWidth = 200; @@ -204,14 +199,11 @@ let%component make = ~isPassword=false, (), ) => { - let%hook (state, dispatch) = - Hooks.reducer( - ~initialState={ - value: Option.value(value, ~default=""), - cursorPosition: Option.value(cursorPosition, ~default=0), - }, - reducer, - ); + let%hook (state, setState) = + Hooks.state({ + value: Option.value(value, ~default=""), + cursorPosition: Option.value(cursorPosition, ~default=0), + }); let%hook textRef = Hooks.ref(None); let%hook scrollOffset = Hooks.ref(0); @@ -291,7 +283,7 @@ let%component make = // Refactor when https://github.com/briskml/brisk-reconciler/issues/54 has been fixed let update = (value, cursorPosition) => { onChange(value, cursorPosition); - dispatch(TextInput(value, cursorPosition)); + setState(_ => {value, cursorPosition}); }; let handleTextInput = (event: NodeEvents.textInputEventParams) => { From cc6f52c6351795ae4ef78c563dc76fd506dfa05b Mon Sep 17 00:00:00 2001 From: Eric P Date: Sun, 24 May 2020 12:54:55 -0700 Subject: [PATCH 5/9] Remove old reducer actions --- src/UI_Components/Input.re | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/UI_Components/Input.re b/src/UI_Components/Input.re index bf5dae299..ef411d754 100644 --- a/src/UI_Components/Input.re +++ b/src/UI_Components/Input.re @@ -52,9 +52,6 @@ type state = { cursorPosition: int, }; -type action = - | TextInput(string, int); - let getStringParts = (index, str) => { switch (index) { | 0 => ("", str) From d61321d3fbca073c664d7033cae5de35983d4c08 Mon Sep 17 00:00:00 2001 From: Eric P Date: Mon, 25 May 2020 12:18:39 -0700 Subject: [PATCH 6/9] Add interface for Focus --- src/UI/Focus.re | 13 ++++++------- src/UI/Focus.rei | 15 +++++++++++++++ src/UI/Keyboard.re | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/UI/Focus.rei diff --git a/src/UI/Focus.re b/src/UI/Focus.re index 9a6d2cf25..6dbe7685e 100644 --- a/src/UI/Focus.re +++ b/src/UI/Focus.re @@ -10,6 +10,8 @@ let focused = ref(None); module Log = (val Log.withNamespace("Revery.UI.Focus")); +let getFocused = () => focused^; + /* Should happen when user clicks anywhere where no focusable node exists */ let loseFocus = () => { Log.trace("loseFocus()"); @@ -23,12 +25,6 @@ let loseFocus = () => { // If there is an active window, with text input active, turn off text input }; -let focusWithoutBlur = (node: Node.node) => { - Log.trace("focus()"); - node#handleEvent(Focus); - focused := Some({handler: node#handleEvent, id: node#getInternalId()}); -}; - let isFocused = (node: Node.node) => switch (focused^) { | Some({id, _}) => node#getInternalId() == id @@ -39,5 +35,8 @@ let isFocused = (node: Node.node) => let focus = (node: Node.node) => if (!isFocused(node)) { loseFocus(); - focusWithoutBlur(node); + + Log.trace("focus()"); + node#handleEvent(Focus); + focused := Some({handler: node#handleEvent, id: node#getInternalId()}); }; diff --git a/src/UI/Focus.rei b/src/UI/Focus.rei new file mode 100644 index 000000000..90aab7703 --- /dev/null +++ b/src/UI/Focus.rei @@ -0,0 +1,15 @@ +open NodeEvents; + +type active = { + handler: event => unit, + id: int, +}; +type focused = ref(option(active)); + +let getFocused: unit => option(active); + +let loseFocus: unit => unit; + +let isFocused: Node.node => bool; + +let focus: Node.node => unit; diff --git a/src/UI/Keyboard.re b/src/UI/Keyboard.re index 4712a4adc..af665c225 100644 --- a/src/UI/Keyboard.re +++ b/src/UI/Keyboard.re @@ -2,7 +2,7 @@ open Revery_Core; open NodeEvents; let dispatch = (event: Revery_Core.Events.internalKeyboardEvent) => { - let focused = Focus.focused^; + let focused = Focus.getFocused(); switch (focused) { | None => () From 1e80aec3f32da1eb739405985211355529816bca Mon Sep 17 00:00:00 2001 From: Eric P Date: Mon, 25 May 2020 12:32:58 -0700 Subject: [PATCH 7/9] Stick with reducer Using reducer instead of state hook because of possible bug with state hooks (https://github.com/briskml/brisk-reconciler/issues/74). --- src/UI_Components/Input.re | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/UI_Components/Input.re b/src/UI_Components/Input.re index ef411d754..fe38616fa 100644 --- a/src/UI_Components/Input.re +++ b/src/UI_Components/Input.re @@ -52,6 +52,14 @@ type state = { cursorPosition: int, }; +type action = + | TextInput(string, int); + +let reducer = (action, state) => + switch (action) { + | TextInput(value, cursorPosition) => {value, cursorPosition} + }; + let getStringParts = (index, str) => { switch (index) { | 0 => ("", str) @@ -196,11 +204,18 @@ let%component make = ~isPassword=false, (), ) => { - let%hook (state, setState) = - Hooks.state({ - value: Option.value(value, ~default=""), - cursorPosition: Option.value(cursorPosition, ~default=0), - }); + // TODO: This ought to be a state hook not reducer but + // a bug with stale hook setters might happen. + // + // Refactor when https://github.com/briskml/brisk-reconciler/issues/74 has been fixed + let%hook (state, dispatch) = + Hooks.reducer( + ~initialState={ + value: Option.value(value, ~default=""), + cursorPosition: Option.value(cursorPosition, ~default=0), + }, + reducer, + ); let%hook textRef = Hooks.ref(None); let%hook scrollOffset = Hooks.ref(0); @@ -280,7 +295,7 @@ let%component make = // Refactor when https://github.com/briskml/brisk-reconciler/issues/54 has been fixed let update = (value, cursorPosition) => { onChange(value, cursorPosition); - setState(_ => {value, cursorPosition}); + dispatch(TextInput(value, cursorPosition)); }; let handleTextInput = (event: NodeEvents.textInputEventParams) => { From 8498f65668b5d97e3f72513a311ba9ba09f145ca Mon Sep 17 00:00:00 2001 From: Eric P Date: Tue, 26 May 2020 12:33:40 -0700 Subject: [PATCH 8/9] Use Focus.dispatch for events --- src/UI/Focus.re | 8 ++++-- src/UI/Focus.rei | 10 ++------ src/UI/Keyboard.re | 62 +++++++++++++++++++++------------------------- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/UI/Focus.re b/src/UI/Focus.re index 6dbe7685e..f734af4c5 100644 --- a/src/UI/Focus.re +++ b/src/UI/Focus.re @@ -10,8 +10,6 @@ let focused = ref(None); module Log = (val Log.withNamespace("Revery.UI.Focus")); -let getFocused = () => focused^; - /* Should happen when user clicks anywhere where no focusable node exists */ let loseFocus = () => { Log.trace("loseFocus()"); @@ -40,3 +38,9 @@ let focus = (node: Node.node) => node#handleEvent(Focus); focused := Some({handler: node#handleEvent, id: node#getInternalId()}); }; + +let dispatch = event => + switch (focused^) { + | Some({handler, _}) => handler(event) + | None => () + }; diff --git a/src/UI/Focus.rei b/src/UI/Focus.rei index 90aab7703..3e0c4aa17 100644 --- a/src/UI/Focus.rei +++ b/src/UI/Focus.rei @@ -1,15 +1,9 @@ open NodeEvents; -type active = { - handler: event => unit, - id: int, -}; -type focused = ref(option(active)); - -let getFocused: unit => option(active); - let loseFocus: unit => unit; let isFocused: Node.node => bool; let focus: Node.node => unit; + +let dispatch: event => unit; diff --git a/src/UI/Keyboard.re b/src/UI/Keyboard.re index af665c225..1ff686bd6 100644 --- a/src/UI/Keyboard.re +++ b/src/UI/Keyboard.re @@ -2,39 +2,33 @@ open Revery_Core; open NodeEvents; let dispatch = (event: Revery_Core.Events.internalKeyboardEvent) => { - let focused = Focus.getFocused(); - - switch (focused) { - | None => () - | Some({handler, _}) => - switch (event) { - | InternalTextEditEvent({text, start, length}) => - handler(TextEdit({text, start, length})) - | InternalTextInputEvent(e) => handler(TextInput({text: e.text})) - | InternalKeyUpEvent(e) => - handler( - KeyUp({ - keycode: e.keycode, - scancode: e.scancode, - repeat: e.repeat, - keymod: e.keymod, - ctrlKey: Key.Keymod.isControlDown(e.keymod), - altKey: Key.Keymod.isAltDown(e.keymod), - shiftKey: Key.Keymod.isShiftDown(e.keymod), - }), - ) - | InternalKeyDownEvent(e) => - handler( - KeyDown({ - keycode: e.keycode, - scancode: e.scancode, - repeat: e.repeat, - keymod: e.keymod, - ctrlKey: Key.Keymod.isControlDown(e.keymod), - altKey: Key.Keymod.isAltDown(e.keymod), - shiftKey: Key.Keymod.isShiftDown(e.keymod), - }), - ) - } + switch (event) { + | InternalTextEditEvent({text, start, length}) => + Focus.dispatch(TextEdit({text, start, length})) + | InternalTextInputEvent(e) => Focus.dispatch(TextInput({text: e.text})) + | InternalKeyUpEvent(e) => + Focus.dispatch( + KeyUp({ + keycode: e.keycode, + scancode: e.scancode, + repeat: e.repeat, + keymod: e.keymod, + ctrlKey: Key.Keymod.isControlDown(e.keymod), + altKey: Key.Keymod.isAltDown(e.keymod), + shiftKey: Key.Keymod.isShiftDown(e.keymod), + }), + ) + | InternalKeyDownEvent(e) => + Focus.dispatch( + KeyDown({ + keycode: e.keycode, + scancode: e.scancode, + repeat: e.repeat, + keymod: e.keymod, + ctrlKey: Key.Keymod.isControlDown(e.keymod), + altKey: Key.Keymod.isAltDown(e.keymod), + shiftKey: Key.Keymod.isShiftDown(e.keymod), + }), + ) }; }; From 0d20db2ced2d0bb40e5a9ab526fe5c0897772136 Mon Sep 17 00:00:00 2001 From: Eric P Date: Tue, 26 May 2020 14:48:53 -0700 Subject: [PATCH 9/9] Separate function for internal to external event --- src/UI/Focus.re | 2 +- src/UI/Keyboard.re | 52 ++++++++++++++++++++------------------ src/UI_Components/Input.re | 2 +- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/UI/Focus.re b/src/UI/Focus.re index f734af4c5..187aa2241 100644 --- a/src/UI/Focus.re +++ b/src/UI/Focus.re @@ -6,7 +6,7 @@ type active = { id: int, }; type focused = ref(option(active)); -let focused = ref(None); +let focused: focused = ref(None); module Log = (val Log.withNamespace("Revery.UI.Focus")); diff --git a/src/UI/Keyboard.re b/src/UI/Keyboard.re index 1ff686bd6..cb0f9e5a0 100644 --- a/src/UI/Keyboard.re +++ b/src/UI/Keyboard.re @@ -1,34 +1,36 @@ open Revery_Core; open NodeEvents; -let dispatch = (event: Revery_Core.Events.internalKeyboardEvent) => { +let internalToExternalEvent = + (event: Revery_Core.Events.internalKeyboardEvent) => switch (event) { | InternalTextEditEvent({text, start, length}) => - Focus.dispatch(TextEdit({text, start, length})) - | InternalTextInputEvent(e) => Focus.dispatch(TextInput({text: e.text})) + TextEdit({text, start, length}) + | InternalTextInputEvent(e) => TextInput({text: e.text}) | InternalKeyUpEvent(e) => - Focus.dispatch( - KeyUp({ - keycode: e.keycode, - scancode: e.scancode, - repeat: e.repeat, - keymod: e.keymod, - ctrlKey: Key.Keymod.isControlDown(e.keymod), - altKey: Key.Keymod.isAltDown(e.keymod), - shiftKey: Key.Keymod.isShiftDown(e.keymod), - }), - ) + KeyUp({ + keycode: e.keycode, + scancode: e.scancode, + repeat: e.repeat, + keymod: e.keymod, + ctrlKey: Key.Keymod.isControlDown(e.keymod), + altKey: Key.Keymod.isAltDown(e.keymod), + shiftKey: Key.Keymod.isShiftDown(e.keymod), + }) | InternalKeyDownEvent(e) => - Focus.dispatch( - KeyDown({ - keycode: e.keycode, - scancode: e.scancode, - repeat: e.repeat, - keymod: e.keymod, - ctrlKey: Key.Keymod.isControlDown(e.keymod), - altKey: Key.Keymod.isAltDown(e.keymod), - shiftKey: Key.Keymod.isShiftDown(e.keymod), - }), - ) + KeyUp({ + keycode: e.keycode, + scancode: e.scancode, + repeat: e.repeat, + keymod: e.keymod, + ctrlKey: Key.Keymod.isControlDown(e.keymod), + altKey: Key.Keymod.isAltDown(e.keymod), + shiftKey: Key.Keymod.isShiftDown(e.keymod), + }) }; + +let dispatch = (event: Revery_Core.Events.internalKeyboardEvent) => { + let eventToSend = internalToExternalEvent(event); + + Focus.dispatch(eventToSend); }; diff --git a/src/UI_Components/Input.re b/src/UI_Components/Input.re index fe38616fa..dbc76dce5 100644 --- a/src/UI_Components/Input.re +++ b/src/UI_Components/Input.re @@ -55,7 +55,7 @@ type state = { type action = | TextInput(string, int); -let reducer = (action, state) => +let reducer = (action, _state) => switch (action) { | TextInput(value, cursorPosition) => {value, cursorPosition} };