mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-10 06:06:33 +00:00
ATL-732: Support for static splash screen.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
cff2c95684
commit
1acf13c397
@ -1,7 +1,9 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { ConnectionStatus, ConnectionStatusService } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ElectronWindowService as TheiaElectronWindowService } from '@theia/core/lib/electron-browser/window/electron-window-service';
|
||||
import { SplashService } from '../electron-common/splash-service';
|
||||
|
||||
@injectable()
|
||||
export class ElectronWindowService extends TheiaElectronWindowService {
|
||||
@ -9,6 +11,17 @@ export class ElectronWindowService extends TheiaElectronWindowService {
|
||||
@inject(ConnectionStatusService)
|
||||
protected readonly connectionStatusService: ConnectionStatusService;
|
||||
|
||||
@inject(SplashService)
|
||||
protected readonly splashService: SplashService;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.appStateService.reachedAnyState('initialized_layout').then(() => this.splashService.requestClose());
|
||||
}
|
||||
|
||||
protected shouldUnload(): boolean {
|
||||
const offline = this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE;
|
||||
const detail = offline
|
||||
|
@ -2,6 +2,8 @@ import { ContainerModule } from 'inversify';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'
|
||||
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
|
||||
import { SplashService, splashServicePath } from '../../../electron-common/splash-service';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ElectronWindowService } from '../../electron-window-service';
|
||||
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
|
||||
@ -15,4 +17,5 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);
|
||||
bind(ElectronWindowService).toSelf().inSingletonScope()
|
||||
rebind(WindowService).toService(ElectronWindowService);
|
||||
bind(SplashService).toDynamicValue(context => ElectronIpcConnectionProvider.createProxy(context.container, splashServicePath)).inSingletonScope();
|
||||
});
|
||||
|
@ -0,0 +1,5 @@
|
||||
export const splashServicePath = '/services/splash-service';
|
||||
export const SplashService = Symbol('SplashService');
|
||||
export interface SplashService {
|
||||
requestClose(): Promise<void>;
|
||||
}
|
@ -1,8 +1,17 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { JsonRpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { ElectronConnectionHandler } from '@theia/core/lib/electron-common/messaging/electron-connection-handler';
|
||||
import { ElectronMainApplication as TheiaElectronMainApplication } from '@theia/core/lib/electron-main/electron-main-application';
|
||||
import { SplashService, splashServicePath } from '../electron-common/splash-service';
|
||||
import { SplashServiceImpl } from './splash/splash-service-impl';
|
||||
import { ElectronMainApplication } from './theia/electron-main-application';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMainApplication).toSelf().inSingletonScope();
|
||||
rebind(TheiaElectronMainApplication).toService(ElectronMainApplication);
|
||||
|
||||
bind(SplashServiceImpl).toSelf().inSingletonScope();
|
||||
bind(SplashService).toService(SplashServiceImpl);
|
||||
bind(ElectronConnectionHandler).toDynamicValue(context =>
|
||||
new JsonRpcConnectionHandler(splashServicePath, () => context.container.get(SplashService))).inSingletonScope();
|
||||
});
|
||||
|
172
arduino-ide-extension/src/electron-main/splash/splash-screen.ts
Normal file
172
arduino-ide-extension/src/electron-main/splash/splash-screen.ts
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Troy McKinnon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
// Copied from https://raw.githubusercontent.com/trodi/electron-splashscreen/2f5052a133be021cbf9a438d0ef4719cd1796b75/index.ts
|
||||
|
||||
/**
|
||||
* Module handles configurable splashscreen to show while app is loading.
|
||||
*/
|
||||
|
||||
import { Event } from '@theia/core/lib/common/event';
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
/**
|
||||
* When splashscreen was shown.
|
||||
* @ignore
|
||||
*/
|
||||
let splashScreenTimestamp: number = 0;
|
||||
/**
|
||||
* Splashscreen is loaded and ready to show.
|
||||
* @ignore
|
||||
*/
|
||||
let splashScreenReady = false;
|
||||
/**
|
||||
* Main window has been loading for a min amount of time.
|
||||
* @ignore
|
||||
*/
|
||||
let slowStartup = false;
|
||||
/**
|
||||
* True when expected work is complete and we've closed splashscreen, else user prematurely closed splashscreen.
|
||||
* @ignore
|
||||
*/
|
||||
let done = false;
|
||||
/**
|
||||
* Show splashscreen if criteria are met.
|
||||
* @ignore
|
||||
*/
|
||||
const showSplash = () => {
|
||||
if (splashScreen && splashScreenReady && slowStartup) {
|
||||
splashScreen.show();
|
||||
splashScreenTimestamp = Date.now();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Close splashscreen / show main screen. Ensure screen is visible for a min amount of time.
|
||||
* @ignore
|
||||
*/
|
||||
const closeSplashScreen = (main: Electron.BrowserWindow, min: number): void => {
|
||||
if (splashScreen) {
|
||||
const timeout = min - (Date.now() - splashScreenTimestamp);
|
||||
setTimeout(() => {
|
||||
done = true;
|
||||
if (splashScreen) {
|
||||
splashScreen.isDestroyed() || splashScreen.close(); // Avoid `Error: Object has been destroyed` (#19)
|
||||
splashScreen = null;
|
||||
}
|
||||
if (!main.isDestroyed()) {
|
||||
main.show();
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
/** `electron-splashscreen` config object. */
|
||||
export interface Config {
|
||||
/** Options for the window that is loading and having a splashscreen tied to. */
|
||||
windowOpts: Electron.BrowserWindowConstructorOptions;
|
||||
/**
|
||||
* URL to the splashscreen template. This is the path to an `HTML` or `SVG` file.
|
||||
* If you want to simply show a `PNG`, wrap it in an `HTML` file.
|
||||
*/
|
||||
templateUrl: string;
|
||||
|
||||
/**
|
||||
* Full set of browser window options for the splashscreen. We override key attributes to
|
||||
* make it look & feel like a splashscreen; the rest is up to you!
|
||||
*/
|
||||
splashScreenOpts: Electron.BrowserWindowConstructorOptions;
|
||||
/** Number of ms the window will load before splashscreen appears (default: 500ms). */
|
||||
delay?: number;
|
||||
/** Minimum ms the splashscreen will be visible (default: 500ms). */
|
||||
minVisible?: number;
|
||||
/** Close window that is loading if splashscreen is closed by user (default: true). */
|
||||
closeWindow?: boolean;
|
||||
}
|
||||
/**
|
||||
* The actual splashscreen browser window.
|
||||
* @ignore
|
||||
*/
|
||||
let splashScreen: Electron.BrowserWindow | null;
|
||||
/**
|
||||
* Initializes a splashscreen that will show/hide smartly (and handle show/hiding of main window).
|
||||
* @param config - Configures splashscreen
|
||||
* @returns {BrowserWindow} the main browser window ready for loading
|
||||
*/
|
||||
export const initSplashScreen = (config: Config, onCloseRequested?: Event<void>): BrowserWindow => {
|
||||
const xConfig: Required<Config> = {
|
||||
windowOpts: config.windowOpts,
|
||||
templateUrl: config.templateUrl,
|
||||
splashScreenOpts: config.splashScreenOpts,
|
||||
delay: config.delay ?? 500,
|
||||
minVisible: config.minVisible ?? 500,
|
||||
closeWindow: config.closeWindow ?? true
|
||||
};
|
||||
xConfig.splashScreenOpts.center = true;
|
||||
xConfig.splashScreenOpts.frame = false;
|
||||
xConfig.windowOpts.show = false;
|
||||
const window = new BrowserWindow(xConfig.windowOpts);
|
||||
splashScreen = new BrowserWindow(xConfig.splashScreenOpts);
|
||||
splashScreen.loadURL(`file://${xConfig.templateUrl}`);
|
||||
xConfig.closeWindow && splashScreen.on("close", () => {
|
||||
done || window.close();
|
||||
});
|
||||
// Splashscreen is fully loaded and ready to view.
|
||||
splashScreen.webContents.on("did-finish-load", () => {
|
||||
splashScreenReady = true;
|
||||
showSplash();
|
||||
});
|
||||
// Startup is taking enough time to show a splashscreen.
|
||||
setTimeout(() => {
|
||||
slowStartup = true;
|
||||
showSplash();
|
||||
}, xConfig.delay);
|
||||
if (onCloseRequested) {
|
||||
onCloseRequested(() => closeSplashScreen(window, xConfig.minVisible));
|
||||
} else {
|
||||
window.webContents.on('did-finish-load', () => {
|
||||
closeSplashScreen(window, xConfig.minVisible);
|
||||
});
|
||||
}
|
||||
window.on('closed', () => closeSplashScreen(window, 0)); // XXX: close splash when main window is closed
|
||||
return window;
|
||||
};
|
||||
/** Return object for `initDynamicSplashScreen()`. */
|
||||
export interface DynamicSplashScreen {
|
||||
/** The main browser window ready for loading */
|
||||
main: BrowserWindow;
|
||||
/** The splashscreen browser window so you can communicate with splashscreen in more complex use cases. */
|
||||
splashScreen: Electron.BrowserWindow;
|
||||
}
|
||||
/**
|
||||
* Initializes a splashscreen that will show/hide smartly (and handle show/hiding of main window).
|
||||
* Use this function if you need to send/receive info to the splashscreen (e.g., you want to send
|
||||
* IPC messages to the splashscreen to inform the user of the app's loading state).
|
||||
* @param config - Configures splashscreen
|
||||
* @returns {DynamicSplashScreen} the main browser window and the created splashscreen
|
||||
*/
|
||||
export const initDynamicSplashScreen = (config: Config): DynamicSplashScreen => {
|
||||
return {
|
||||
main: initSplashScreen(config),
|
||||
// initSplashScreen initializes splashscreen so this is a safe cast.
|
||||
splashScreen: splashScreen as Electron.BrowserWindow,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { SplashService } from '../../electron-common/splash-service';
|
||||
|
||||
@injectable()
|
||||
export class SplashServiceImpl implements SplashService {
|
||||
|
||||
protected requested = false;
|
||||
protected readonly onCloseRequestedEmitter = new Emitter<void>();
|
||||
|
||||
get onCloseRequested(): Event<void> {
|
||||
return this.onCloseRequestedEmitter.event;
|
||||
}
|
||||
|
||||
async requestClose(): Promise<void> {
|
||||
if (!this.requested) {
|
||||
this.requested = true;
|
||||
this.onCloseRequestedEmitter.fire()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
.container {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 95%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<p><img src="splash.png"></p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
arduino-ide-extension/src/electron-main/splash/static/splash.png
Normal file
BIN
arduino-ide-extension/src/electron-main/splash/static/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
@ -1,14 +1,23 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { app } from 'electron';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { app, BrowserWindow, BrowserWindowConstructorOptions, screen } from 'electron';
|
||||
import { fork } from 'child_process';
|
||||
import { AddressInfo } from 'net';
|
||||
import { join } from 'path';
|
||||
import { initSplashScreen } from '../splash/splash-screen';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
||||
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
||||
import { ElectronMainApplication as TheiaElectronMainApplication, TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/electron-main-application';
|
||||
import { SplashServiceImpl } from '../splash/splash-service-impl';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
|
||||
protected windows: BrowserWindow[] = [];
|
||||
|
||||
@inject(SplashServiceImpl)
|
||||
protected readonly splashService: SplashServiceImpl;
|
||||
|
||||
async start(config: FrontendApplicationConfig): Promise<void> {
|
||||
// Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit")
|
||||
// See: https://github.com/electron-userland/electron-builder/issues/2468
|
||||
@ -17,6 +26,62 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
return super.start(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this rather than creating `BrowserWindow` instances from scratch, since some security parameters need to be set, this method will do it.
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
async createWindow(asyncOptions: MaybePromise<TheiaBrowserWindowOptions> = this.getDefaultBrowserWindowOptions()): Promise<BrowserWindow> {
|
||||
const options = await asyncOptions;
|
||||
let electronWindow: BrowserWindow | undefined;
|
||||
if (this.windows.length) {
|
||||
electronWindow = new BrowserWindow(options);
|
||||
} else {
|
||||
const { bounds } = screen.getDisplayNearestPoint(screen.getCursorScreenPoint());
|
||||
const splashHeight = 450;
|
||||
const splashWidth = 600;
|
||||
const splashY = Math.floor(bounds.y + (bounds.height - splashHeight) / 2);
|
||||
const splashX = Math.floor(bounds.x + (bounds.width - splashWidth) / 2);
|
||||
const splashScreenOpts: BrowserWindowConstructorOptions = {
|
||||
height: splashHeight,
|
||||
width: splashWidth,
|
||||
x: splashX,
|
||||
y: splashY,
|
||||
transparent: true,
|
||||
alwaysOnTop: true,
|
||||
focusable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
hasShadow: false,
|
||||
resizable: false
|
||||
};
|
||||
electronWindow = initSplashScreen({
|
||||
windowOpts: options,
|
||||
templateUrl: join(__dirname, '..', '..', '..', 'src', 'electron-main', 'splash', 'static', 'splash.html'),
|
||||
delay: 0,
|
||||
minVisible: 2000,
|
||||
splashScreenOpts
|
||||
}, this.splashService.onCloseRequested);
|
||||
}
|
||||
this.windows.push(electronWindow);
|
||||
electronWindow.on('closed', () => {
|
||||
if (electronWindow) {
|
||||
const index = this.windows.indexOf(electronWindow);
|
||||
if (index === -1) {
|
||||
console.warn(`Could not dispose browser window: '${electronWindow.title}'.`);
|
||||
} else {
|
||||
this.windows.splice(index, 1);
|
||||
electronWindow = undefined;
|
||||
}
|
||||
}
|
||||
})
|
||||
this.attachReadyToShow(electronWindow);
|
||||
this.attachSaveWindowState(electronWindow);
|
||||
this.attachGlobalShortcuts(electronWindow);
|
||||
this.restoreMaximizedState(electronWindow, options);
|
||||
return electronWindow;
|
||||
}
|
||||
|
||||
protected async getDefaultBrowserWindowOptions(): Promise<TheiaBrowserWindowOptions> {
|
||||
const options = await super.getDefaultBrowserWindowOptions();
|
||||
return {
|
||||
|
@ -7,7 +7,7 @@
|
||||
"indent": [true, "spaces"],
|
||||
"max-line-length": [true, 180],
|
||||
"no-trailing-whitespace": false,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-expression": false,
|
||||
"no-var-keyword": true,
|
||||
"one-line": [true,
|
||||
"check-open-brace",
|
||||
|
Loading…
x
Reference in New Issue
Block a user