mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
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:
parent
95e98006c0
commit
0ff1cb9584
8 changed files with 71 additions and 37 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue