Cross-Platform

some tips by Kilian Valkhof

Kilian Valkhof

"UX Developer"

Partner at Fluxility

In my spare time, I like to make tools for other people, mostly developers.

Trimage
(in Qt)
f.lux linux gui
(in GTK)
FromScratch
(in Electron!)

Electron-before-Electron.

Cross-platform apps are nice

But make your app feel like it belongs on someone's computer.

Using shitty tools sucks

& we have enough of them already

Getting it (mostly) right is not hard

Just pay attention to the details :)

Here's some tips for Electron apps

Copy and Paste on OS X

Doesn't work

...without adding an App Menu that has them.


	if (process.platform === 'darwin') {
    var template = [{
      label: 'FromScratch',
      submenu: [{
        label: 'Quit',
        accelerator: 'CmdOrCtrl+Q',
        click: function() { app.quit(); }
      }]
    }, {
      label: 'Edit',
      submenu: [{
        label: 'Undo',
        accelerator: 'CmdOrCtrl+Z',
        selector: 'undo:'
      }, {
        label: 'Redo',
        accelerator: 'Shift+CmdOrCtrl+Z',
        selector: 'redo:'
      }, {
        type: 'separator'
      }, {
        label: 'Cut',
        accelerator: 'CmdOrCtrl+X',
        selector: 'cut:'
      }, {
        label: 'Copy',
        accelerator: 'CmdOrCtrl+C',
        selector: 'copy:'
      }, {
        label: 'Paste',
        accelerator: 'CmdOrCtrl+V',
        selector: 'paste:'
      }, {
        label: 'Select All',
        accelerator: 'CmdOrCtrl+A',
        selector: 'selectAll:'
      }]
    }];

    var osxMenu = menu.buildFromTemplate(template);
    menu.setApplicationMenu(osxMenu);
  }

...just copy and paste this into your main.js

Specify an icon!

Or your app will look like this on Ubuntu:

Many apps get this wrong.

And it's super easy to fix:


mainWindow = new BrowserWindow({
  title: 'ElectronApp',
  icon: __dirname + '/app/assets/img/icon.png',
};
		

This also gives you a nice little icon in the topleft of your windows app chrome.

UI text is not supposed to be selectable

So prevent selection to make your app feel more native:


.my-ui-text {
	-webkit-user-select:none;
}
		

Ctrl+A selects all selectable text in your app.

You need 3 icons for 3 platforms

Windows: .ICO
OS X: .ICNS
Linux: .PNG

Start with a 1024x1024px png

Linux, check!

to get an .ico, use icotools:

icotool -c icon.png > icon.ico

to get an .icns, use png2icns:

png2icns icon.icns icon.png

Or use a GUI tool such as img2icns (mac) or iconverticons (web, Windows, OS X)

Bonus!

electron-packager doesn't need the extension


					$ electron-packager . MyApp --icon=img/icon --platform=all --arch=all --version=0.36.0 --out=../dist/ --asar

White loading screens belong in browsers

Hide your app until your page has loaded:


var mainWindow = new BrowserWindow({
	title: 'ElectronApp',
	show: false,
};

mainWindow.webContents.on('did-finish-load', function() {
	mainWindow.show();
	mainWindow.focus();
});
		

Preserve your window dimensions

There is a special circle of hell for apps that don't do this.

Prebuilt solutions:

electron-window-state and electron-window-state-manager

Both work, have good documentation and take care of edge cases.

Get something to save your state to:


var JSONStorage = require('node-localstorage').JSONStorage;
var storageLocation = process.env[(process.platform === 'win32') ?
	'USERPROFILE' : 'HOME'] + '/.fromscratch';
global.nodeStorage = new JSONStorage(storageLocation);
				

When opening your app,
try to load in the state:


var windowState = {};
  try {
    windowState = global.nodeStorage.getItem('windowstate');
  } catch (err) {
    // the file is there, but corrupt. Handle appropriately.
  }
				

Set your BrowserWindow dimensions:


var mainWindow = new BrowserWindow({
  title: 'ElectronApp',
  x: windowState.bounds && windowState.bounds.x || undefined,
  y: windowState.bounds && windowState.bounds.y || undefined,
  width: windowState.bounds && windowState.bounds.width || 550,
  height: windowState.bounds && windowState.bounds.height || 450,
});
				

// Restore maximised state if it is set.
// not possible via options so we do it here
if (windowState.isMaximized) {
  mainWindow.maximize();
}
				

Save your current state on move, resize and close:


['resize', 'move', 'close'].forEach(function(e) {
  mainWindow.on(e, function() {
    storeWindowState();
  });
});
 				

var storeWindowState = function() {
  windowState.isMaximized = mainWindow.isMaximized();
  if (!windowState.isMaximized) {
    // only update bounds if the window isn't currently maximized
    windowState.bounds = mainWindow.getBounds();
  }
  global.nodeStorage.setItem('windowstate', windowState);
};
				

Quick fire

For Accelerators (shortcuts) don't use Cmd or Ctrl, just use CmdOrCtrl.

Quick fire

Want to use the system font San Francisco?


body {
  font: caption;
}
				

Quick fire

For an extra native look and feel, use system colors.

Quick fire

Check out Grid Stylesheets for a contraint based layout system (like GTK, Qt or Autolayout)

Quick fire

...Though you can get very far by using calc() in your css.

Thanks!

Questions?

I would love to get feedback, you can find me here:

kilian@kilianvalkhof.com, https://kilianvalkhof.com
/kilianvalkhof, /kilian

Slides will end up somewhere on my blog or on Twitter. Or both!