Skip to content
This repository was archived by the owner on Sep 13, 2024. It is now read-only.

Commit fbf6f09

Browse files
committed
Redesign menu
1 parent 7c416eb commit fbf6f09

File tree

13 files changed

+223
-167
lines changed

13 files changed

+223
-167
lines changed

.vscode/tasks.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
{
77
"label": "electron-debug",
88
"type": "process",
9-
"command": "./node_modules/.bin/tsc",
9+
"command": "./node_modules/.bin/webpack",
1010
"windows": {
11-
"command": "./node_modules/.bin/tsc.cmd"
11+
"command": "./node_modules/.bin/webpack.cmd"
1212
},
13-
"isBackground": true,
14-
// "args": [],
13+
"args": [
14+
"--config",
15+
"./webpack.config.js",
16+
"--mode",
17+
"development"
18+
],
1519
"problemMatcher": {
1620
"owner": "custom",
1721
"pattern": {

example/assets/images/icon.svg

Lines changed: 6 additions & 0 deletions
Loading

example/images/icon.png

-1.38 KB
Binary file not shown.

example/main.js

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ app.on('window-all-closed', function () {
5252
// In this file you can include the rest of your app's specific main process
5353
// code. You can also put them in separate files and require them here.
5454
ipcMain.on('request-application-menu', function (event) {
55-
const menu = JSON.parse(JSON.stringify(Menu.getApplicationMenu(), getCircularReplacer()));
55+
const m = Menu.buildFromTemplate(exampleMenuTemplate());
56+
const menu = JSON.parse(JSON.stringify(m, parseMenu()));
5657
event.sender.send('titlebar-menu', menu);
5758
});
5859

5960
ipcMain.on('menu-event', (event, commandId) => {
60-
getMenuItemByCommandId(commandId)?.click(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
61+
const item = getMenuItemByCommandId(commandId);
62+
item?.click(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
6163
});
6264

6365
ipcMain.on('window-minimize', function (event) {
@@ -77,30 +79,79 @@ ipcMain.on('window-is-maximized', function (event) {
7779
event.returnValue = BrowserWindow.fromWebContents(event.sender).isMaximized()
7880
})
7981

80-
const getCircularReplacer = () => {
81-
const seen = new WeakSet();
82+
const parseMenu = () => {
83+
const menu = new WeakSet();
8284
return (key, value) => {
8385
if (key === 'commandsMap') return;
8486
if (typeof value === 'object' && value !== null) {
85-
if (seen.has(value)) {
86-
return;
87-
}
88-
seen.add(value);
87+
if (menu.has(value)) return;
88+
menu.add(value);
8989
}
9090
return value;
9191
};
92+
}
93+
94+
const getMenuItemByCommandId = (commandId, menu = Menu.buildFromTemplate(exampleMenuTemplate())) => {
95+
let menuItem;
96+
menu.items.forEach(item => {
97+
if (item.submenu) {
98+
const submenuItem = getMenuItemByCommandId(commandId, item.submenu);
99+
if (submenuItem) menuItem = submenuItem;
100+
}
101+
if (item.commandId === commandId) menuItem = item;
102+
});
103+
104+
return menuItem;
92105
};
93106

94-
const getMenuItemByCommandId = (commandId, menu = Menu.getApplicationMenu()) => {
95-
for (let i = 0; i < menu.items.length; i++) {
96-
const item = menu.items[i];
97-
if (item.commandId === commandId) {
98-
return item;
99-
} else if (item.submenu) {
100-
const result = getMenuItemByCommandId(commandId, item.submenu);
101-
if (result) {
102-
return result;
107+
const exampleMenuTemplate = () => [
108+
{
109+
label: "Options",
110+
submenu: [
111+
{
112+
label: "Quit",
113+
click: () => app.quit()
114+
},
115+
{
116+
label: "Checkbox1",
117+
type: "checkbox",
118+
checked: true,
119+
click: (item) => {
120+
console.log("item is checked? " + item.checked);
121+
}
122+
},
123+
{ type: "separator" },
124+
{
125+
label: "Checkbox2",
126+
type: "checkbox",
127+
checked: false,
128+
click: (item) => {
129+
console.log("item is checked? " + item.checked);
130+
}
131+
},
132+
{
133+
label: "Esto es un submenu",
134+
submenu: [
135+
{
136+
label: "Sample Checkbox",
137+
type: "checkbox",
138+
checked: true
139+
},
140+
{ type: "separator" },
141+
{
142+
label: "Checkbox",
143+
type: "checkbox",
144+
}
145+
]
146+
},
147+
{
148+
label: "zoomIn",
149+
role: "zoomIn"
150+
},
151+
{
152+
label: "zoomOut",
153+
role: "zoomOut"
103154
}
104-
}
155+
]
105156
}
106-
};
157+
];

example/preload.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ window.addEventListener('DOMContentLoaded', () => {
1616
}
1717

1818
titlebar = new customTitlebar.Titlebar({
19-
backgroundColor: customTitlebar.Color.fromHex('#2f3241'),
20-
icon: './images/icon.png',
19+
backgroundColor: customTitlebar.Color.fromHex('#2F3241'),
20+
icon: './assets/images/icon.svg',
2121
shadow: true,
2222
onMinimize: () => ipcRenderer.send('window-minimize'),
2323
onMaximize: () => ipcRenderer.send('window-maximize'),

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"dist": "dist"
3333
},
3434
"peerDependencies": {
35-
"electron": "^16.0.5"
35+
"electron": "^16.0.5",
36+
"@types/node": "^17.0.2"
3637
},
3738
"devDependencies": {
3839
"@babel/core": "^7.16.5",

src/menu/menu.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Licensed under the MIT License. See License in the project root for license information.
99
*-------------------------------------------------------------------------------------------------------*/
1010

11-
import { Color } from "../common/color";
11+
import { Color, RGBA } from "../common/color";
1212
import { addClass, addDisposableListener, EventType, isAncestor, hasClass, append, addClasses, $, removeNode, EventHelper, EventLike } from "../common/dom";
1313
import { KeyCode, KeyCodeUtils, KeyMod } from "../common/keyCodes";
1414
import { isLinux } from "../common/platform";
@@ -19,6 +19,7 @@ import { Event, Emitter } from "../common/event";
1919
import { RunOnceScheduler } from "../common/async";
2020
import { MenuItem, Menu } from "electron";
2121
import { MenubarOptions } from "../interfaces";
22+
import icons from "../styles/icons.json";
2223

2324
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
2425
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&amp;)?(&amp;)([^\s&])/g;
@@ -243,6 +244,8 @@ export class CETMenu extends Disposable {
243244
}
244245

245246
createMenu(items: MenuItem[]) {
247+
console.log('createMenu', items);
248+
246249
items.forEach((menuItem: MenuItem) => {
247250
const itemElement = document.createElement('li');
248251
itemElement.className = 'action-item';
@@ -424,7 +427,10 @@ export class CETMenu extends Disposable {
424427
style(style: IMenuStyle) {
425428
const container = this.getContainer();
426429

427-
container.style.backgroundColor = style.backgroundColor ? style.backgroundColor.toString() : null;
430+
const rgba = style.backgroundColor.rgba;
431+
const color = new Color(new RGBA(rgba.r, rgba.g, rgba.b, 0.8));
432+
433+
container.style.backgroundColor = style.backgroundColor ? color.toString() : null;
428434

429435
if (this.items) {
430436
this.items.forEach(item => {
@@ -494,6 +500,7 @@ class Submenu extends CETMenuItem {
494500
this.itemElement.setAttribute('aria-haspopup', 'true');
495501

496502
this.submenuIndicator = append(this.itemElement, $('span.submenu-indicator'));
503+
this.submenuIndicator.innerHTML = icons.arrow;
497504
this.submenuIndicator.setAttribute('aria-hidden', 'true');
498505

499506
this._register(addDisposableListener(this.container, EventType.KEY_UP, e => {
@@ -633,7 +640,7 @@ class Submenu extends CETMenuItem {
633640
const isSelected = this.container && hasClass(this.container, 'focused');
634641
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
635642

636-
this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : null;
643+
//this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : null;
637644

638645
if (this.parentData.submenu) {
639646
this.parentData.submenu.style(this.menuStyle);

src/menu/menuitem.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { KeyCode, KeyCodeUtils } from "../common/keyCodes";
1515
import { Disposable } from "../common/lifecycle";
1616
import { isMacintosh } from "../common/platform";
1717
import { MenubarOptions } from "../interfaces";
18+
import defaultIcons from '../styles/icons.json';
1819

1920
export interface IMenuItem {
2021
render(element: HTMLElement): void;
@@ -37,7 +38,6 @@ export class CETMenuItem extends Disposable implements IMenuItem {
3738

3839
private radioGroup: { start: number, end: number }; // used only if item.type === "radio"
3940
private labelElement: HTMLElement;
40-
private checkElement: HTMLElement;
4141
private iconElement: HTMLElement;
4242
private mnemonic: KeyCode;
4343
protected closeSubMenu: () => void;
@@ -113,9 +113,6 @@ export class CETMenuItem extends Disposable implements IMenuItem {
113113
this.itemElement.setAttribute('aria-keyshortcuts', `${this.mnemonic}`);
114114
}
115115

116-
this.checkElement = append(this.itemElement, $('span.menu-item-check'));
117-
this.checkElement.setAttribute('role', 'none');
118-
119116
this.iconElement = append(this.itemElement, $('span.menu-item-icon'));
120117
this.iconElement.setAttribute('role', 'none');
121118

@@ -273,15 +270,16 @@ export class CETMenuItem extends Disposable implements IMenuItem {
273270
}
274271

275272
updateIcon(): void {
276-
let icon: string | NativeImage | null = null;
277-
278273
if (this.item.icon) {
279-
icon = this.item.icon;
280-
}
274+
const icon = this.item.icon;
281275

282-
if (icon) {
283-
const iconE = append(this.iconElement, $('img'));
284-
iconE.setAttribute('src', icon.toString());
276+
if (icon) {
277+
const iconE = append(this.iconElement, $('img'));
278+
iconE.setAttribute('src', icon.toString());
279+
}
280+
} else if (this.item.type === 'checkbox') {
281+
addClass(this.iconElement, 'checkbox');
282+
this.iconElement.innerHTML = defaultIcons.check;
285283
}
286284
}
287285

@@ -387,13 +385,13 @@ export class CETMenuItem extends Disposable implements IMenuItem {
387385
return;
388386
}
389387

390-
const isSelected = this.container && hasClass(this.container, 'focused');
388+
/*const isSelected = this.container && hasClass(this.container, 'focused');
391389
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
392390
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
393391
394392
this.checkElement.style.backgroundColor = fgColor ? fgColor.toString() : null;
395393
this.itemElement.style.color = fgColor ? fgColor.toString() : null;
396-
this.itemElement.style.backgroundColor = bgColor ? bgColor.toString() : null;
394+
this.itemElement.style.backgroundColor = bgColor ? bgColor.toString() : null;*/
397395
}
398396

399397
style(style: IMenuStyle): void {

src/menubar.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,6 @@ export class Menubar extends Disposable {
145145
}
146146

147147
setupMenubar(): void {
148-
console.log(this.options.menu);
149-
150148
const topLevelMenus = this.options.menu.items;
151149

152150
this._register(this.onFocusStateChange(e => this._onFocusStateChange.fire(e)));
@@ -604,7 +602,7 @@ export class Menubar extends Disposable {
604602
addClass(btnElement, 'open');
605603
menuHolder.setAttribute('role', 'menu');
606604
menuHolder.tabIndex = 0;
607-
menuHolder.style.top = `${btnRect.bottom}px`;
605+
menuHolder.style.top = `${btnRect.bottom - 5}px`;
608606
menuHolder.style.left = `${btnRect.left}px`;
609607

610608
btnElement.appendChild(menuHolder);

src/styles/icons.json

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
{
2-
"mainMenu": "<svg width='10px' height='10px' viewBox='0 0 384 384'><rect x='0' y='277.333' width='384' height='42.667'/><rect x='0' y='170.667' width='384' height='42.667'/><rect x='0' y='64' width='384' height='42.667'/></svg>",
3-
"check": "<svg class='{class}' width='12px' height='12px' viewBox='0 0 512 512'><path d='M504.502,75.496c-9.997-9.998-26.205-9.998-36.204,0L161.594,382.203L43.702,264.311c-9.997-9.998-26.205-9.997-36.204,0c-9.998,9.997-9.998,26.205,0,36.203l135.994,135.992c9.994,9.997,26.214,9.99,36.204,0L504.502,111.7C514.5,101.703,514.499,85.494,504.502,75.496z'/></svg>",
4-
"arrow": "<svg class='{class}' width='20px' height='20px' viewBox='0 0 24 24'><path d='M9.29,6.71L9.29,6.71c-0.39,0.39-0.39,1.02,0,1.41L13.17,12l-3.88,3.88c-0.39,0.39-0.39,1.02,0,1.41l0,0c0.39,0.39,1.02,0.39,1.41,0l4.59-4.59c0.39-0.39,0.39-1.02,0-1.41l-4.59-4.59C10.32,6.32,9.68,6.32,9.29,6.71z'/></svg>",
2+
"check": "<svg viewBox='0 0 11 11'><path d='M3.8,9.3c-0.1,0-0.2,0-0.3-0.1L0.2,5.8C0,5.6,0,5.4,0.2,5.2C0.4,5,0.7,5,0.9,5.2l3,3l6.3-6.3c0.2-0.2,0.5-0.2,0.7,0C11,2,11,2.3,10.8,2.5L4.2,9.1C4.1,9.2,4,9.3,3.8,9.3z'/></svg>",
3+
"arrow": "<svg viewBox='0 0 11 11'><path d='M3.1,10.7c-0.1,0-0.2,0-0.3-0.1c-0.2-0.2-0.2-0.5,0-0.7l4.4-4.4L2.8,1.1c-0.2-0.2-0.2-0.5,0-0.7c0.2-0.2,0.5-0.2,0.7,0l4.8,4.8c0.2,0.2,0.2,0.5,0,0.7l-4.8,4.8C3.4,10.7,3.2,10.7,3.1,10.7z'/></svg>",
54
"win": {
6-
"minimize": "<svg viewBox='0 0 10.2 1'><rect x='0' y='50%' width='10.2' height='1'/></svg>",
7-
"maximize": "<svg viewBox='0 0 10 10'><path d='M0,0v10h10V0H0z M9,9H1V1h8V9z'/></svg>",
8-
"restore": "<svg viewBox='0 0 10.2 10.1'><path d='M2.1,0v2H0v8.1h8.2v-2h2V0H2.1z M7.2,9.2H1.1V3h6.1V9.2z M9.2,7.1h-1V2H3.1V1h6.1V7.1z'/></svg>",
9-
"close": "<svg viewBox='0 0 10 10'><polygon points='10.2,0.7 9.5,0 5.1,4.4 0.7,0 0,0.7 4.4,5.1 0,9.5 0.7,10.2 5.1,5.8 9.5,10.2 10.2,9.5 5.8,5.1'/></svg>"
10-
},
11-
"mac": {
12-
"minimize": "<svg viewBox='0 0 24 24'><path d='M21,13.5H3v-3h18V13.5z'/></svg>",
13-
"maximize": "<svg viewBox='0 0 20 20'><g transform='translate(4,16) scale(0.1,-0.1)'><path d='M74.9,75l44.7-45l0.4,45c0,45,0,45-45.1,45l-44.7,0L74.9,75z'/><path d='M0,44.8C0,0,0,0,45.6,0l45.6,0.4L42.3,46.9L0,89.5V44.8z'/></g></svg>",
14-
"plus": "<svg viewBox='0 0 24 24'><polygon points='10.5,3 10.5,10.5 3,10.5 3,13.5 10.5,13.5 10.5,21 13.5,21 13.5,13.5 21,13.5 21,10.5 13.5,10.5 13.5,3'/></svg>",
15-
"restore": "<svg viewBox='0 0 24 24'><polygon points='12,12 12,1 23,12'/><polygon points='1,12 12,12 12,23'/></svg>",
16-
"close": "<svg viewBox='0 0 24 24'><path d='M20,7.4L16.6,4L12,8.6L7.4,4L4,7.4L8.6,12L4,16.6L7.4,20l4.6-4.6l4.6,4.6l3.4-3.4L15.4,12L20,7.4z'/></svg>"
5+
"minimize": "<svg viewBox='0 0 11 11'><path d='M11,4.9v1.1H0V4.399h11z'/></svg>",
6+
"maximize": "<svg viewBox='0 0 11 11'><path d='M0,1.7v7.6C0,10.2,0.8,11,1.7,11h7.6c0.9,0,1.7-0.8,1.7-1.7V1.7C11,0.8,10.2,0,9.3,0H1.7C0.8,0,0,0.8,0,1.7z M8.8,9.9H2.2c-0.6,0-1.1-0.5-1.1-1.1V2.2c0-0.6,0.5-1.1,1.1-1.1h6.7c0.6,0,1.1,0.5,1.1,1.1v6.7C9.9,9.4,9.4,9.9,8.8,9.9z'/></svg>",
7+
"restore": "<svg viewBox='0 0 11 11'><path d='M7.9,2.2h-7C0.4,2.2,0,2.6,0,3.1v7C0,10.6,0.4,11,0.9,11h7c0.5,0,0.9-0.4,0.9-0.9v-7C8.8,2.6,8.4,2.2,7.9,2.2z M7.7,9.6 c0,0.2-0.1,0.3-0.3,0.3h-6c-0.2,0-0.3-0.1-0.3-0.3v-6c0-0.2,0.1-0.3,0.3-0.3h6c0.2,0,0.3,0.1,0.3,0.3V9.6z'/><path d='M10,0H3.5v1.1h6.1c0.2,0,0.3,0.1,0.3,0.3v6.1H11V1C11,0.4,10.6,0,10,0z'/></svg>",
8+
"close": "<svg viewBox='0 0 11 11'><path d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z'/></svg>"
179
}
1810
}

0 commit comments

Comments
 (0)