Skip to content
This repository has been archived by the owner on Jun 29, 2019. It is now read-only.

Implement sophisticated draggable window functionality #270

Merged
merged 9 commits into from
Jun 11, 2018
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build Status](https://travis-ci.org/hainproject/hain.svg)](https://travis-ci.org/hainproject/hain)
[![Join the chat at https://gitter.im/appetizermonster/hain](https://badges.gitter.im/appetizermonster/hain.svg)](https://gitter.im/appetizermonster/hain?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

An `alt+space` launcher for Windows, built with Electron.
An <kbd>alt</kbd>+<kbd>space</kbd> launcher for Windows, built with Electron.

I always dreamed of an alternative to Alfred on Windows, that is made with JavaScript.
so, I made it.
Expand All @@ -27,7 +27,7 @@ I believe the strict syntax can provide more powerful and fast response than to
Go to [Releases](https://github.com/hainproject/hain/releases), then you can download prebuilt binaries.

## Usage
Run and press `alt+space` anywhere.
Run and press <kbd>alt</kbd>+<kbd>space</kbd> anywhere.

## Themes
See [THEMES.md](THEMES.md)
Expand Down
7 changes: 6 additions & 1 deletion app/main/conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ const MAIN_PLUGIN_REPO = path.resolve(`${HAIN_USER_PATH}/plugins`);
const DEV_PLUGIN_REPO = path.resolve(`${HAIN_USER_PATH}/devplugins`);
const THEME_REPO = path.resolve(`${HAIN_USER_PATH}/themes`);

const APP_PREF_ID = 'Hain';
const APP_NAME = 'Hain';

const APP_PREF_ID = 'General';
const WINDOW_PREF_ID = 'Window';
const THEME_PREF_ID = 'Themes';

const PREF_GROUP_APPLICATION = 'Application';
Expand Down Expand Up @@ -58,7 +61,9 @@ module.exports = {
MAIN_PLUGIN_REPO,
DEV_PLUGIN_REPO,
THEME_REPO,
APP_NAME,
APP_PREF_ID,
WINDOW_PREF_ID,
THEME_PREF_ID,
PREF_GROUP_APPLICATION,
PREF_GROUP_PLUGINS,
Expand Down
160 changes: 131 additions & 29 deletions app/main/server/app/ui/main-window.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const lo_debounce = require('lodash.debounce');

const electron = require('electron');
const shell = electron.shell;
const BrowserWindow = electron.BrowserWindow;
Expand All @@ -17,7 +19,7 @@ const ipc = electron.ipcMain;
module.exports = class MainWindow {
constructor(workerProxy, prefManager, themeService) {
this.workerProxy = workerProxy;
this.appPref = prefManager.appPref;
this.windowPref = prefManager.windowPref;
this.themePref = prefManager.themePref;
this.themeService = themeService;

Expand Down Expand Up @@ -46,7 +48,7 @@ module.exports = class MainWindow {
closable: false,
minimizable: false,
maximizable: false,
moveable: false,
movable: this.windowPref.get('windowDraggable'),
resizable: false,
skipTaskbar: true,
transparent: activeThemeObj.themeObj.window.transparent
Expand Down Expand Up @@ -75,12 +77,75 @@ module.exports = class MainWindow {
evt.preventDefault();
});
browserWindow.loadURL(`file://${__dirname}/../../../../dist/index.html`);

// process list of available displays into user-friendly list
const displays = electron.screen.getAllDisplays();
const displayList = [];

for (const i in displays) {
displayList.push([
displays[i].id,
`Display ${parseInt(i, 10) + 1} (${displays[i].size.width}x${
displays[i].size.height
})`
]);
}

// set list of available displays into the preferences UI control
this.windowPref.schema.properties.display.properties.openOnSpecificDisplay.enum = displayList;

// implement lock variable so that we do not attempt to save the window position when the hide event is fired (needed for MacOS)
let doNotSavePosition = false;

// if the window is draggable, observe window move event
if (this.windowPref.get('windowDraggable')) {
browserWindow.on(
'move',
lo_debounce((event) => {
// refocus text input box
this.rpc.call('handleKeyboardFocus');

// if rememberWindowPosition is selected, observe window position changes and store in preferences
if (
this.windowPref.get('rememberWindowPosition') &&
!doNotSavePosition
) {
// get new window position
const newPosition = browserWindow.getPosition();

// if any position is less than 0 (for example when the window is hidden), do not continue
if (Math.min(...newPosition) < 0) {
return;
}

// set new position values into preferences
this.windowPref.model.position.posX = newPosition[0];
this.windowPref.model.position.posY = newPosition[1];

this.windowPref.update(this.windowPref.model);
this.windowPref.commit();
}
}, 200)
);
}

// observe window blur event
browserWindow.on('blur', () => {
if (browserWindow.webContents.isDevToolsOpened()) return;

// set lock variable
doNotSavePosition = true;

// hide the window
this.hide(true);

// reset lock variable
setTimeout(() => {
doNotSavePosition = false;
}, 500);
});

// store internal reference to created BrowserWindow object
this.browserWindow = browserWindow;
}

Expand Down Expand Up @@ -122,12 +187,27 @@ module.exports = class MainWindow {
this.hasWindowShown = true;
}

// center the window in the middle of the screen?
if (!this.browserWindow.isVisible())
windowUtil.centerWindowOnSelectedScreen(
this.browserWindow,
this.appPref.get('openOnActiveDisplay')
);
// set window position
if (!this.browserWindow.isVisible()) {
const positionWindow = this.windowPref.get('position.positionWindow');

// get openOnDisplay preference - if this is set to "specified" window, get the selected specified window ID
let openOnDisplay = this.windowPref.get('display.openOnDisplay');
if (openOnDisplay === 'specified') {
openOnDisplay = this.windowPref.get('display.openOnSpecificDisplay');
}

if (positionWindow === 'specified') {
// center the window in the middle of the screen
windowUtil.positionWindowOnScreen(this.browserWindow, openOnDisplay, [
this.windowPref.model.position.posX,
this.windowPref.model.position.posY
]);
} else {
// set to stored previous position
windowUtil.centerWindowOnScreen(this.browserWindow, openOnDisplay);
}
}

// show the main Hain app window
this.browserWindow.show();
Expand Down Expand Up @@ -193,6 +273,8 @@ module.exports = class MainWindow {
return;
}

let cssStr = '';

this.browserWindow.setSize(
themeObj.themeObj.window.width,
themeObj.themeObj.window.height
Expand All @@ -206,34 +288,54 @@ module.exports = class MainWindow {
);

if (windowColor) {
this.browserWindow.webContents.insertCSS(
`html { background: ${windowColor} !important; }`
);
cssStr += `
html {
background: ${windowColor} !important;
}
`;
}

// allow window to be draggable?
if (this.windowPref.get('windowDraggable')) {
cssStr += `
body {
-webkit-app-region: drag;
user-select: none;
}
[data-draggable="false"] {
-webkit-app-region: no-drag;
}
`;
}

// set scrollbar styling?
if (
typeof themeObj.themeObj.scrollbar.thickness === 'number' &&
typeof themeObj.themeObj.scrollbar.color === 'string'
) {
this.browserWindow.webContents.insertCSS(
`::-webkit-scrollbar {
width: ${themeObj.themeObj.scrollbar.thickness}px !important;
}
::-webkit-scrollbar-track {
background-color: #eaeaea !important;
border-radius: ${themeObj.themeObj.scrollbar.thickness /
2}px !important;
}
::-webkit-scrollbar-thumb {
background-color: ${themeObj.themeObj.scrollbar.color} !important;
border-radius: ${themeObj.themeObj.scrollbar.thickness /
2}px !important;
}
::-webkit-scrollbar-thumb:hover {
background-color: #aaa !important;
}`
);
cssStr += `
::-webkit-scrollbar {
width: ${themeObj.themeObj.scrollbar.thickness}px !important;
}
::-webkit-scrollbar-track {
background-color: #eaeaea !important;
border-radius: ${themeObj.themeObj.scrollbar.thickness /
2}px !important;
}
::-webkit-scrollbar-thumb {
background-color: ${themeObj.themeObj.scrollbar.color} !important;
border-radius: ${themeObj.themeObj.scrollbar.thickness /
2}px !important;
}
::-webkit-scrollbar-thumb:hover {
background-color: #aaa !important;
}
`;
}

// if we have created a CSS string, set it into the window
if (cssStr) {
this.browserWindow.webContents.insertCSS(cssStr);
}

this.rpc.call('applyTheme', themeObj.themeObj);
Expand Down
Loading