|
@@ -47,18 +47,18 @@ of this theming, due to the use of the macOS 10.14 SDK.
|
|
|
|
|
|
## Example
|
|
|
|
|
|
-We'll start with a working application from the
|
|
|
-[Quick Start Guide](quick-start.md) and add functionality gradually.
|
|
|
+This example demonstrates an Electron application that derives its theme colors from the
|
|
|
+`nativeTheme`. Additionally, it provides theme toggle and reset controls using IPC channels.
|
|
|
|
|
|
-First, let's edit our interface so users can toggle between light and dark
|
|
|
-modes. This basic UI contains buttons to change the `nativeTheme.themeSource`
|
|
|
-setting and a text element indicating which `themeSource` value is selected.
|
|
|
-By default, Electron follows the system's dark mode preference, so we
|
|
|
-will hardcode the theme source as "System".
|
|
|
+```javascript fiddle='docs/fiddles/features/macos-dark-mode'
|
|
|
|
|
|
-Add the following lines to the `index.html` file:
|
|
|
+```
|
|
|
+
|
|
|
+### How does this work?
|
|
|
|
|
|
-```html
|
|
|
+Starting with the `index.html` file:
|
|
|
+
|
|
|
+```html title='index.html'
|
|
|
<!DOCTYPE html>
|
|
|
<html>
|
|
|
<head>
|
|
@@ -80,65 +80,70 @@ Add the following lines to the `index.html` file:
|
|
|
</html>
|
|
|
```
|
|
|
|
|
|
-Next, add [event listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
|
|
|
-that listen for `click` events on the toggle buttons. Because the `nativeTheme`
|
|
|
-module only exposed in the Main process, you need to set up each listener's
|
|
|
-callback to use IPC to send messages to and handle responses from the Main
|
|
|
-process:
|
|
|
+And the `style.css` file:
|
|
|
+
|
|
|
+```css title='style.css'
|
|
|
+@media (prefers-color-scheme: dark) {
|
|
|
+ body { background: #333; color: white; }
|
|
|
+}
|
|
|
|
|
|
-* when the "Toggle Dark Mode" button is clicked, we send the
|
|
|
-`dark-mode:toggle` message (event) to tell the Main process to trigger a theme
|
|
|
-change, and update the "Current Theme Source" label in the UI based on the
|
|
|
-response from the Main process.
|
|
|
-* when the "Reset to System Theme" button is clicked, we send the
|
|
|
-`dark-mode:system` message (event) to tell the Main process to use the system
|
|
|
-color scheme, and update the "Current Theme Source" label to `System`.
|
|
|
+@media (prefers-color-scheme: light) {
|
|
|
+ body { background: #ddd; color: black; }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+The example renders an HTML page with a couple elements. The `<strong id="theme-source">`
|
|
|
+ element shows which theme is currently selected, and the two `<button>` elements are the
|
|
|
+ controls. The CSS file uses the [`prefers-color-scheme`][prefers-color-scheme] media query
|
|
|
+ to set the `<body>` element background and text colors.
|
|
|
+
|
|
|
+The `preload.js` script adds a new API to the `window` object called `darkMode`. This API
|
|
|
+ exposes two IPC channels to the renderer process, `'dark-mode:toggle'` and `'dark-mode:system'`.
|
|
|
+ It also assigns two methods, `toggle` and `system`, which pass messages from the renderer to the
|
|
|
+ main process.
|
|
|
+
|
|
|
+```js title='preload.js'
|
|
|
+const { contextBridge, ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+contextBridge.exposeInMainWorld('darkMode', {
|
|
|
+ toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
|
|
|
+ system: () => ipcRenderer.invoke('dark-mode:system')
|
|
|
+})
|
|
|
+```
|
|
|
|
|
|
-To add listeners and handlers, add the following lines to the `renderer.js` file:
|
|
|
+Now the renderer process can communicate with the main process securely and perform the necessary
|
|
|
+ mutations to the `nativeTheme` object.
|
|
|
|
|
|
-```javascript
|
|
|
-const { ipcRenderer } = require('electron')
|
|
|
+The `renderer.js` file is responsible for controlling the `<button>` functionality.
|
|
|
|
|
|
+```js title='renderer.js'
|
|
|
document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
|
|
|
- const isDarkMode = await ipcRenderer.invoke('dark-mode:toggle')
|
|
|
+ const isDarkMode = await window.darkMode.toggle()
|
|
|
document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
|
|
|
})
|
|
|
|
|
|
document.getElementById('reset-to-system').addEventListener('click', async () => {
|
|
|
- await ipcRenderer.invoke('dark-mode:system')
|
|
|
+ await window.darkMode.system()
|
|
|
document.getElementById('theme-source').innerHTML = 'System'
|
|
|
})
|
|
|
```
|
|
|
|
|
|
-If you run your code at this point, you'll see that your buttons don't do
|
|
|
-anything just yet, and your Main process will output an error like this when
|
|
|
-you click on your buttons:
|
|
|
-`Error occurred in handler for 'dark-mode:toggle': No handler registered for 'dark-mode:toggle'`
|
|
|
-This is expected — we haven't actually touched any `nativeTheme` code yet.
|
|
|
+Using `addEventListener`, the `renderer.js` file adds `'click'` [event listeners][event-listeners]
|
|
|
+ to each button element. Each event listener handler makes calls to the respective `window.darkMode`
|
|
|
+ API methods.
|
|
|
|
|
|
-Now that we're done wiring the IPC from the Renderer's side, the next step
|
|
|
-is to update the `main.js` file to handle events from the Renderer process.
|
|
|
+Finally, the `main.js` file represents the main process and contains the actual `nativeTheme` API.
|
|
|
|
|
|
-Depending on the received event, we update the
|
|
|
-[`nativeTheme.themeSource`](../api/native-theme.md#nativethemethemesource)
|
|
|
-property to apply the desired theme on the system's native UI elements
|
|
|
-(e.g. context menus) and propagate the preferred color scheme to the Renderer
|
|
|
-process:
|
|
|
-
|
|
|
-* Upon receiving `dark-mode:toggle`, we check if the dark theme is currently
|
|
|
-active using the `nativeTheme.shouldUseDarkColors` property, and set the
|
|
|
-`themeSource` to the opposite theme.
|
|
|
-* Upon receiving `dark-mode:system`, we reset the `themeSource` to `system`.
|
|
|
-
|
|
|
-```javascript
|
|
|
+```js
|
|
|
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
|
|
|
+const path = require('path')
|
|
|
|
|
|
function createWindow () {
|
|
|
const win = new BrowserWindow({
|
|
|
width: 800,
|
|
|
height: 600,
|
|
|
webPreferences: {
|
|
|
- nodeIntegration: true
|
|
|
+ preload: path.join(__dirname, 'preload.js')
|
|
|
}
|
|
|
})
|
|
|
|
|
@@ -154,44 +159,41 @@ function createWindow () {
|
|
|
})
|
|
|
|
|
|
ipcMain.handle('dark-mode:system', () => {
|
|
|
- nativeTheme.themeSource = 'system'
|
|
|
+ nativeTheme.themeSouce = 'system'
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-app.whenReady().then(createWindow)
|
|
|
+app.whenReady().then(() => {
|
|
|
+ createWindow()
|
|
|
+
|
|
|
+ app.on('activate', () => {
|
|
|
+ if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
+ createWindow()
|
|
|
+ }
|
|
|
+ })
|
|
|
+})
|
|
|
|
|
|
app.on('window-all-closed', () => {
|
|
|
if (process.platform !== 'darwin') {
|
|
|
app.quit()
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
-app.on('activate', () => {
|
|
|
- if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
- createWindow()
|
|
|
- }
|
|
|
-})
|
|
|
```
|
|
|
|
|
|
-The final step is to add a bit of styling to enable dark mode for the web parts
|
|
|
-of the UI by leveraging the [`prefers-color-scheme`][prefer-color-scheme] CSS
|
|
|
-attribute. The value of `prefers-color-scheme` will follow your
|
|
|
-`nativeTheme.themeSource` setting.
|
|
|
-
|
|
|
-Create a `styles.css` file and add the following lines:
|
|
|
+The `ipcMain.handle` methods are how the main process responds to the click events from the buttons
|
|
|
+ on the HTML page.
|
|
|
|
|
|
-```css fiddle='docs/fiddles/features/macos-dark-mode'
|
|
|
-@media (prefers-color-scheme: dark) {
|
|
|
- body { background: #333; color: white; }
|
|
|
-}
|
|
|
+The `'dark-mode:toggle'` IPC channel handler method checks the `shouldUseDarkColors` boolean property,
|
|
|
+ sets the corresponding `themeSource`, and then returns the current `shouldUseDarkColors` property.
|
|
|
+ Looking back on the renderer process event listener for this IPC channel, the return value from this
|
|
|
+ handler is utilized to assign the correct text to the `<strong id='theme-source'>` element.
|
|
|
|
|
|
-@media (prefers-color-scheme: light) {
|
|
|
- body { background: #ddd; color: black; }
|
|
|
-}
|
|
|
-```
|
|
|
+The `'dark-mode:system'` IPC channel handler method assigns the string `'system'` to the `themeSource`
|
|
|
+ and returns nothing. This also corresponds with the relative renderer process event listener as the
|
|
|
+ method is awaited with no return value expected.
|
|
|
|
|
|
-After launching the Electron application, you can change modes or reset the
|
|
|
-theme to system default by clicking corresponding buttons:
|
|
|
+Run the example using Electron Fiddle and then click the "Toggle Dark Mode" button; the app should
|
|
|
+ start alternating between a light and dark background color.
|
|
|
|
|
|

|
|
|
|
|
@@ -199,4 +201,5 @@ theme to system default by clicking corresponding buttons:
|
|
|
[electron-forge]: https://www.electronforge.io/
|
|
|
[electron-packager]: https://github.com/electron/electron-packager
|
|
|
[packager-darwindarkmode-api]: https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html#darwindarkmodesupport
|
|
|
-[prefer-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
|
|
|
+[prefers-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
|
|
|
+[event-listeners]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|