Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ContextMenu to Notification Status Bar item #1211

Merged
merged 15 commits into from
Jan 22, 2020
274 changes: 274 additions & 0 deletions src/Components/ContextMenu.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
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

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),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IconTheme currently lives in Model. I'm not sure if Core is the right place for it, so for now we'll have to do without.

data: [@opaque] 'data,
};

type placement = {
glennsl marked this conversation as resolved.
Show resolved Hide resolved
x: int,
y: int,
orientation: ([ | `Top | `Middle | `Bottom], [ | `Left | `Middle | `Right]),
glennsl marked this conversation as resolved.
Show resolved Hide resolved
};

type t('data) = {
id: Id.t,
placement: option(placement),
items: list(item('data)),
};

// 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 component = React.Expert.component("MenuItem");
let make:
'data.
(
~item: item('data),
~theme: Theme.t,
~font: UiFont.t,
~onClick: unit => unit,
unit
) =>
_
=
(~item, ~theme, ~font, ~onClick, ()) =>
component(hooks => {
let ((isFocused, setIsFocused), hooks) = Hooks.state(false, hooks);

// let iconView =
// switch (item.icon) {
// | Some(icon) =>
// IconTheme.IconDefinition.(
// <Text
// style={Styles.icon(icon.fontColor)}
// text={FontIcon.codeToIcon(icon.fontCharacter)}
// />
// )

// | None => <Text style={Styles.icon(Colors.transparentWhite)} text="" />
// };

let labelView = {
let style = Styles.label(~font, ~theme, ~isFocused);
<Text style text={item.label} />;
};

(
<Clickable onClick>
<View
style={Styles.container(~theme, ~isFocused)}
onMouseOut={_ => setIsFocused(_ => false)}
onMouseOver={_ => setIsFocused(_ => true)}>
// iconView
labelView </View>
</Clickable>,
hooks,
);
});
};

// 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),
boxShadow(
~xOffset=-5.,
~yOffset=-5.,
~blurRadius=25.,
~spreadRadius=-10.,
~color=Color.rgba(0., 0., 0., 0.0001),
),
Comment on lines +144 to +150
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works! But it's also kind of broken. The alpha value of the color doesn't seem to be respected, and there seems to be some built-in offset that has to be countered by specifying negative offsets.

I managed to tone it down a bit by adding a negative spread, but it still looks a bit off.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works!

Hooray! 🎉

But it's also kind of broken. The alpha value of the color doesn't seem to be respected, and there seems to be some built-in offset that has to be countered by specifying negative offsets.

Ya, unfortunately there are bugs and quirks with the current implementation.

Skia will give us a more robust set of primitives for stuff like this!

Copy link
Member Author

@glennsl glennsl Jan 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer! Looks like fixing it so the alpha value is taken into account shouldn't be too hard, just pass in a vec4 instead of a vec3 and multiply the alpha value with the blur value. But the offset bug seems trickier. I can't se where that comes from. That's also less of an issue I think though, so it can definitely wait for Skia.

];
};

let component = React.Expert.component("Menu");
let make = (~items, ~placement, ~theme, ~font, ~onItemSelect, ()) =>
component(hooks => {
let ((maybeRef, setRef), hooks) = Hooks.state(None, hooks);
let {x, y, orientation: (orientY, orientX)} = placement;

let height =
switch (maybeRef) {
| Some((node: node)) => node#measurements().height
| None => List.length(items) * 20
};
let width = Constants.menuWidth;

let x =
switch (orientX) {
| `Left => x
| `Middle => x - width / 2
| `Right => x - width
};

let y =
switch (orientY) {
| `Top => y - height
| `Middle => y - height / 2
| `Bottom => y
};

(
<View
style={Styles.container(~x, ~y, ~theme)}
ref={node => setRef(_ => Some(node))}>
{items
|> List.map(item => {
let onClick = () => onItemSelect(item);
<MenuItem item theme font onClick />;
})
|> React.listToElement}
</View>,
hooks,
);
});
};

// OVERLAY

module Overlay = {
module Styles = {
open Style;

let overlay = [
position(`Absolute),
top(0),
bottom(0),
left(0),
right(0),
pointerEvents(`Allow),
cursor(MouseCursors.arrow),
];
};

let make = (~model, ~theme, ~font, ~onOverlayClick, ~onItemSelect, ()) =>
switch (model) {
| {items, placement: Some(placement), _} =>
<Clickable onClick=onOverlayClick style=Styles.overlay>
<Menu items placement theme font onItemSelect />
</Clickable>
| _ => React.empty
};
};

module Make = (()) => {
let id = Id.create();

let init = items => {id, placement: None, items};

module Anchor = {
let component = React.Expert.component("Anchor");
let make =
(
~model as maybeModel,
~orientation=(`Bottom, `Left),
~offsetX=0,
~offsetY=0,
~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, 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,
y: int_of_float(y) + offsetY,
orientation,
});

if (model.placement != placement) {
onUpdate({...model, placement});
};
}

| _ => ()
};

(<View ref={node => setRef(Some(node))} />, hooks);
});
};
};
48 changes: 48 additions & 0 deletions src/Components/ContextMenu.rei
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
open Oni_Core;

open Revery.UI;

[@deriving show]
type item('data) = {
label: string,
// icon: option(IconTheme.IconDefinition.t),
data: [@opaque] 'data,
};

type 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 Make:
() =>
{
let init: list(item('data)) => t('data);

module Anchor: {
let make:
(
~model: option(t('data)),
~orientation: (
[ | `Top | `Middle | `Bottom],
[ | `Left | `Middle | `Right],
)
=?,
~offsetX: int=?,
~offsetY: int=?,
~onUpdate: t('data) => unit,
unit
) =>
React.element(React.node);
};
};
14 changes: 12 additions & 2 deletions src/Components/dune
Original file line number Diff line number Diff line change
@@ -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 ppx_deriving.show))
(libraries
str
bigarray
zed_oni
lwt
lwt.unix
Oni2.core
Rench
Revery
editor-core-types
))
5 changes: 5 additions & 0 deletions src/Model/Actions.re
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -55,6 +56,9 @@ type t =
| TextInput([@opaque] Revery.Events.textInputEvent)
| HoverShow
| ChangeMode([@opaque] Vim.Mode.t)
| ContextMenuUpdated([@opaque] ContextMenu.t(t))
| ContextMenuOverlayClicked
| ContextMenuItemSelected(ContextMenu.item(t))
| DiagnosticsHotKey
| DiagnosticsSet(Uri.t, string, [@opaque] list(Diagnostic.t))
| DiagnosticsClear(string)
Expand All @@ -80,6 +84,7 @@ type t =
| EditorScrollToColumn(EditorId.t, int)
| ShowNotification(Notification.t)
| HideNotification(Notification.t)
| ClearNotifications
glennsl marked this conversation as resolved.
Show resolved Hide resolved
| FileExplorer(FileExplorer.action)
| LanguageFeature(LanguageFeatures.action)
| QuickmenuShow(quickmenuVariant)
Expand Down
4 changes: 4 additions & 0 deletions src/Model/Notifications.re
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ open Notification;

type t = list(Notification.t);

module ContextMenu =
Oni_Components.ContextMenu.Make({});
glennsl marked this conversation as resolved.
Show resolved Hide resolved

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
};
};
Expand Down
Loading