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

feat(native): add initial tray-support for macOS #972

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Examples.re
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ let examples = [
render: _ => NativeMenuExample.render(),
source: "NativeMenuExample.re",
},
{name: "Native: Tray", render: _w => Tray.render(), source: "Tray.re"},
{
name: "Native: Inputs",
render: _ => NativeInputExample.render(),
Expand Down
36 changes: 36 additions & 0 deletions examples/Tray.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
open Revery;
open Revery.UI;
open Revery.UI.Components;

let%component make = () => {
let%hook () =
Hooks.effect(
OnMount,
() => {
let trayImage =
Native.Tray.make(
~imagePath=Environment.getAssetPath("outrun-logo.png"),
(),
);

let trayText =
Native.Tray.make() |> Native.Tray.setTitle(~text="Hello Revery!");

Some(
() => {
trayImage |> Native.Tray.remove;
trayText |> Native.Tray.remove;
},
);
},
);

<Center>
<Text
text="You should see two items in your tray-bar. One image and one with
the text \"Hello Revery!\""
/>
</Center>;
};

let render = () => make();
3 changes: 3 additions & 0 deletions src/Native/ReveryCocoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ void revery_setIconProgress_cocoa(void* dt, double progress);
void revery_setIconProgressIndeterminate_cocoa(void *dt);
void revery_hideIconProgress_cocoa(void* ip);

/* Image functions */
void *revery_makeImageFromAbsolutePath_cocoa(const char *image_path_v);

/* Open functions */
int revery_openURL_cocoa(const char *url_string);
int revery_openFile_cocoa(const char *path_string);
Expand Down
1 change: 1 addition & 0 deletions src/Native/Revery_Native.re
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Notification = Notification;
module Shell = Shell;
module Locale = Locale;
module Gtk = Gtk;
module Tray = Tray;
module Menu = Menu;
module Input = Input;
module Window = Window;
Expand Down
7 changes: 7 additions & 0 deletions src/Native/Tray.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type t;

external make: (~imagePath: string=?, unit) => t = "revery_makeTrayHandle";

external setTitle: (t, ~text: string) => t = "revery_setTrayTitle";

external remove: t => unit = "revery_removeTrayItem";
38 changes: 38 additions & 0 deletions src/Native/Tray.rei
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
type t;
lessp marked this conversation as resolved.
Show resolved Hide resolved

/**
* make
*
* Takes an optional [imagePath] which is an absolute path to a in image-file.
* Returns a newly created Tray.t;
*
* Examples:
* Tray.make(~imagePath="/absolute/path/to/image.png", ()) |> ignore;
* Tray.make(~imagePath=Environment.getAssetPath("some_asset_image.png", ()) |> ignore;
* let tray = Tray.make();
*/
let make: (~imagePath: string=?, unit) => t;

/**
* setTitle
*
* Takes a [title] of string and sets the tray's text to it.
* Returns the updated tray item.
* *
* Examples:
* tray |> Tray.setTitle(~text="Hello Revery!") |> ignore;
* let tray = Tray.make() |> Tray.setTitle(~text="Hello Revery!");
* Tray.setTitle(tray, ~text="Hello World!") |> ignore;
*/
let setTitle: (t, ~text: string) => t;

/**
* remove
*
* Given a [t] removes the tray item from the tray.
* *
* Example:
* tray |> Tray.remove;
* Tray.remove(tray);
*/
let remove: t => unit;
4 changes: 2 additions & 2 deletions src/Native/dune
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
(language c)
(names Revery_Native dialog dialog_cocoa dialog_win32 dialog_gtk
notification notification_cocoa environment environment_mac
environment_linux environment_windows icon icon_cocoa icon_win32 shell
shell_cocoa shell_gtk shell_win32 locale locale_cocoa locale_win32 menu
environment_linux environment_windows icon icon_cocoa icon_win32 image_cocoa shell
shell_cocoa shell_gtk shell_win32 tray locale locale_cocoa locale_win32 menu
menu_cocoa input input_cocoa window window_cocoa utilities ReveryGtk
ReveryGtk_Widget ReveryAppDelegate ReveryAppDelegate_func ReveryNSObject
ReveryNSView ReveryNSViewCoords ReveryMenuItemTarget ReveryButtonTarget
Expand Down
16 changes: 16 additions & 0 deletions src/Native/image_cocoa.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "config.h"
#ifdef USE_COCOA
#include <stdio.h>

#import <Cocoa/Cocoa.h>

void *revery_makeImageFromAbsolutePath_cocoa(const char *imagePath) {
NSString *nsImagePath =
[NSString stringWithCString:imagePath encoding:NSUTF8StringEncoding];

NSImage *nsImage = [[NSImage alloc]initWithContentsOfFile:nsImagePath];

return nsImage;
}

#endif
88 changes: 88 additions & 0 deletions src/Native/tray.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <stdio.h>

#include <caml/alloc.h>
#include <caml/callback.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>

#include "caml_values.h"

#include "config.h"
#ifdef USE_WIN32
#include "ReveryWin32.h"
#elif USE_COCOA
#include "ReveryCocoa.h"
#import <Cocoa/Cocoa.h>
#elif USE_GTK
#include "ReveryGtk.h"
#endif

#include "utilities.h"

CAMLprim value revery_makeTrayHandle(value imagePath_v) {
CAMLparam1(imagePath_v);
CAMLlocal1(result);

#ifdef USE_COCOA
NSStatusItem *statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
lessp marked this conversation as resolved.
Show resolved Hide resolved

if (imagePath_v != Val_none) {
const char *imagePath = String_val(Some_val(imagePath_v));

NSImage *nsImage = revery_makeImageFromAbsolutePath_cocoa(imagePath);

statusItem.button.image = nsImage;

UNUSED(imagePath);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: is this still needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, actually, I don't know 🤔 Most likely I followed another example, but looking at the context, I was under the impression that it is, but this is your forte!

    if (vImagePath != Val_none) {
        const char *imagePath = String_val(Some_val(vImagePath));

        NSImage *nsImage = revery_makeImageFromAbsolutePath_cocoa(imagePath);

        statusItem.button.image = nsImage;

        UNUSED(imagePath);
    }

}

result = revery_wrapPointer(statusItem);
lessp marked this conversation as resolved.
Show resolved Hide resolved

CAMLreturn(result);
#elif USE_WIN32
result = caml_alloc(sizeof(NULL), Abstract_tag);
#else
result = caml_alloc(sizeof(NULL), Abstract_tag);
#endif
CAMLreturn(result);
}

CAMLprim value revery_setTrayTitle(value trayHandle_v, value title_v) {
lessp marked this conversation as resolved.
Show resolved Hide resolved
CAMLparam2(trayHandle_v, title_v);
CAMLlocal1(result);
#ifdef USE_COCOA
NSStatusItem* statusItem = (NSStatusItem*)(void*)Field(trayHandle_v, 0);
lessp marked this conversation as resolved.
Show resolved Hide resolved

const char *title = String_val(title_v);

NSString *nsTitle =
[NSString stringWithCString:title encoding:NSUTF8StringEncoding];

statusItem.button.image = NULL;
statusItem.button.title = nsTitle;

result = revery_wrapPointer(statusItem);

CAMLreturn(result);
lessp marked this conversation as resolved.
Show resolved Hide resolved
#elif USE_WIN32
result = caml_alloc(sizeof(NULL), Abstract_tag);
#else
result = caml_alloc(sizeof(NULL), Abstract_tag);
#endif
CAMLreturn(result);
}

void revery_removeTrayItem(value trayHandle_v) {
CAMLparam1(trayHandle_v);
#ifdef USE_COCOA
NSStatusItem* statusItem = (NSStatusItem *)revery_unwrapPointer(trayHandle_v);

[[NSStatusBar systemStatusBar] removeStatusItem: statusItem];

CAMLreturn0;
#elif USE_WIN32
CAMLreturn0;
#else
CAMLreturn0;
#endif
}
31 changes: 31 additions & 0 deletions src/Native/tray_cocoa.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "config.h"
#ifdef USE_COCOA
#include <stdio.h>

#import <Cocoa/Cocoa.h>

void *revery_makeTrayHandleText_cocoa(const char *titleText) {
NSStatusItem *statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
lessp marked this conversation as resolved.
Show resolved Hide resolved

NSString *nsTitle =
[NSString stringWithCString:titleText encoding:NSUTF8StringEncoding];

statusItem.button.title = nsTitle;

return statusItem;
}

void *revery_makeTrayHandleImage_cocoa(const char *titleImage) {
NSStatusItem *statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];

NSString *nsImagePath =
[NSString stringWithCString:titleImage encoding:NSUTF8StringEncoding];

NSImage *nsImage = [[NSImage alloc]initWithContentsOfFile:nsImagePath];

statusItem.button.image = nsImage;

return statusItem;
}

#endif