Skip to content

Commit 97416ab

Browse files
authored
Add SystemTrayIcon component (#224)
* Add SystemTrayIcon component * Remove useless return * Add id prop * Add comments for SystemTrayIcon props * Remove use of ViewProps for SystemTrayIcon * Move SystemTrayIcon instance check to reconciler * Switch SystemTrayIcon to use Menu component as a child * Fix comment example for SystemTrayIcon component
1 parent ad3720e commit 97416ab

File tree

5 files changed

+216
-5
lines changed

5 files changed

+216
-5
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { WidgetEventListeners } from "../View/RNView";
2+
import {
3+
QSystemTrayIconSignals,
4+
QSystemTrayIcon,
5+
NodeWidget,
6+
QIcon,
7+
QMenu,
8+
} from "@nodegui/nodegui";
9+
import { RNComponent, RNProps } from "../config";
10+
import { throwUnsupported } from "../../utils/helpers";
11+
12+
/**
13+
* The SystemTrayIcon component provides the ability to add and manipulate a native system tray icon.
14+
* [NodeGui's QSystemTrayIcon](https://docs.nodegui.org/docs/api/generated/classes/qsystemtrayicon).
15+
* ## Example
16+
* ```javascript
17+
* import React from "react";
18+
* import { QIcon, QAction } from "@nodegui/nodegui";
19+
* import { Menu, Renderer, SystemTrayIcon, Window } from "@nodegui/react-nodegui";
20+
* import path from "path";
21+
*
22+
* const icon = new QIcon(path.join(__dirname, "../extras/assets/nodegui.png"));
23+
* const action = new QAction();
24+
* action.setText("Hello");
25+
* action.addEventListener("triggered", () => {
26+
* console.log("hello");
27+
* });
28+
*
29+
* const App = () => {
30+
* return (
31+
* <Window>
32+
* <SystemTrayIcon icon={icon} tooltip="Hello World" visible>
33+
* <Menu actions={[action]} />
34+
* </SystemTrayIcon>
35+
* </Window>
36+
* );
37+
* };
38+
*
39+
* Renderer.render(<App />);
40+
*
41+
* ```
42+
*/
43+
export interface SystemTrayIconProps extends RNProps {
44+
/**
45+
* Sets an icon for the system tray. [QSystemTrayIcon: setIcon](https://docs.nodegui.org/docs/api/generated/classes/qsystemtrayicon#seticon)
46+
*/
47+
icon?: QIcon;
48+
49+
/**
50+
* Sets the object name (id) of the widget in Qt. Object name can be analogous to id of an element in the web world. Using the objectName of the widget one can reference it in the Qt's stylesheet much like what we do with id in the web world. [QWidget: setObjectName](https://docs.nodegui.org/docs/api/NodeWidget#widgetsetobjectnameobjectname)
51+
*/
52+
id?: string;
53+
54+
/**
55+
* Prop to set the event listener map. See [Handlong Events](/docs/guides/handle-events)
56+
*/
57+
on?: Partial<WidgetEventListeners | QSystemTrayIconSignals>;
58+
59+
/**
60+
* Sets a tooltip for the system tray. [QSystemTrayIcon: setTooltip](https://docs.nodegui.org/docs/api/generated/classes/qsystemtrayicon#settooltip)
61+
*/
62+
tooltip?: string;
63+
64+
/**
65+
* Shows or hides the widget and its children. [QWidget: show](https://docs.nodegui.org/docs/api/NodeWidget#widgetshow)
66+
*/
67+
visible?: boolean;
68+
}
69+
70+
const setSystemTrayIconProps = (
71+
widget: RNSystemTrayIcon,
72+
newProps: SystemTrayIconProps,
73+
oldProps: SystemTrayIconProps
74+
) => {
75+
const setter: SystemTrayIconProps = {
76+
set icon(icon: QIcon) {
77+
widget.setIcon(icon);
78+
},
79+
set id(id: string) {
80+
widget.setObjectName(id);
81+
},
82+
set on(
83+
listenerMap: Partial<WidgetEventListeners | QSystemTrayIconSignals>
84+
) {
85+
const listenerMapLatest: any = Object.assign({}, listenerMap);
86+
const oldListenerMap = Object.assign({}, oldProps.on);
87+
Object.entries(oldListenerMap).forEach(([eventType, oldEvtListener]) => {
88+
const newEvtListener = listenerMapLatest[eventType];
89+
if (oldEvtListener !== newEvtListener) {
90+
widget.removeEventListener(eventType as any, oldEvtListener);
91+
} else {
92+
delete listenerMapLatest[eventType];
93+
}
94+
});
95+
96+
Object.entries(listenerMapLatest).forEach(
97+
([eventType, newEvtListener]) => {
98+
widget.addEventListener(eventType as any, newEvtListener);
99+
}
100+
);
101+
},
102+
set tooltip(tooltip: string) {
103+
widget.setToolTip(tooltip);
104+
},
105+
set visible(shouldShow: boolean) {
106+
shouldShow ? widget.show() : widget.hide();
107+
},
108+
};
109+
Object.assign(setter, newProps);
110+
};
111+
112+
/**
113+
* @ignore
114+
*/
115+
export class RNSystemTrayIcon extends QSystemTrayIcon implements RNComponent {
116+
static tagName = "systemtrayicon";
117+
118+
setProps(newProps: SystemTrayIconProps, oldProps: SystemTrayIconProps): void {
119+
setSystemTrayIconProps(this, newProps, oldProps);
120+
}
121+
appendInitialChild(child: NodeWidget<any>): void {
122+
if (child instanceof QMenu) {
123+
if (!this.contextMenu) {
124+
this.setContextMenu(child);
125+
} else {
126+
console.warn("SystemTrayIcon can't have more than one Menu.");
127+
}
128+
} else {
129+
console.warn("SystemTrayIcon only supports Menu as its children");
130+
}
131+
}
132+
appendChild(child: NodeWidget<any>): void {
133+
this.appendInitialChild(child);
134+
}
135+
insertBefore(child: NodeWidget<any>, beforeChild: NodeWidget<any>): void {
136+
throwUnsupported(this);
137+
}
138+
removeChild(child: NodeWidget<any>): void {
139+
throwUnsupported(this);
140+
}
141+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Fiber } from "react-reconciler";
2+
import { registerComponent, ComponentConfig } from "../config";
3+
import { RNSystemTrayIcon, SystemTrayIconProps } from "./RNSystemTrayIcon";
4+
import { AppContainer } from "../../reconciler";
5+
6+
class SystemTrayIconConfig extends ComponentConfig {
7+
tagName = RNSystemTrayIcon.tagName;
8+
shouldSetTextContent(nextProps: SystemTrayIconProps): boolean {
9+
return false;
10+
}
11+
createInstance(
12+
newProps: SystemTrayIconProps,
13+
rootInstance: AppContainer,
14+
context: any,
15+
workInProgress: Fiber
16+
): RNSystemTrayIcon {
17+
const widget = new RNSystemTrayIcon();
18+
widget.setProps(newProps, {});
19+
return widget;
20+
}
21+
commitMount(
22+
instance: RNSystemTrayIcon,
23+
newProps: SystemTrayIconProps,
24+
internalInstanceHandle: any
25+
): void {
26+
if (newProps.visible !== false) {
27+
instance.show();
28+
}
29+
}
30+
commitUpdate(
31+
instance: RNSystemTrayIcon,
32+
updatePayload: any,
33+
oldProps: SystemTrayIconProps,
34+
newProps: SystemTrayIconProps,
35+
finishedWork: Fiber
36+
): void {
37+
instance.setProps(newProps, oldProps);
38+
}
39+
}
40+
41+
export const SystemTrayIcon = registerComponent<SystemTrayIconProps>(
42+
new SystemTrayIconConfig()
43+
);

src/demo.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
2-
import { Renderer, Window, MenuBar, Menu } from "./index";
3-
import { QAction, QApplication } from "@nodegui/nodegui";
2+
import { QIcon, QAction, QApplication } from "@nodegui/nodegui";
3+
import path from "path";
4+
import { MenuBar, Menu, SystemTrayIcon, Renderer, Window } from ".";
45

56
const quitAction = new QAction();
67
quitAction.setText("Quit");
@@ -19,9 +20,20 @@ sayHi.addEventListener("triggered", () => {
1920

2021
const randActions: QAction[] = [sayHi];
2122

23+
const trayIcon = new QIcon(
24+
path.join(__dirname, "../extras/assets/nodegui.png")
25+
);
26+
const separatorAction = new QAction();
27+
separatorAction.setSeparator(true);
28+
29+
const systemTrayMenuActions = [sayHi, separatorAction, quitAction];
30+
2231
const App = () => {
2332
return (
2433
<Window>
34+
<SystemTrayIcon icon={trayIcon} tooltip="React Nodegui" visible>
35+
<Menu actions={systemTrayMenuActions} />
36+
</SystemTrayIcon>
2537
<MenuBar>
2638
<Menu title={"File"} actions={fileActions} />
2739
<Menu title={"Random"} actions={randActions} />

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export { Dial } from "./components/Dial";
1717
export { SpinBox } from "./components/SpinBox";
1818
export { ScrollArea } from "./components/ScrollArea";
1919
export { ComboBox } from "./components/ComboBox";
20+
export { SystemTrayIcon } from "./components/SystemTrayIcon";
2021
export { Tabs } from "./components/Tab";
2122
export { TabItem } from "./components/TabItem";
2223
export { useEventHandler } from "./hooks";

src/reconciler/index.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Reconciler from "react-reconciler";
2-
import { NodeWidget } from "@nodegui/nodegui";
2+
import { NodeWidget, QSystemTrayIcon, NodeObject } from "@nodegui/nodegui";
33
import {
44
getComponentByTagName,
55
RNWidget,
@@ -10,6 +10,9 @@ import {
1010
export type AppContainer = Set<NodeWidget<any>>;
1111
export const appContainer: AppContainer = new Set<NodeWidget<any>>();
1212

13+
const shouldIgnoreChild = (child: NodeObject<any>) =>
14+
child instanceof QSystemTrayIcon;
15+
1316
const HostConfig: Reconciler.HostConfig<
1417
string,
1518
RNProps,
@@ -66,7 +69,10 @@ const HostConfig: Reconciler.HostConfig<
6669
workInProgress
6770
);
6871
},
69-
appendInitialChild: function(parent: RNWidget, child: NodeWidget<any>) {
72+
appendInitialChild: function (parent: RNWidget, child: NodeWidget<any>) {
73+
if (shouldIgnoreChild(child)) {
74+
return;
75+
}
7076
parent.appendInitialChild(child);
7177
},
7278
finalizeInitialChildren: function(
@@ -141,17 +147,25 @@ const HostConfig: Reconciler.HostConfig<
141147
);
142148
},
143149
appendChild: (parent: RNWidget, child: NodeWidget<any>) => {
150+
if (shouldIgnoreChild(child)) {
151+
return;
152+
}
144153
parent.appendChild(child);
145154
},
146155
insertBefore: (
147156
parent: RNWidget,
148157
child: NodeWidget<any>,
149158
beforeChild: NodeWidget<any>
150159
) => {
160+
if (shouldIgnoreChild(child)) {
161+
return;
162+
}
151163
parent.insertBefore(child, beforeChild);
152164
},
153165
removeChild: (parent: RNWidget, child: NodeWidget<any>) => {
154-
parent.removeChild(child);
166+
if (!shouldIgnoreChild(child)) {
167+
parent.removeChild(child);
168+
}
155169
if (child.close) {
156170
child.close();
157171
}

0 commit comments

Comments
 (0)