Windows titlebar improvements (#1307)

* Make double-clicking on the titlebar work

* We don't actually need this

* Draw a Restore icon when the window is Maximized

* Update React State when BrowserWindow changes behind our back

* Cleanup

* Don't show the border if the window is maximized

* Fight with the linter
This commit is contained in:
Paul Betts 2017-01-10 21:45:49 -08:00 committed by Guillermo Rauch
parent 95e98006c0
commit 0ff1cb9584
8 changed files with 71 additions and 37 deletions

View file

@ -139,7 +139,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'), backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
// we want to go frameless on windows and linux // we want to go frameless on windows and linux
frame: process.platform === 'darwin', frame: process.platform === 'darwin',
transparent: true, transparent: process.platform === 'darwin',
icon: resolve(__dirname, 'static/icon.png'), icon: resolve(__dirname, 'static/icon.png'),
// we only want to show when the prompt is ready for user input // we only want to show when the prompt is ready for user input
// HYPERTERM_DEBUG for backwards compatibility with hyperterm // HYPERTERM_DEBUG for backwards compatibility with hyperterm
@ -346,6 +346,13 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
pluginsUnsubscribe(); pluginsUnsubscribe();
}); });
// Same deal as above, grabbing the window titlebar when the window
// is maximized on Windows results in unmaximize, without hitting any
// app buttons
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore']) {
win.on(ev, () => rpc.emit('windowGeometry change'));
}
win.on('closed', () => { win.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) { if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit(); app.quit();

View file

@ -34,6 +34,19 @@
<use stroke="currentColor" stroke-width="2" mask="url(#maximize-window-b)" xlink:href="#maximize-window-a"/> <use stroke="currentColor" stroke-width="2" mask="url(#maximize-window-b)" xlink:href="#maximize-window-a"/>
</g> </g>
</symbol> </symbol>
<symbol id="restore-window" viewBox="0 0 10.2 10.2">
<title>restore window</title>
<defs>
<mask id="restore-window-b" width="10.2" height="10.2" x="0" y="0">
<use xlink:href="#restore-window-a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="currentColor"
d='M2.1,0v2H0v8.1h8.2v-2h2V0H2.1z M7.2,9.2H1.1V3h6.1V9.2z M9.2,7.1h-1V2H3.1V1h6.1V7.1z' />
<use stroke="currentColor" xlink:href="#restore-window-a"/>
</g>
</symbol>
<symbol id="close-window" viewBox="0 0 10 10"> <symbol id="close-window" viewBox="0 0 10 10">
<title>close window</title> <title>close window</title>
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -24,6 +24,7 @@ import {
UI_MOVE_NEXT_PANE, UI_MOVE_NEXT_PANE,
UI_MOVE_PREV_PANE, UI_MOVE_PREV_PANE,
UI_SHOW_PREFERENCES, UI_SHOW_PREFERENCES,
UI_WINDOW_GEOMETRY_CHANGED,
UI_WINDOW_MOVE, UI_WINDOW_MOVE,
UI_OPEN_FILE UI_OPEN_FILE
} from '../constants/ui'; } from '../constants/ui';
@ -88,6 +89,12 @@ export function setFontSmoothing() {
}; };
} }
export function windowGeometryUpdated() {
return {
type: UI_WINDOW_GEOMETRY_CHANGED
};
}
// Find all sessions that are below the given // Find all sessions that are below the given
// termGroup uid in the hierarchy: // termGroup uid in the hierarchy:
const findChildSessions = (termGroups, uid) => { const findChildSessions = (termGroups, uid) => {
@ -246,6 +253,17 @@ export function windowMove() {
}; };
} }
export function windowGeometryChange() {
return dispatch => {
dispatch({
type: UI_WINDOW_MOVE,
effect() {
dispatch(setFontSmoothing());
}
});
};
}
export function openFile(path) { export function openFile(path) {
return dispatch => { return dispatch => {
dispatch({ dispatch({

View file

@ -12,7 +12,6 @@ export default class Header extends Component {
constructor() { constructor() {
super(); super();
this.onChangeIntent = this.onChangeIntent.bind(this); this.onChangeIntent = this.onChangeIntent.bind(this);
this.handleHeaderClick = this.handleHeaderClick.bind(this);
this.handleHeaderMouseDown = this.handleHeaderMouseDown.bind(this); this.handleHeaderMouseDown = this.handleHeaderMouseDown.bind(this);
this.handleHamburgerMenuClick = this.handleHamburgerMenuClick.bind(this); this.handleHamburgerMenuClick = this.handleHamburgerMenuClick.bind(this);
this.handleMaximizeClick = this.handleMaximizeClick.bind(this); this.handleMaximizeClick = this.handleMaximizeClick.bind(this);
@ -43,35 +42,6 @@ export default class Header extends Component {
this.headerMouseDownWindowY = window.screenY; this.headerMouseDownWindowY = window.screenY;
} }
handleHeaderClick(event) {
this.clicks = this.clicks || 0;
// Reset clicks if mouse moved between clicks
if (this.headerClickPointerX !== event.clientX ||
this.headerClickPointerY !== event.clientY) {
this.clicks = 0;
clearTimeout(this.clickTimer);
this.headerClickPointerX = event.clientX;
this.headerClickPointerY = event.clientY;
}
if (++this.clicks === 2) {
if (this.props.maximized) {
this.props.unmaximize();
} else {
this.props.maximize();
}
this.clicks = 0;
clearTimeout(this.clickTimer);
} else {
// http://www.quirksmode.org/dom/events/click.html
// https://en.wikipedia.org/wiki/Double-click
this.clickTimer = setTimeout(() => {
this.clicks = 0;
}, 500);
}
}
handleHamburgerMenuClick(event) { handleHamburgerMenuClick(event) {
let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect(); let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
x -= 15; // to compensate padding x -= 15; // to compensate padding
@ -135,9 +105,12 @@ export default class Header extends Component {
} }
const {hambMenu, winCtrls} = this.getWindowHeaderConfig(); const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left'; const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized ?
'./dist/assets/icons.svg#restore-window' :
'./dist/assets/icons.svg#maximize-window';
return (<header return (<header
className={css('header', isMac && 'headerRounded')} className={css('header', isMac && 'headerRounded')}
onClick={this.handleHeaderClick}
onMouseDown={this.handleHeaderMouseDown} onMouseDown={this.handleHeaderMouseDown}
> >
{ {
@ -169,7 +142,7 @@ export default class Header extends Component {
className={css('shape', left && 'maximizeWindowLeft')} className={css('shape', left && 'maximizeWindowLeft')}
onClick={this.handleMaximizeClick} onClick={this.handleMaximizeClick}
> >
<use xlinkHref="./dist/assets/icons.svg#maximize-window"/> <use xlinkHref={maxButtonHref}/>
</svg> </svg>
<svg <svg
className={css('shape', 'closeWindow', left && 'closeWindowLeft')} className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
@ -210,6 +183,7 @@ export default class Header extends Component {
left: '1px', left: '1px',
right: '1px', right: '1px',
WebkitAppRegion: 'drag', WebkitAppRegion: 'drag',
WebkitUserSelect: 'none',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center' alignItems: 'center'

View file

@ -12,6 +12,7 @@ export const UI_SHOW_PREFERENCES = 'UI_SHOW_PREFERENCES';
export const UI_WINDOW_MOVE = 'UI_WINDOW_MOVE'; export const UI_WINDOW_MOVE = 'UI_WINDOW_MOVE';
export const UI_WINDOW_MAXIMIZE = 'UI_WINDOW_MAXIMIZE'; export const UI_WINDOW_MAXIMIZE = 'UI_WINDOW_MAXIMIZE';
export const UI_WINDOW_UNMAXIMIZE = 'UI_WINDOW_UNMAXIMIZE'; export const UI_WINDOW_UNMAXIMIZE = 'UI_WINDOW_UNMAXIMIZE';
export const UI_WINDOW_GEOMETRY_CHANGED = 'UI_WINDOW_GEOMETRY_CHANGED';
export const UI_OPEN_FILE = 'UI_OPEN_FILE'; export const UI_OPEN_FILE = 'UI_OPEN_FILE';
export const UI_OPEN_HAMBURGER_MENU = 'UI_OPEN_HAMBURGER_MENU'; export const UI_OPEN_HAMBURGER_MENU = 'UI_OPEN_HAMBURGER_MENU';
export const UI_WINDOW_MINIMIZE = 'UI_WINDOW_MINIMIZE'; export const UI_WINDOW_MINIMIZE = 'UI_WINDOW_MINIMIZE';

View file

@ -97,10 +97,13 @@ class Hyper extends Component {
} }
template(css) { template(css) {
const {isMac, customCSS, borderColor} = this.props; const {isMac, customCSS, borderColor, maximized} = this.props;
const borderWidth = isMac ? '' :
`${maximized ? '0' : '1'}px`;
return (<div> return (<div>
<div <div
style={{borderColor}} style={{borderColor, borderWidth}}
className={css('main', isMac && 'mainRounded')} className={css('main', isMac && 'mainRounded')}
> >
<HeaderContainer/> <HeaderContainer/>
@ -139,7 +142,8 @@ const HyperContainer = connect(
customCSS: state.ui.css, customCSS: state.ui.css,
borderColor: state.ui.borderColor, borderColor: state.ui.borderColor,
activeSession: state.sessions.activeUid, activeSession: state.sessions.activeUid,
backgroundColor: state.ui.backgroundColor backgroundColor: state.ui.backgroundColor,
maximized: state.ui.maximized
}; };
}, },
dispatch => { dispatch => {

View file

@ -120,6 +120,10 @@ rpc.on('move', () => {
store_.dispatch(uiActions.windowMove()); store_.dispatch(uiActions.windowMove());
}); });
rpc.on('windowGeometry change', () => {
store_.dispatch(uiActions.windowGeometryUpdated());
});
rpc.on('add notification', ({text, url, dismissable}) => { rpc.on('add notification', ({text, url, dismissable}) => {
store_.dispatch(addNotificationMessage(text, url, dismissable)); store_.dispatch(addNotificationMessage(text, url, dismissable));
}); });

View file

@ -1,3 +1,4 @@
import {remote} from 'electron';
import Immutable from 'seamless-immutable'; import Immutable from 'seamless-immutable';
import {decorateUIReducer} from '../utils/plugins'; import {decorateUIReducer} from '../utils/plugins';
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config'; import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
@ -6,7 +7,8 @@ import {
UI_FONT_SIZE_RESET, UI_FONT_SIZE_RESET,
UI_FONT_SMOOTHING_SET, UI_FONT_SMOOTHING_SET,
UI_WINDOW_MAXIMIZE, UI_WINDOW_MAXIMIZE,
UI_WINDOW_UNMAXIMIZE UI_WINDOW_UNMAXIMIZE,
UI_WINDOW_GEOMETRY_CHANGED
} from '../constants/ui'; } from '../constants/ui';
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications'; import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
import { import {
@ -86,8 +88,11 @@ const initial = Immutable({
showWindowControls: '' showWindowControls: ''
}); });
const currentWindow = remote.getCurrentWindow();
const reducer = (state = initial, action) => { const reducer = (state = initial, action) => {
let state_ = state; let state_ = state;
let isMax;
switch (action.type) { // eslint-disable-line default-case switch (action.type) { // eslint-disable-line default-case
case CONFIG_LOAD: case CONFIG_LOAD:
@ -284,6 +289,14 @@ const reducer = (state = initial, action) => {
state_ = state.set('maximized', false); state_ = state.set('maximized', false);
break; break;
case UI_WINDOW_GEOMETRY_CHANGED:
isMax = currentWindow.isMaximized();
if (state.maximized !== isMax) {
state_ = state.set('maximized', isMax);
}
break;
case NOTIFICATION_DISMISS: case NOTIFICATION_DISMISS:
state_ = state.merge({ state_ = state.merge({
notifications: { notifications: {