From a3b2218c571dd750580a77f5c0a83afed313f2b2 Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 9 Jan 2020 21:49:26 +0100 Subject: [PATCH 01/15] add Components project --- src/Components/dune | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Components/dune b/src/Components/dune index 82b8c58f30..e99849f6d6 100644 --- a/src/Components/dune +++ b/src/Components/dune @@ -1,5 +1,15 @@ (library (name Oni_Components) (public_name Oni2.components) - (libraries editor-core-types Oni2.core Revery) - (preprocess (pps ppx_deriving_yojson ppx_deriving.show brisk-reconciler.ppx))) + (preprocess (pps brisk-reconciler.ppx)) + (libraries + str + bigarray + zed_oni + lwt + lwt.unix + Oni2.core + Rench + Revery + editor-core-types + )) From 62ad7c38a65624d7050a4cba088f63895170e15f Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 9 Jan 2020 21:49:58 +0100 Subject: [PATCH 02/15] add ContextMenu --- src/Components/ContextMenu.re | 258 +++++++++++++++++++++++++++++++ src/Components/ContextMenu.rei | 52 +++++++ src/Components/Oni_Components.re | 1 + 3 files changed, 311 insertions(+) create mode 100644 src/Components/ContextMenu.re create mode 100644 src/Components/ContextMenu.rei create mode 100644 src/Components/Oni_Components.re diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re new file mode 100644 index 0000000000..55320ff4fa --- /dev/null +++ b/src/Components/ContextMenu.re @@ -0,0 +1,258 @@ +open Oni_Core; + +open Revery; +open Revery.UI; +open Revery.UI.Components; + +module Option = Utility.Option; + +module Constants = { + let menuWidth = 200; + // let maxMenuHeight = 600; +}; + +// TYPES + +type identity = int; + +type item('data) = { + label: string, + // icon: option(IconTheme.IconDefinition.t), + data: 'data, +}; + +type t('data) = { + identity, + x: int, + y: int, + originX: [ | `Left | `Middle | `Right], + originY: [ | `Top | `Middle | `Bottom], + items: list(item('data)), +}; + +let create = (~originX=`Left, ~originY=`Bottom, identity, items) => { + identity, + x: 0, + y: 0, + originX, + originY, + items, +}; + +// MENUITEM + +module MenuItem = { + module Constants = { + let fontSize = 12; + }; + + module Styles = { + open Style; + + let bg = (~theme: Theme.t, ~isFocused) => + isFocused ? theme.menuSelectionBackground : theme.menuBackground; + + let container = (~theme, ~isFocused) => [ + padding(10), + flexDirection(`Row), + backgroundColor(bg(~theme, ~isFocused)), + ]; + + // let icon = fgColor => [ + // fontFamily("seti.ttf"), + // fontSize(Constants.fontSize), + // marginRight(10), + // color(fgColor), + // ]; + + let label = (~font: UiFont.t, ~theme: Theme.t, ~isFocused) => [ + fontFamily(font.fontFile), + textOverflow(`Ellipsis), + fontSize(Constants.fontSize), + color(theme.menuForeground), + backgroundColor(bg(~theme, ~isFocused)), + ]; + + let clickable = [cursor(Revery.MouseCursors.pointer)]; + }; + + let make: + 'data. + ( + ~item: item('data), + ~theme: Theme.t, + ~font: UiFont.t, + ~onClick: unit => unit, + unit + ) => + React.element(React.node) + = + (~item, ~theme, ~font, ~onClick, ()) => { + let isFocused = false; + + // let iconView = + // switch (item.icon) { + // | Some(icon) => + // IconTheme.IconDefinition.( + // + // ) + + // | None => + // }; + + let labelView = { + let style = Styles.label(~font, ~theme, ~isFocused); + ; + }; + + + // onMouseOver={_ => setIsFocused(_ => true)} + // onMouseOut={_ => setIsFocused(_ => false)} + + + // iconView + labelView + ; + }; +}; + +// MENU + +module Menu = { + module Styles = { + open Style; + + let container = (~x, ~y, ~theme: Theme.t) => [ + position(`Absolute), + top(y), + left(x), + backgroundColor(theme.menuBackground), + color(theme.menuForeground), + width(Constants.menuWidth), + ]; + }; + + let component = React.Expert.component("Menu"); + let make = (~model, ~theme, ~font, ~onItemSelect, ()) => + component(hooks => { + let ((maybeRef, setRef), hooks) = Hooks.state(None, hooks); + let {x, y, originX, originY, items, _} = model; + + let height = + switch (maybeRef) { + | Some((node: node)) => node#measurements().height + | None => List.length(model.items) * 20 + }; + let width = Constants.menuWidth; + + let x = + switch (originX) { + | `Left => x + | `Middle => x - width / 2 + | `Right => x - width + }; + + let y = + switch (originY) { + | `Top => y - height + | `Middle => y - height / 2 + | `Bottom => y + }; + + ( + + setRef(_ => Some(node))}> + {items + |> List.map(item => { + let onClick = () => onItemSelect(item); + ; + }) + |> React.listToElement} + + , + hooks, + ); + }); +}; + +// OVERLAY + +module Overlay = { + module Styles = { + open Style; + + let overlay = [ + position(`Absolute), + top(0), + bottom(0), + left(0), + right(0), + pointerEvents(`Allow), + ]; + }; + + let make = (~model, ~theme, ~font, ~onOverlayClick, ~onItemSelect, ()) => { + + + ; + }; +}; + +// IDENTITY + +module Identity = { + let generateId = { + let lastId = ref(0); + () => { + lastId := lastId^ + 1; + lastId^; + }; + }; + + let component = React.Expert.component("Anchor"); + let make = (~children as render, ()) => + component(hooks => { + let ((id, _), hooks) = Hooks.state(generateId(), hooks); + + (render(id), hooks); + }); +}; + +// ANCHOR + +module Anchor = { + let component = React.Expert.component("Anchor"); + let make = (~identity, ~model as maybeModel, ~onUpdate, ()) => + component(hooks => { + let ((maybeRef, setRef), hooks) = Hooks.ref(None, hooks); + + switch (maybeModel, maybeRef) { + | (Some(model), Some(node)) => + if (model.identity == identity) { + let (x, y, _, _) = + Math.BoundingBox2d.getBounds(node#getBoundingBox()); + let (x, y) = (int_of_float(x), int_of_float(y)); + + if (x != model.x || y != model.y) { + onUpdate({...model, x, y}); + }; + } + | _ => () + }; + + ( setRef(Some(node))} />, hooks); + }); +}; diff --git a/src/Components/ContextMenu.rei b/src/Components/ContextMenu.rei new file mode 100644 index 0000000000..bc1e3e335b --- /dev/null +++ b/src/Components/ContextMenu.rei @@ -0,0 +1,52 @@ +open Oni_Core; + +open Revery.UI; + +type identity; + +type item('data) = { + label: string, + // icon: option(IconTheme.IconDefinition.t), + data: 'data, +}; + +type t('data); + +let create: + ( + ~originX: [ | `Left | `Middle | `Right]=?, + ~originY: [ | `Top | `Middle | `Bottom]=?, + identity, + list(item('data)) + ) => + t('data); + +module Overlay: { + let make: + ( + ~model: t('data), + ~theme: Theme.t, + ~font: UiFont.t, + ~onOverlayClick: unit => unit, + ~onItemSelect: item('data) => unit, + unit + ) => + React.element(React.node); +}; + +module Identity: { + let make: + (~children: identity => React.element(React.node), unit) => + React.element(React.node); +}; + +module Anchor: { + let make: + ( + ~identity: identity, + ~model: option(t('data)), + ~onUpdate: t('data) => unit, + unit + ) => + React.element(React.node); +}; diff --git a/src/Components/Oni_Components.re b/src/Components/Oni_Components.re new file mode 100644 index 0000000000..4bde20e742 --- /dev/null +++ b/src/Components/Oni_Components.re @@ -0,0 +1 @@ +module ContextMenu = ContextMenu; From d10d360b644578089496fc6d75667deb5e3e1a69 Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 08:13:16 +0100 Subject: [PATCH 03/15] use functor to generate identity instead of Identity component --- src/Components/ContextMenu.re | 163 ++++++++++++++++----------------- src/Components/ContextMenu.rei | 47 ++++------ src/Components/dune | 2 +- 3 files changed, 101 insertions(+), 111 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index 55320ff4fa..209ef060fd 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -13,16 +13,28 @@ module Constants = { // TYPES -type identity = int; +module Id: { + type t; + let create: unit => t; +} = { + type t = int; + + let lastId = ref(0); + let create = () => { + incr(lastId); + lastId^; + }; +}; +[@deriving show({with_path: false})] type item('data) = { label: string, // icon: option(IconTheme.IconDefinition.t), - data: 'data, + data: [@opaque] 'data, }; type t('data) = { - identity, + id: Id.t, x: int, y: int, originX: [ | `Left | `Middle | `Right], @@ -30,15 +42,6 @@ type t('data) = { items: list(item('data)), }; -let create = (~originX=`Left, ~originY=`Bottom, identity, items) => { - identity, - x: 0, - y: 0, - originX, - originY, - items, -}; - // MENUITEM module MenuItem = { @@ -72,8 +75,6 @@ module MenuItem = { color(theme.menuForeground), backgroundColor(bg(~theme, ~isFocused)), ]; - - let clickable = [cursor(Revery.MouseCursors.pointer)]; }; let make: @@ -108,14 +109,13 @@ module MenuItem = { ; }; - - // onMouseOver={_ => setIsFocused(_ => true)} - // onMouseOut={_ => setIsFocused(_ => false)} - - - // iconView - labelView - ; + + + // iconView + labelView + ; + // onMouseOut={_ => setIsFocused(_ => false)} + // onMouseOver={_ => setIsFocused(_ => true)} }; }; @@ -163,26 +163,27 @@ module Menu = { }; ( - - setRef(_ => Some(node))}> - {items - |> List.map(item => { - let onClick = () => onItemSelect(item); - ; - }) - |> React.listToElement} - - , + // TODO: BoxShadow apparently blocks mouse events. Figure out why before adding back + // + setRef(_ => Some(node))}> + {items + |> List.map(item => { + let onClick = () => onItemSelect(item); + ; + }) + |> React.listToElement} + , + // , hooks, ); }); @@ -211,48 +212,46 @@ module Overlay = { }; }; -// IDENTITY +module Make = (()) => { + let id = Id.create(); -module Identity = { - let generateId = { - let lastId = ref(0); - () => { - lastId := lastId^ + 1; - lastId^; - }; + let init = items => { + id, + x: 0, + y: 0, + originX: `Left, + originY: `Bottom, + items, }; - let component = React.Expert.component("Anchor"); - let make = (~children as render, ()) => - component(hooks => { - let ((id, _), hooks) = Hooks.state(generateId(), hooks); - - (render(id), hooks); - }); -}; - -// ANCHOR - -module Anchor = { - let component = React.Expert.component("Anchor"); - let make = (~identity, ~model as maybeModel, ~onUpdate, ()) => - component(hooks => { - let ((maybeRef, setRef), hooks) = Hooks.ref(None, hooks); - - switch (maybeModel, maybeRef) { - | (Some(model), Some(node)) => - if (model.identity == identity) { - let (x, y, _, _) = - Math.BoundingBox2d.getBounds(node#getBoundingBox()); - let (x, y) = (int_of_float(x), int_of_float(y)); - - if (x != model.x || y != model.y) { - onUpdate({...model, x, y}); - }; - } - | _ => () - }; + module Anchor = { + let component = React.Expert.component("Anchor"); + let make = + ( + ~model as maybeModel, + ~originX=`Left, + ~originY=`Bottom, + ~onUpdate, + (), + ) => + component(hooks => { + let ((maybeRef, setRef), hooks) = Hooks.ref(None, hooks); + + switch (maybeModel, maybeRef) { + | (Some(model), Some(node)) => + if (model.id == id) { + let (x, y, _, _) = + Math.BoundingBox2d.getBounds(node#getBoundingBox()); + let (x, y) = (int_of_float(x), int_of_float(y)); + + if (x != model.x || y != model.y) { + onUpdate({...model, x, y, originX, originY}); + }; + } + | _ => () + }; - ( setRef(Some(node))} />, hooks); - }); + ( setRef(Some(node))} />, hooks); + }); + }; }; diff --git a/src/Components/ContextMenu.rei b/src/Components/ContextMenu.rei index bc1e3e335b..e9450baeaa 100644 --- a/src/Components/ContextMenu.rei +++ b/src/Components/ContextMenu.rei @@ -2,25 +2,15 @@ open Oni_Core; open Revery.UI; -type identity; - +[@deriving show] type item('data) = { label: string, // icon: option(IconTheme.IconDefinition.t), - data: 'data, + data: [@opaque] 'data, }; type t('data); -let create: - ( - ~originX: [ | `Left | `Middle | `Right]=?, - ~originY: [ | `Top | `Middle | `Bottom]=?, - identity, - list(item('data)) - ) => - t('data); - module Overlay: { let make: ( @@ -34,19 +24,20 @@ module Overlay: { React.element(React.node); }; -module Identity: { - let make: - (~children: identity => React.element(React.node), unit) => - React.element(React.node); -}; - -module Anchor: { - let make: - ( - ~identity: identity, - ~model: option(t('data)), - ~onUpdate: t('data) => unit, - unit - ) => - React.element(React.node); -}; +module Make: + () => + { + let init: list(item('data)) => t('data); + + module Anchor: { + let make: + ( + ~model: option(t('data)), + ~originX: [ | `Left | `Middle | `Right]=?, + ~originY: [ | `Top | `Middle | `Bottom]=?, + ~onUpdate: t('data) => unit, + unit + ) => + React.element(React.node); + }; + }; diff --git a/src/Components/dune b/src/Components/dune index e99849f6d6..bfd66311e3 100644 --- a/src/Components/dune +++ b/src/Components/dune @@ -1,7 +1,7 @@ (library (name Oni_Components) (public_name Oni2.components) - (preprocess (pps brisk-reconciler.ppx)) + (preprocess (pps brisk-reconciler.ppx ppx_deriving.show)) (libraries str bigarray From d425b63bba30a1616903a88a17d34cfb44448aa4 Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 08:16:26 +0100 Subject: [PATCH 04/15] wire up context menu --- src/Components/Oni_Components.re | 1 - src/Model/Actions.re | 5 +++++ src/Model/State.re | 3 +++ src/Store/ContextMenuStore.re | 36 ++++++++++++++++++++++++++++++++ src/Store/StoreThread.re | 2 ++ src/UI/Root.re | 18 ++++++++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) delete mode 100644 src/Components/Oni_Components.re create mode 100644 src/Store/ContextMenuStore.re diff --git a/src/Components/Oni_Components.re b/src/Components/Oni_Components.re deleted file mode 100644 index 4bde20e742..0000000000 --- a/src/Components/Oni_Components.re +++ /dev/null @@ -1 +0,0 @@ -module ContextMenu = ContextMenu; diff --git a/src/Model/Actions.re b/src/Model/Actions.re index 12abe53cdd..31933d041d 100644 --- a/src/Model/Actions.re +++ b/src/Model/Actions.re @@ -10,6 +10,7 @@ open Oni_Input; open Oni_Syntax; module Ext = Oni_Extensions; +module ContextMenu = Oni_Components.ContextMenu; [@deriving show({with_path: false})] type t = @@ -55,6 +56,9 @@ type t = | TextInput([@opaque] Revery.Events.textInputEvent) | HoverShow | ChangeMode([@opaque] Vim.Mode.t) + | ContextMenuUpdate([@opaque] ContextMenu.t(t)) + | ContextMenuOverlayClicked + | ContextMenuItemSelected(ContextMenu.item(t)) | DiagnosticsHotKey | DiagnosticsSet(Uri.t, string, [@opaque] list(Diagnostic.t)) | DiagnosticsClear(string) @@ -80,6 +84,7 @@ type t = | EditorScrollToColumn(EditorId.t, int) | ShowNotification(Notification.t) | HideNotification(Notification.t) + | ClearNotifications | FileExplorer(FileExplorer.action) | LanguageFeature(LanguageFeatures.action) | QuickmenuShow(quickmenuVariant) diff --git a/src/Model/State.re b/src/Model/State.re index 3f1e1870ce..fef505f929 100644 --- a/src/Model/State.re +++ b/src/Model/State.re @@ -9,6 +9,7 @@ open Oni_Input; open Oni_Syntax; module Ext = Oni_Extensions; +module ContextMenu = Oni_Components.ContextMenu; type t = { buffers: Buffers.t, @@ -16,6 +17,7 @@ type t = { bufferHighlights: BufferHighlights.t, bufferSyntaxHighlights: BufferSyntaxHighlights.t, commands: Commands.t, + contextMenu: option(ContextMenu.t(Actions.t)), mode: Vim.Mode.t, completions: Completions.t, diagnostics: Diagnostics.t, @@ -64,6 +66,7 @@ let create: unit => t = bufferRenderers: BufferRenderers.initial, bufferSyntaxHighlights: BufferSyntaxHighlights.empty, commands: Commands.empty, + contextMenu: None, completions: Completions.initial, configuration: Configuration.default, definition: Definition.empty, diff --git a/src/Store/ContextMenuStore.re b/src/Store/ContextMenuStore.re new file mode 100644 index 0000000000..1ec4e1c952 --- /dev/null +++ b/src/Store/ContextMenuStore.re @@ -0,0 +1,36 @@ +open Oni_Model; +open Actions; + +module ContextMenu = Oni_Components.ContextMenu; + +let start = () => { + let selectItemEffect = (item: ContextMenu.item(_)) => + Isolinear.Effect.createWithDispatch("contextMenu.selectItem", dispatch => + dispatch(item.data) + ); + + let updater = (state: State.t, action) => { + let default = (state, Isolinear.Effect.none); + + switch (action) { + | ContextMenuUpdate(model) => ( + {...state, contextMenu: Some(model)}, + Isolinear.Effect.none, + ) + + | ContextMenuOverlayClicked => ( + {...state, contextMenu: None}, + Isolinear.Effect.none, + ) + + | ContextMenuItemSelected(item) => ( + {...state, contextMenu: None}, + selectItemEffect(item), + ) + + | _ => default + }; + }; + + updater; +}; diff --git a/src/Store/StoreThread.re b/src/Store/StoreThread.re index cb68a6e6c2..a5619a5426 100644 --- a/src/Store/StoreThread.re +++ b/src/Store/StoreThread.re @@ -157,6 +157,7 @@ let start = let titleUpdater = TitleStoreConnector.start(setTitle); let sneakUpdater = SneakStore.start(); + let contextMenuUpdater = ContextMenuStore.start(); let (storeDispatch, storeStream) = Isolinear.Store.create( @@ -185,6 +186,7 @@ let start = titleUpdater, sneakUpdater, Features.update, + contextMenuUpdater, ]), (), ); diff --git a/src/UI/Root.re b/src/UI/Root.re index e0627471d3..a8335a6f22 100644 --- a/src/UI/Root.re +++ b/src/UI/Root.re @@ -7,6 +7,7 @@ open Revery; open Revery.UI; open Oni_Model; +open Oni_Components; module Styles = { open Style; @@ -39,6 +40,7 @@ let make = (~state: State.t, ()) => { let State.{ theme, configuration, + contextMenu, uiFont as font, editorFont, sideBar, @@ -112,6 +114,22 @@ let make = (~state: State.t, ()) => { statusBar + {switch (contextMenu) { + | Some(model) => + let onOverlayClick = () => + GlobalContext.current().dispatch(ContextMenuOverlayClicked); + let onItemSelect = item => + GlobalContext.current().dispatch(ContextMenuItemSelected(item)); + + ; + | None => React.empty + }} ; From cce3f18fa3d9386f5346773e8f5567404e42bc4a Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 08:16:44 +0100 Subject: [PATCH 05/15] add context menu to notification status abr item --- src/Model/Notifications.re | 4 +++ src/Model/StatusBarModel.re | 3 +- src/Store/ContextMenuStore.re | 21 +++++++++++++ src/UI/Root.re | 7 ++++- src/UI/StatusBar.re | 55 ++++++++++++++++++++++++++++++----- 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/Model/Notifications.re b/src/Model/Notifications.re index 150a973ffc..19c0c94abe 100644 --- a/src/Model/Notifications.re +++ b/src/Model/Notifications.re @@ -3,12 +3,16 @@ open Notification; type t = list(Notification.t); +module ContextMenu = + Oni_Components.ContextMenu.Make({}); + let initial: t = []; let reduce = (state, action: Actions.t) => { switch (action) { | ShowNotification(item) => [item, ...state] | HideNotification(item) => List.filter(it => it.id != item.id, state) + | ClearNotifications => initial | _ => state }; }; diff --git a/src/Model/StatusBarModel.re b/src/Model/StatusBarModel.re index 4ab5d210e4..9b2cf6157e 100644 --- a/src/Model/StatusBarModel.re +++ b/src/Model/StatusBarModel.re @@ -7,7 +7,8 @@ [@deriving show({with_path: false})] type action = | DiagnosticsClicked - | NotificationCountClicked; + | NotificationCountClicked + | NotificationsContextMenu; module Alignment = { type t = diff --git a/src/Store/ContextMenuStore.re b/src/Store/ContextMenuStore.re index 1ec4e1c952..794fbd6035 100644 --- a/src/Store/ContextMenuStore.re +++ b/src/Store/ContextMenuStore.re @@ -3,6 +3,22 @@ open Actions; module ContextMenu = Oni_Components.ContextMenu; +let contextMenu = + Notifications.ContextMenu.init( + ContextMenu.[ + { + label: "Clear All", + // icon: None, + data: ClearNotifications, + }, + { + label: "Open", + // icon: None, + data: StatusBar(NotificationCountClicked), + }, + ], + ); + let start = () => { let selectItemEffect = (item: ContextMenu.item(_)) => Isolinear.Effect.createWithDispatch("contextMenu.selectItem", dispatch => @@ -28,6 +44,11 @@ let start = () => { selectItemEffect(item), ) + | StatusBar(NotificationsContextMenu) => ( + {...state, contextMenu: Some(contextMenu)}, + Isolinear.Effect.none, + ) + | _ => default }; }; diff --git a/src/UI/Root.re b/src/UI/Root.re index a8335a6f22..455eef068c 100644 --- a/src/UI/Root.re +++ b/src/UI/Root.re @@ -9,6 +9,8 @@ open Revery.UI; open Oni_Model; open Oni_Components; +module ContextMenu = Oni_Components.ContextMenu; + module Styles = { open Style; @@ -48,6 +50,9 @@ let make = (~state: State.t, ()) => { _, } = state; + let onContextMenuUpdate = model => + GlobalContext.current().dispatch(ContextMenuUpdate(model)); + let statusBarVisible = Selectors.getActiveConfigurationValue(state, c => c.workbenchStatusBarVisible @@ -72,7 +77,7 @@ let make = (~state: State.t, ()) => { let statusBar = statusBarVisible ? - + : React.empty; diff --git a/src/UI/StatusBar.re b/src/UI/StatusBar.re index 3275a9fb34..68a82ed08d 100644 --- a/src/UI/StatusBar.re +++ b/src/UI/StatusBar.re @@ -16,6 +16,7 @@ open Oni_Model.StatusBarModel; module Option = Utility.Option; module Animation = Revery.UI.Animation; +module ContextMenu = Oni_Components.ContextMenu; let useExpiration = (~equals=(==), ~expireAfter, items) => { let%hook (active, setActive) = Hooks.state([]); @@ -222,12 +223,20 @@ let section = (~children=React.empty, ~align, ()) => children ; let item = - (~children, ~backgroundColor=Colors.transparentWhite, ~onClick=?, ()) => { + ( + ~children, + ~backgroundColor=Colors.transparentWhite, + ~onClick=?, + ~onRightClick=?, + (), + ) => { let style = Styles.item(backgroundColor); - switch (onClick) { - | Some(onClick) => children - | None => children + // Avoid cursor turning into pointer if there's no mouse interaction available + if (onClick == None && onRightClick == None) { + children ; + } else { + children ; }; }; @@ -240,15 +249,32 @@ let textItem = (~background, ~font, ~theme: Theme.t, ~text, ()) => ; let notificationCount = - (~font, ~foreground as color, ~background, ~notifications, ()) => { + ( + ~font, + ~foreground as color, + ~background, + ~notifications, + ~contextMenu, + ~onContextMenuUpdate, + (), + ) => { let text = notifications |> List.length |> string_of_int; let onClick = () => GlobalContext.current().dispatch( Actions.StatusBar(NotificationCountClicked), ); + let onRightClick = () => + GlobalContext.current().dispatch( + Actions.StatusBar(NotificationsContextMenu), + ); - + + ease(Easing.ease) |> tween(50.0, 0.) ); -let%component make = (~state: State.t, ()) => { +let%component make = + ( + ~state: State.t, + ~contextMenu: option(ContextMenu.t(Actions.t)), + ~onContextMenuUpdate, + (), + ) => { let State.{mode, theme, uiFont: font, diagnostics, notifications, _} = state; let%hook activeNotifications = @@ -391,7 +423,14 @@ let%component make = (~state: State.t, ()) => {
- +
leftItems
From ed0a4057d8bc4d81615d60a98a6ee9b91efa5677 Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 08:18:22 +0100 Subject: [PATCH 06/15] fix warning --- src/Store/ContextMenuStore.re | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Store/ContextMenuStore.re b/src/Store/ContextMenuStore.re index 794fbd6035..c9921a2850 100644 --- a/src/Store/ContextMenuStore.re +++ b/src/Store/ContextMenuStore.re @@ -21,7 +21,8 @@ let contextMenu = let start = () => { let selectItemEffect = (item: ContextMenu.item(_)) => - Isolinear.Effect.createWithDispatch("contextMenu.selectItem", dispatch => + Isolinear.Effect.createWithDispatch( + ~name="contextMenu.selectItem", dispatch => dispatch(item.data) ); From 4484b2016397f1b646520341832c1386e65aa46d Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 08:48:00 +0100 Subject: [PATCH 07/15] add menu item focus --- src/Components/ContextMenu.re | 66 +++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index 209ef060fd..7d9144a5c4 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -77,6 +77,7 @@ module MenuItem = { ]; }; + let component = React.Expert.component("MenuItem"); let make: 'data. ( @@ -86,37 +87,42 @@ module MenuItem = { ~onClick: unit => unit, unit ) => - React.element(React.node) + _ = - (~item, ~theme, ~font, ~onClick, ()) => { - let isFocused = false; - - // let iconView = - // switch (item.icon) { - // | Some(icon) => - // IconTheme.IconDefinition.( - // - // ) - - // | None => - // }; - - let labelView = { - let style = Styles.label(~font, ~theme, ~isFocused); - ; - }; - - - - // iconView - labelView - ; - // onMouseOut={_ => setIsFocused(_ => false)} - // onMouseOver={_ => setIsFocused(_ => true)} - }; + (~item, ~theme, ~font, ~onClick, ()) => + component(hooks => { + let ((isFocused, setIsFocused), hooks) = Hooks.state(false, hooks); + + // let iconView = + // switch (item.icon) { + // | Some(icon) => + // IconTheme.IconDefinition.( + // + // ) + + // | None => + // }; + + let labelView = { + let style = Styles.label(~font, ~theme, ~isFocused); + ; + }; + + ( + + setIsFocused(_ => false)} + onMouseOver={_ => setIsFocused(_ => true)}> + // iconView + labelView + , + hooks, + ); + }); }; // MENU From b785a17098e1666c73a460b5dd27d6898000eae4 Mon Sep 17 00:00:00 2001 From: glennsl Date: Thu, 16 Jan 2020 09:06:53 +0100 Subject: [PATCH 08/15] extract placement from ContextMenu.t --- src/Components/ContextMenu.re | 51 ++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index 7d9144a5c4..f21208da28 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -33,12 +33,16 @@ type item('data) = { data: [@opaque] 'data, }; -type t('data) = { - id: Id.t, +type placement = { x: int, y: int, originX: [ | `Left | `Middle | `Right], originY: [ | `Top | `Middle | `Bottom], +}; + +type t('data) = { + id: Id.t, + placement: option(placement), items: list(item('data)), }; @@ -142,15 +146,15 @@ module Menu = { }; let component = React.Expert.component("Menu"); - let make = (~model, ~theme, ~font, ~onItemSelect, ()) => + let make = (~items, ~placement, ~theme, ~font, ~onItemSelect, ()) => component(hooks => { let ((maybeRef, setRef), hooks) = Hooks.state(None, hooks); - let {x, y, originX, originY, items, _} = model; + let {x, y, originX, originY} = placement; let height = switch (maybeRef) { | Some((node: node)) => node#measurements().height - | None => List.length(model.items) * 20 + | None => List.length(items) * 20 }; let width = Constants.menuWidth; @@ -211,24 +215,20 @@ module Overlay = { ]; }; - let make = (~model, ~theme, ~font, ~onOverlayClick, ~onItemSelect, ()) => { - - - ; - }; + let make = (~model, ~theme, ~font, ~onOverlayClick, ~onItemSelect, ()) => + switch (model) { + | {items, placement: Some(placement), _} => + + + + | _ => React.empty + }; }; module Make = (()) => { let id = Id.create(); - let init = items => { - id, - x: 0, - y: 0, - originX: `Left, - originY: `Bottom, - items, - }; + let init = items => {id, placement: None, items}; module Anchor = { let component = React.Expert.component("Anchor"); @@ -248,12 +248,19 @@ module Make = (()) => { if (model.id == id) { let (x, y, _, _) = Math.BoundingBox2d.getBounds(node#getBoundingBox()); - let (x, y) = (int_of_float(x), int_of_float(y)); - - if (x != model.x || y != model.y) { - onUpdate({...model, x, y, originX, originY}); + let placement = + Some({ + x: int_of_float(x), + y: int_of_float(y), + originX, + originY, + }); + + if (model.placement != placement) { + onUpdate({...model, placement}); }; } + | _ => () }; From 37637edf651f03a8be417b7fcd8b2fa3afddafe9 Mon Sep 17 00:00:00 2001 From: glennsl Date: Sat, 18 Jan 2020 04:39:24 +0100 Subject: [PATCH 09/15] ContextMenu -> ContextMenuUpdated --- src/Model/Actions.re | 2 +- src/Store/ContextMenuStore.re | 2 +- src/UI/Root.re | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/Actions.re b/src/Model/Actions.re index 31933d041d..b4804456c2 100644 --- a/src/Model/Actions.re +++ b/src/Model/Actions.re @@ -56,7 +56,7 @@ type t = | TextInput([@opaque] Revery.Events.textInputEvent) | HoverShow | ChangeMode([@opaque] Vim.Mode.t) - | ContextMenuUpdate([@opaque] ContextMenu.t(t)) + | ContextMenuUpdated([@opaque] ContextMenu.t(t)) | ContextMenuOverlayClicked | ContextMenuItemSelected(ContextMenu.item(t)) | DiagnosticsHotKey diff --git a/src/Store/ContextMenuStore.re b/src/Store/ContextMenuStore.re index c9921a2850..c4625ee880 100644 --- a/src/Store/ContextMenuStore.re +++ b/src/Store/ContextMenuStore.re @@ -30,7 +30,7 @@ let start = () => { let default = (state, Isolinear.Effect.none); switch (action) { - | ContextMenuUpdate(model) => ( + | ContextMenuUpdated(model) => ( {...state, contextMenu: Some(model)}, Isolinear.Effect.none, ) diff --git a/src/UI/Root.re b/src/UI/Root.re index 455eef068c..6ad63d7fe9 100644 --- a/src/UI/Root.re +++ b/src/UI/Root.re @@ -51,7 +51,7 @@ let make = (~state: State.t, ()) => { } = state; let onContextMenuUpdate = model => - GlobalContext.current().dispatch(ContextMenuUpdate(model)); + GlobalContext.current().dispatch(ContextMenuUpdated(model)); let statusBarVisible = Selectors.getActiveConfigurationValue(state, c => From 3bc5690246786b4622eb9cfc28fe301790537212 Mon Sep 17 00:00:00 2001 From: glennsl Date: Sat, 18 Jan 2020 06:43:18 +0100 Subject: [PATCH 10/15] post-rebase fix --- src/UI/Root.re | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UI/Root.re b/src/UI/Root.re index 6ad63d7fe9..6ec3d04640 100644 --- a/src/UI/Root.re +++ b/src/UI/Root.re @@ -7,7 +7,6 @@ open Revery; open Revery.UI; open Oni_Model; -open Oni_Components; module ContextMenu = Oni_Components.ContextMenu; From b59409dee906f9dc46466a5b790dc323f3a09f31 Mon Sep 17 00:00:00 2001 From: glennsl Date: Sun, 19 Jan 2020 09:49:42 +0100 Subject: [PATCH 11/15] originX/Y -> orientation --- src/Components/ContextMenu.re | 24 ++++++------------------ src/Components/ContextMenu.rei | 7 +++++-- src/UI/StatusBar.re | 2 +- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index f21208da28..dfebccbe6d 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -36,8 +36,7 @@ type item('data) = { type placement = { x: int, y: int, - originX: [ | `Left | `Middle | `Right], - originY: [ | `Top | `Middle | `Bottom], + orientation: ([ | `Top | `Middle | `Bottom], [ | `Left | `Middle | `Right]), }; type t('data) = { @@ -149,7 +148,7 @@ module Menu = { let make = (~items, ~placement, ~theme, ~font, ~onItemSelect, ()) => component(hooks => { let ((maybeRef, setRef), hooks) = Hooks.state(None, hooks); - let {x, y, originX, originY} = placement; + let {x, y, orientation: (orientY, orientX)} = placement; let height = switch (maybeRef) { @@ -159,14 +158,14 @@ module Menu = { let width = Constants.menuWidth; let x = - switch (originX) { + switch (orientX) { | `Left => x | `Middle => x - width / 2 | `Right => x - width }; let y = - switch (originY) { + switch (orientY) { | `Top => y - height | `Middle => y - height / 2 | `Bottom => y @@ -233,13 +232,7 @@ module Make = (()) => { module Anchor = { let component = React.Expert.component("Anchor"); let make = - ( - ~model as maybeModel, - ~originX=`Left, - ~originY=`Bottom, - ~onUpdate, - (), - ) => + (~model as maybeModel, ~orientation=(`Bottom, `Left), ~onUpdate, ()) => component(hooks => { let ((maybeRef, setRef), hooks) = Hooks.ref(None, hooks); @@ -249,12 +242,7 @@ module Make = (()) => { let (x, y, _, _) = Math.BoundingBox2d.getBounds(node#getBoundingBox()); let placement = - Some({ - x: int_of_float(x), - y: int_of_float(y), - originX, - originY, - }); + Some({x: int_of_float(x), y: int_of_float(y), orientation}); if (model.placement != placement) { onUpdate({...model, placement}); diff --git a/src/Components/ContextMenu.rei b/src/Components/ContextMenu.rei index e9450baeaa..bddbbeaee7 100644 --- a/src/Components/ContextMenu.rei +++ b/src/Components/ContextMenu.rei @@ -33,8 +33,11 @@ module Make: let make: ( ~model: option(t('data)), - ~originX: [ | `Left | `Middle | `Right]=?, - ~originY: [ | `Top | `Middle | `Bottom]=?, + ~orientation: ( + [ | `Top | `Middle | `Bottom], + [ | `Left | `Middle | `Right], + ) + =?, ~onUpdate: t('data) => unit, unit ) => diff --git a/src/UI/StatusBar.re b/src/UI/StatusBar.re index 68a82ed08d..359c8683cf 100644 --- a/src/UI/StatusBar.re +++ b/src/UI/StatusBar.re @@ -271,7 +271,7 @@ let notificationCount = From 460eb7abfcff6ef33f9c7870f5a3a0873e56757d Mon Sep 17 00:00:00 2001 From: glennsl Date: Sun, 19 Jan 2020 09:59:37 +0100 Subject: [PATCH 12/15] add offsetX/Y --- src/Components/ContextMenu.re | 15 +++++++++++++-- src/Components/ContextMenu.rei | 2 ++ src/UI/StatusBar.re | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index dfebccbe6d..db0ee9f6d9 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -232,7 +232,14 @@ module Make = (()) => { module Anchor = { let component = React.Expert.component("Anchor"); let make = - (~model as maybeModel, ~orientation=(`Bottom, `Left), ~onUpdate, ()) => + ( + ~model as maybeModel, + ~orientation=(`Bottom, `Left), + ~offsetX=0, + ~offsetY=0, + ~onUpdate, + (), + ) => component(hooks => { let ((maybeRef, setRef), hooks) = Hooks.ref(None, hooks); @@ -242,7 +249,11 @@ module Make = (()) => { let (x, y, _, _) = Math.BoundingBox2d.getBounds(node#getBoundingBox()); let placement = - Some({x: int_of_float(x), y: int_of_float(y), orientation}); + Some({ + x: int_of_float(x) + offsetX, + y: int_of_float(y) + offsetY, + orientation, + }); if (model.placement != placement) { onUpdate({...model, placement}); diff --git a/src/Components/ContextMenu.rei b/src/Components/ContextMenu.rei index bddbbeaee7..cc0c499c7b 100644 --- a/src/Components/ContextMenu.rei +++ b/src/Components/ContextMenu.rei @@ -38,6 +38,8 @@ module Make: [ | `Left | `Middle | `Right], ) =?, + ~offsetX: int=?, + ~offsetY: int=?, ~onUpdate: t('data) => unit, unit ) => diff --git a/src/UI/StatusBar.re b/src/UI/StatusBar.re index 359c8683cf..7ddc5c4f6b 100644 --- a/src/UI/StatusBar.re +++ b/src/UI/StatusBar.re @@ -272,6 +272,7 @@ let notificationCount = From 8735d1c02c2cfa636299f9fdfe67883414a37543 Mon Sep 17 00:00:00 2001 From: glennsl Date: Sun, 19 Jan 2020 10:00:05 +0100 Subject: [PATCH 13/15] adjust origin for orientation --- src/Components/ContextMenu.re | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index db0ee9f6d9..d6b2eef7c0 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -246,8 +246,16 @@ module Make = (()) => { switch (maybeModel, maybeRef) { | (Some(model), Some(node)) => if (model.id == id) { - let (x, y, _, _) = + let (x, y, width, _) = Math.BoundingBox2d.getBounds(node#getBoundingBox()); + + let x = + switch (orientation) { + | (_, `Left) => x + | (_, `Middle) => x -. width /. 2. + | (_, `Right) => x -. width + }; + let placement = Some({ x: int_of_float(x) + offsetX, From 77f00ba2eaa408842e63d77c5e1a66090f7283fc Mon Sep 17 00:00:00 2001 From: glennsl Date: Tue, 21 Jan 2020 10:47:59 +0100 Subject: [PATCH 14/15] add box shadow --- src/Components/ContextMenu.re | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index d6b2eef7c0..809bdea6c7 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -141,6 +141,13 @@ module Menu = { backgroundColor(theme.menuBackground), color(theme.menuForeground), width(Constants.menuWidth), + boxShadow( + ~xOffset=-5., + ~yOffset=-5., + ~blurRadius=25., + ~spreadRadius=-10., + ~color=Color.rgba(0., 0., 0., 0.0001), + ), ]; }; @@ -172,16 +179,6 @@ module Menu = { }; ( - // TODO: BoxShadow apparently blocks mouse events. Figure out why before adding back - // setRef(_ => Some(node))}> @@ -192,7 +189,6 @@ module Menu = { }) |> React.listToElement} , - // , hooks, ); }); From 9f8d77984c20c29990e34cb27dd05e9c57d31c71 Mon Sep 17 00:00:00 2001 From: glennsl Date: Tue, 21 Jan 2020 10:51:34 +0100 Subject: [PATCH 15/15] use default mouse cursor on overlay --- src/Components/ContextMenu.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Components/ContextMenu.re b/src/Components/ContextMenu.re index 809bdea6c7..e3de6a0124 100644 --- a/src/Components/ContextMenu.re +++ b/src/Components/ContextMenu.re @@ -207,6 +207,7 @@ module Overlay = { left(0), right(0), pointerEvents(`Allow), + cursor(MouseCursors.arrow), ]; };