Study & Project ✏️/electron 🐣

[라즈베리파이 크로스컴파일] 2. electron darkmode 예제

JM 2022. 1. 3. 16:11
반응형

출처. https://www.electronjs.org/docs/latest/tutorial/dark-mode

 

Dark Mode | Electron

"Native interfaces" include the file picker, window border, dialogs, context menus, and more - anything where the UI comes from your operating system and not from your app. The default behavior is to opt into this automatic theming from the OS.

www.electronjs.org

앞서 공부했던 mainProcess, rendererProcess와

ipc, 프로그램의 전체적인 Process를 공부해보자!!

 

ps. 항상 출처를 앞에 다는 이유는 내가 비전공자이기 때문에 정확하지 않은 정보를 제공할 수 있으므로!!


html button에서 toggle 된 값을 가지고 css theme을 변경해주는 예제다.

우선 nativeTheme.shouldUseDarkColors가 true일 때 접근 가능하다고 API문서에서 설명하고 있다.

https://www.electronjs.org/docs/latest/api/native-theme#nativethemethemesource

 

nativeTheme | Electron

Read and respond to changes in Chromium's native color theme.

www.electronjs.org

이 점을 기억하고 시작해보자.

 

html에서 버튼과 글을 추가해준다.

//index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <link href="./styles.css" rel="stylesheet">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    
    //main.js에서 innerHTML로 접근할 예정
    <p>Current Theme Source: <strong id="theme-source">System</strong><p>
    
    //renderer.js에서 이벤트를 읽을 예정
    <button id="toggle-dark-mode">Toggle Dark Mode</button>
    <button id="reset-to-system">Reset to System Theme</button>
    
    <!-- You can also require other files to run in this process -->
    <script src="renderer.js"></script>
  </body>
</html>

css에서 실제로 바꿔줄 값을 지정해준다.

/* styles.css */
@media (prefers-color-scheme: dark) {
    body { background: #333; color: white;
}

@media (prefers-color-scheme: light) {
    body { background: #ddd; color: black;
}

main.js의 window 객체가 darkMode를 호출할 수 있게

preload.js에 contextBridge를 이용해서 darkMode를 추가해준다.

이 contextBridge안의 채널인 toggle과 system을 통해 신호를 주고 받는다.

신호는 dark-mode-toggle, dark-mode-system이다.

//preload.js
const { contextBridge, ipcRenderer } = require('electron')
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
contextBridge.exposeInMainWorld('darkMode', {
    toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
    system: () => ipcRenderer.invoke('dark-mode:system')
})

그럼 이 채널을 가지고 renderer.js에서 main.js에게 특정 행동을 요청하고 html 소스도 수정한다.

여기서 main.js에게 특정 행동을 요청할 땐 window Object를 이용한다.

//renderer.js
document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
    const isDarkMode = await window.darkMode.toggle()
    document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
})

document.getElementById('reset-to-system').addEventListener('click', async () => {
    await window.darkMode.system()
    document.getElementById('theme-source').innerHTML = 'System'
})

최종적으로 renderer.js에게 받은 요청을 ipcMain.handle을 통해 메인 프로세스에서 처리하게 된다.

//main.js
const {app, BrowserWindow, ipcMain, nativeTheme} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')

  ipcMain.handle('dark-mode:toggle', () => {
    if(nativeTheme.shouldUseDarkColors) {
      nativeTheme.themeSource = 'light'
    }else {
      nativeTheme.themeSource = 'dark'
    }
    return nativeTheme.shouldUseDarkColors
  })

  ipcMain.handle('dark-mode:system', () => {
    nativeTheme.themeSource = 'system'
  })

}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

main.js의 window에서 preload.js의 채널을 통해 받은 명령만 처리하기 위해 이렇게 복잡한 로직이 생긴 것 같다....

이런 식으로 잘 동작한다.


생각보다 복잡한..... 이벤트 처리 방식이다. 이래서 리소스를 많이 차지한다고 하는 건가??

다른 예제들도 많이 해보면서 실력을 키워야겠다.