Add target support for Electron (#1157)

* add initial support for Electron
This commit is contained in:
Pete Shand
2018-06-01 07:18:29 +10:00
committed by Joshua Granick
parent dccc2da7cb
commit e45f0dd51d
14 changed files with 726 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ package lime.project;
var CONSOLE_PC = "console-pc";
var FIREFOX = "firefox";
var FLASH = "flash";
var ELECTRON = "electron";
var HTML5 = "html5";
var IOS = "ios";
var LINUX = "linux";

View File

@@ -20,7 +20,7 @@ import flash.Lib;
import flash.desktop.NativeApplication;
#end
#if (js && html5)
#if ((js && html5) || electron)
import js.html.Element;
import js.Browser;
#end
@@ -316,7 +316,7 @@ class System {
#if flash
return flash.Lib.getTimer ();
#elseif (js && !nodejs)
#elseif ((js && !nodejs) || electron)
return Std.int (Browser.window.performance.now ());
#elseif (!disable_cffi && !macro)
return cast NativeCFFI.lime_system_get_timer ();

View File

@@ -0,0 +1,19 @@
package lime.tools.helpers;
import lime.tools.helpers.PlatformHelper;
import lime.tools.helpers.ProcessHelper;
import lime.project.HXProject;
import utils.PlatformSetup;
class ElectronHelper {
public static function launch (project:HXProject, path:String):Void {
var electronPath = project.defines.get ("ELECTRON");
if (electronPath == null || electronPath == "") electronPath = "electron";
else electronPath = electronPath + "\\electron";
var exitCode = ProcessHelper.runCommand ("", electronPath, [path]);
}
}

View File

@@ -219,7 +219,7 @@ class AIRPlatform extends FlashPlatform {
var context = generateContext ();
context.OUTPUT_DIR = targetDirectory;
context.AIR_SDK_VERSION = project.config.getString ("air.sdk-version", "25.0");
context.AIR_SDK_VERSION = project.config.getString ("air.sdk-version", "28.0");
var buildNumber = Std.string (context.APP_BUILD_NUMBER);

View File

@@ -0,0 +1,425 @@
package lime.tools.platforms;
import haxe.io.Path;
import haxe.Template;
import lime.tools.helpers.DeploymentHelper;
import lime.tools.helpers.ElectronHelper;
import lime.tools.helpers.FileHelper;
import lime.tools.helpers.HTML5Helper;
import lime.tools.helpers.IconHelper;
import lime.tools.helpers.LogHelper;
import lime.tools.helpers.ModuleHelper;
import lime.tools.helpers.PathHelper;
import lime.tools.helpers.ProcessHelper;
import lime.tools.helpers.WatchHelper;
import lime.project.AssetType;
import lime.project.HXProject;
import lime.project.Icon;
import lime.project.PlatformTarget;
import lime.project.Haxelib;
import sys.io.File;
import sys.FileSystem;
class ElectronPlatform extends PlatformTarget {
private var outputFile:String;
public function new (command:String, _project:HXProject, targetFlags:Map<String, String> ) {
super (command, _project, targetFlags);
this.project.haxelibs.push(new Haxelib("electron"));
initialize (command, _project);
}
public override function build ():Void {
ModuleHelper.buildModules (project, targetDirectory + "/obj", targetDirectory + "/bin");
if (project.app.main != null) {
var type = "release";
if (project.debug) {
type = "debug";
} else if (project.targetFlags.exists ("final")) {
type = "final";
}
var hxml = targetDirectory + "/haxe/" + type + ".hxml";
ProcessHelper.runCommand ("", "haxe", [ hxml ] );
if (noOutput) return;
if (project.targetFlags.exists ("webgl")) {
FileHelper.copyFile (targetDirectory + "/obj/ApplicationMain.js", outputFile);
}
if (project.modules.iterator ().hasNext ()) {
ModuleHelper.patchFile (outputFile);
}
if (project.targetFlags.exists ("minify") || type == "final") {
HTML5Helper.minify (project, targetDirectory + "/bin/" + project.app.file + ".js");
}
}
}
public override function clean ():Void {
if (FileSystem.exists (targetDirectory)) {
PathHelper.removeDirectory (targetDirectory);
}
}
public override function deploy ():Void {
DeploymentHelper.deploy (project, targetFlags, targetDirectory, "electron");
}
public override function display ():Void {
Sys.println (getDisplayHXML ());
}
private function getDisplayHXML ():String {
var hxml = PathHelper.findTemplate (project.templatePaths, "electron/hxml/" + buildType + ".hxml");
var context = project.templateContext;
context.OUTPUT_DIR = targetDirectory;
context.OUTPUT_FILE = outputFile;
var template = new Template (File.getContent (hxml));
return template.execute (context) + "\n-D display";
}
private function initialize (command:String, project:HXProject):Void {
targetDirectory = PathHelper.combine (project.app.path, project.config.getString ("electron.output-directory", "electron"));
trace("targetDirectory = " + targetDirectory);
outputFile = targetDirectory + "/bin/" + project.app.file + ".js";
}
public override function run ():Void {
trace("ElectronHelper.launch");
ElectronHelper.launch (project, targetDirectory + "/bin");
}
public override function update ():Void {
project = project.clone ();
var destination = targetDirectory + "/bin/";
PathHelper.mkdir (destination);
var webfontDirectory = targetDirectory + "/obj/webfont";
var useWebfonts = true;
for (haxelib in project.haxelibs) {
if (haxelib.name == "openfl-html5-dom" || haxelib.name == "openfl-bitfive") {
useWebfonts = false;
}
}
var fontPath;
for (asset in project.assets) {
if (asset.type == AssetType.FONT && asset.targetPath != null) {
if (useWebfonts) {
fontPath = PathHelper.combine (webfontDirectory, Path.withoutDirectory (asset.targetPath));
if (!FileSystem.exists (fontPath)) {
PathHelper.mkdir (webfontDirectory);
FileHelper.copyFile (asset.sourcePath, fontPath);
var originalPath = asset.sourcePath;
asset.sourcePath = fontPath;
HTML5Helper.generateWebfonts (project, asset);
var ext = "." + Path.extension (asset.sourcePath);
var source = Path.withoutExtension (asset.sourcePath);
var extensions = [ ext, ".eot", ".woff", ".svg" ];
for (extension in extensions) {
if (!FileSystem.exists (source + extension)) {
LogHelper.warn ("Could not generate *" + extension + " web font for \"" + originalPath + "\"");
}
}
}
asset.sourcePath = fontPath;
asset.targetPath = Path.withoutExtension (asset.targetPath);
} else {
project.haxeflags.push (HTML5Helper.generateFontData (project, asset));
}
}
}
if (project.targetFlags.exists ("xml")) {
project.haxeflags.push ("-xml " + targetDirectory + "/types.xml");
}
if (LogHelper.verbose) {
project.haxedefs.set ("verbose", 1);
}
ModuleHelper.updateProject (project);
var libraryNames = new Map<String, Bool> ();
for (asset in project.assets) {
if (asset.library != null && !libraryNames.exists (asset.library)) {
libraryNames[asset.library] = true;
}
}
//for (library in libraryNames.keys ()) {
//
//project.haxeflags.push ("-resource " + targetDirectory + "/obj/manifest/" + library + ".json@__ASSET_MANIFEST__" + library);
//
//}
//project.haxeflags.push ("-resource " + targetDirectory + "/obj/manifest/default.json@__ASSET_MANIFEST__default");
var context = project.templateContext;
context.WIN_FLASHBACKGROUND = project.window.background != null ? StringTools.hex (project.window.background, 6) : "";
context.OUTPUT_DIR = targetDirectory;
context.OUTPUT_FILE = outputFile;
if (project.targetFlags.exists ("webgl")) {
context.CPP_DIR = targetDirectory + "/obj";
}
context.favicons = [];
var icons = project.icons;
if (icons.length == 0) {
icons = [ new Icon (PathHelper.findTemplate (project.templatePaths, "default/icon.svg")) ];
}
//if (IconHelper.createWindowsIcon (icons, PathHelper.combine (destination, "favicon.ico"))) {
//
//context.favicons.push ({ rel: "icon", type: "image/x-icon", href: "./favicon.ico" });
//
//}
if (IconHelper.createIcon (icons, 192, 192, PathHelper.combine (destination, "favicon.png"))) {
context.favicons.push ({ rel: "shortcut icon", type: "image/png", href: "./favicon.png" });
}
context.linkedLibraries = [];
for (dependency in project.dependencies) {
if (StringTools.endsWith (dependency.name, ".js")) {
context.linkedLibraries.push (dependency.name);
} else if (StringTools.endsWith (dependency.path, ".js") && FileSystem.exists (dependency.path)) {
var name = Path.withoutDirectory (dependency.path);
context.linkedLibraries.push ("./lib/" + name);
FileHelper.copyIfNewer (dependency.path, PathHelper.combine (destination, PathHelper.combine ("lib", name)));
}
}
for (asset in project.assets) {
var path = PathHelper.combine (destination, asset.targetPath);
if (asset.type != AssetType.TEMPLATE) {
if (asset.type != AssetType.FONT) {
PathHelper.mkdir (Path.directory (path));
FileHelper.copyAssetIfNewer (asset, path);
} else if (useWebfonts) {
PathHelper.mkdir (Path.directory (path));
var ext = "." + Path.extension (asset.sourcePath);
var source = Path.withoutExtension (asset.sourcePath);
var hasFormat = [ false, false, false, false ];
var extensions = [ ext, ".eot", ".svg", ".woff" ];
var extension;
for (i in 0...extensions.length) {
extension = extensions[i];
if (FileSystem.exists (source + extension)) {
FileHelper.copyIfNewer (source + extension, path + extension);
hasFormat[i] = true;
}
}
var shouldEmbedFont = false;
for (embedded in hasFormat) {
if (embedded) shouldEmbedFont = true;
}
var embeddedAssets:Array<Dynamic> = cast context.assets;
for (embeddedAsset in embeddedAssets) {
if (embeddedAsset.type == "font" && embeddedAsset.sourcePath == asset.sourcePath) {
if (shouldEmbedFont) {
var urls = [];
if (hasFormat[1]) urls.push ("url('" + embeddedAsset.targetPath + ".eot?#iefix') format('embedded-opentype')");
if (hasFormat[2]) urls.push ("url('" + embeddedAsset.targetPath + ".svg#my-font-family') format('svg')");
if (hasFormat[3]) urls.push ("url('" + embeddedAsset.targetPath + ".woff') format('woff')");
urls.push ("url('" + embeddedAsset.targetPath + ext + "') format('truetype')");
var fontFace = "\t\t@font-face {\n";
fontFace += "\t\t\tfont-family: '" + embeddedAsset.fontName + "';\n";
if (hasFormat[1]) fontFace += "\t\t\tsrc: url('" + embeddedAsset.targetPath + ".eot');\n";
fontFace += "\t\t\tsrc: " + urls.join (",\n\t\t\t") + ";\n";
fontFace += "\t\t\tfont-weight: normal;\n";
fontFace += "\t\t\tfont-style: normal;\n";
fontFace += "\t\t}\n";
embeddedAsset.cssFontFace = fontFace;
}
break;
}
}
}
}
}
FileHelper.recursiveSmartCopyTemplate (project, "electron/template", destination, context);
if (project.app.main != null) {
FileHelper.recursiveSmartCopyTemplate (project, "haxe", targetDirectory + "/haxe", context);
FileHelper.recursiveSmartCopyTemplate (project, "electron/haxe", targetDirectory + "/haxe", context, true, false);
FileHelper.recursiveSmartCopyTemplate (project, "electron/hxml", targetDirectory + "/haxe", context);
if (project.targetFlags.exists ("webgl")) {
FileHelper.recursiveSmartCopyTemplate (project, "webgl/hxml", targetDirectory + "/haxe", context, true, false);
}
}
for (asset in project.assets) {
var path = PathHelper.combine (destination, asset.targetPath);
if (asset.type == AssetType.TEMPLATE) {
PathHelper.mkdir (Path.directory (path));
FileHelper.copyAsset (asset, path, context);
}
}
}
public override function watch ():Void {
// TODO: Use a custom live reload HTTP server for test/run instead
var dirs = WatchHelper.processHXML (project, getDisplayHXML ());
var command = WatchHelper.getCurrentCommand ();
WatchHelper.watch (project, command, dirs);
}
@ignore public override function install ():Void {}
@ignore public override function rebuild ():Void {}
@ignore public override function trace ():Void {}
@ignore public override function uninstall ():Void {}
}

View File

@@ -0,0 +1,95 @@
import electron.main.App;
import electron.main.BrowserWindow;
class Electron {
public static var window:BrowserWindow;
static function main()
{
var windows:Array<OpenFLWindow> = [
::foreach windows::
{
allowHighDPI: ::allowHighDPI::,
alwaysOnTop: ::alwaysOnTop::,
antialiasing: ::antialiasing::,
background: ::background::,
borderless: ::borderless::,
colorDepth: ::colorDepth::,
depthBuffer: ::depthBuffer::,
display: ::display::,
fullscreen: ::fullscreen::,
hardware: ::hardware::,
height: ::height::,
hidden: #if munit true #else ::hidden:: #end,
maximized: ::maximized::,
minimized: ::minimized::,
parameters: ::parameters::,
resizable: ::resizable::,
stencilBuffer: ::stencilBuffer::,
title: "::title::",
vsync: ::vsync::,
width: ::width::,
x: ::x::,
y: ::y::
},::end::
];
for (i in 0...windows.length)
{
var window:OpenFLWindow = windows[i];
var width:Int = window.width;
var height:Int = window.height;
if (width < 1200) width = 1200;
if (height < 800) height = 800;
var frame:Bool = window.borderless == false;
electron.main.App.on( 'ready', function(e) {
Electron.window = new BrowserWindow( {
fullscreen: window.fullscreen,
frame:frame,
resizable: window.resizable,
alwaysOnTop: window.alwaysOnTop,
width:width,
height:height
} );
Electron.window.on( closed, function() {
if( js.Node.process.platform != 'darwin' )
electron.main.App.quit();
});
Electron.window.loadURL( 'file://' + js.Node.__dirname + '/index.html' );
#if debug
Electron.window.webContents.openDevTools();
#end
});
}
}
}
typedef OpenFLWindow =
{
allowHighDPI:Bool,
alwaysOnTop:Bool,
antialiasing:Int,
background:UInt,
borderless:Bool,
colorDepth:Int,
depthBuffer:Bool,
display:Dynamic,
fullscreen:Bool,
hardware:Dynamic,
height:Int,
hidden: Bool,
maximized:Bool,
minimized:Bool,
parameters:Dynamic,
resizable:Bool,
stencilBuffer:Bool,
title:String,
vsync:Bool,
width:Int,
x:Int,
y:Int
}

View File

@@ -0,0 +1,15 @@
-cp ::OUTPUT_DIR::/haxe
--each
--next
-js ::OUTPUT_FILE::
-main ApplicationMain ::HAXE_FLAGS::
--next
-js ::OUTPUT_DIR::/bin/ElectronSetup.js
-main Electron ::HAXE_FLAGS::
-D html5
-D html
-debug

View File

@@ -0,0 +1,17 @@
-cp ::OUTPUT_DIR::/haxe
--each
--next
-js ::OUTPUT_FILE::
-main ApplicationMain ::HAXE_FLAGS::
--next
-js ::OUTPUT_DIR::/bin/ElectronSetup.js
-main Electron ::HAXE_FLAGS::
-D html5
-D html
-D final
-D js-flatten
-dce full

View File

@@ -0,0 +1,14 @@
-cp ::OUTPUT_DIR::/haxe
--each
--next
-js ::OUTPUT_FILE::
-main ApplicationMain ::HAXE_FLAGS::
--next
-js ::OUTPUT_DIR::/bin/ElectronSetup.js
-main Electron ::HAXE_FLAGS::
-D html5
-D html

Binary file not shown.

View File

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>::APP_TITLE::</title>
<meta id="viewport" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
::if favicons::::foreach (favicons)::
<link rel="::__current__.rel::" type="::__current__.type::" href="::__current__.href::">::end::::end::
::if linkedLibraries::::foreach (linkedLibraries)::
<script type="text/javascript" src="::__current__::"></script>::end::::end::
<script type="text/javascript" src="./::APP_FILE::.js"></script>
<script>
window.addEventListener ("touchmove", function (event) { event.preventDefault (); }, false);
if (typeof window.devicePixelRatio != 'undefined' && window.devicePixelRatio > 2) {
var meta = document.getElementById ("viewport");
meta.setAttribute ('content', 'width=device-width, initial-scale=' + (2 / window.devicePixelRatio) + ', user-scalable=no');
}
</script>
<style>
html,body { margin: 0; padding: 0; height: 100%; overflow: hidden; }
#content { background: #FF0000; width: ::if (WIN_RESIZABLE)::100%::elseif (WIN_WIDTH > 0)::::WIN_WIDTH::px::else::100%::end::; height: ::if (WIN_RESIZABLE)::100%::elseif (WIN_WIDTH > 0)::::WIN_HEIGHT::px::else::100%::end::; }
::foreach assets::::if (type == "font")::::if (cssFontFace)::::cssFontFace::::end::::end::::end::
</style>
</head>
<body>
::foreach assets::::if (type == "font")::
<span style="font-family: ::id::"> </span>::end::::end::
<div id="content"></div>
<script type="text/javascript">
lime.embed ("::APP_FILE::", "content");
</script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
{
"name": "::meta.title::",
"version": "::meta.version::",
"main": "ElectronSetup.js",
"scripts": {
"start": "electron ElectronSetup.js"
},
"devDependencies": {
"electron": "^1.7.9"
}
}

View File

@@ -264,7 +264,6 @@ class CommandLineTools {
}
for (targetName in targets) {
var target = null;
switch (targetName) {
@@ -284,6 +283,10 @@ class CommandLineTools {
target = PlatformHelper.hostPlatform;
targetFlags.set ("nodejs", "");
case "electron":
target = Platform.HTML5;
targetFlags.set ("electron", "");
case "cs":
target = PlatformHelper.hostPlatform;
@@ -717,7 +720,12 @@ class CommandLineTools {
case HTML5:
platform = new HTML5Platform (command, project, targetFlags);
if (targetFlags.exists("electron")) {
platform = new ElectronPlatform (command, project, targetFlags);
}
else {
platform = new HTML5Platform (command, project, targetFlags);
}
case FIREFOX:
@@ -1040,6 +1048,7 @@ class CommandLineTools {
LogHelper.println (" \x1b[1mnodejs\x1b[0m -- Alias for host platform (using \x1b[1m-nodejs\x1b[0m)");
LogHelper.println (" \x1b[1mjava\x1b[0m -- Alias for host platform (using \x1b[1m-java\x1b[0m)");
LogHelper.println (" \x1b[1mcs\x1b[0m -- Alias for host platform (using \x1b[1m-cs\x1b[0m)");
LogHelper.println (" \x1b[1melectron\x1b[0m -- Alias for \x1b[1mhtml5 -electron\x1b[0m");
LogHelper.println (" \x1b[1muwp\x1b[0;3m/\x1b[0m\x1b[1mwinjs\x1b[0m -- Alias for \x1b[1mwindows -uwp\x1b[0m");
// LogHelper.println (" \x1b[1miphone\x1b[0;3m/\x1b[0m\x1b[1miphoneos\x1b[0m -- \x1b[1mios\x1b[0m");
// LogHelper.println (" \x1b[1miphonesim\x1b[0m -- Alias for \x1b[1mios -simulator\x1b[0m");
@@ -1146,6 +1155,7 @@ class CommandLineTools {
if (command != "run" && command != "trace") {
LogHelper.println (" \x1b[3m(html5)\x1b[0m \x1b[1m-electron\x1b[0m -- Target Electron instead of the browser");
LogHelper.println (" \x1b[3m(emscripten)\x1b[0m \x1b[1m-webassembly\x1b[0m -- Compile for WebAssembly instead of asm.js");
}
@@ -1680,6 +1690,11 @@ class CommandLineTools {
target = PlatformHelper.hostPlatform;
targetFlags.set ("nodejs", "");
case "electron":
target = Platform.HTML5;
targetFlags.set ("electron", "");
case "cs":
target = PlatformHelper.hostPlatform;

View File

@@ -515,6 +515,11 @@ class PlatformSetup {
// setupWebOS ();
case "electron":
setupElectron();
case "windows":
if (PlatformHelper.hostPlatform == Platform.WINDOWS) {
@@ -1277,6 +1282,65 @@ class PlatformSetup {
}
public static function setupElectron ():Void {
if (PlatformHelper.hostPlatform != Platform.WINDOWS) return;
var setElectronToPath = false;
var defines = getDefines ();
var answer = CLIHelper.ask ("Download and install Electron?");
var electronPath:String = "";
if (answer == YES || answer == ALWAYS) {
var downloadPath:String = electronWin;
var defaultInstallPath:String = "C:\\_sdks\\electron";
var localPath:String = "";
downloadFile (downloadPath);
localPath = unescapePath (CLIHelper.param ("Output directory [" + defaultInstallPath + "]"));
localPath = createPath (localPath, defaultInstallPath);
extractFile (Path.withoutDirectory (downloadPath), localPath, "");
defines.set ("ELECTRON", localPath);
writeConfig (defines.get ("LIME_CONFIG"), defines);
LogHelper.println ("");
setElectronToPath = true;
}
var requiredVariables = new Array<String> ();
var requiredVariableDescriptions = new Array<String> ();
if (!setElectronToPath) {
requiredVariables.push ("Electron");
requiredVariableDescriptions.push ("Path to Electron");
}
if (!setElectronToPath) {
LogHelper.println ("");
}
var defines = getDefines (requiredVariables, requiredVariableDescriptions, null);
if (defines != null) {
writeConfig (defines.get ("LIME_CONFIG"), defines);
}
HaxelibHelper.runCommand ("", [ "install", "electron" ], true, true);
}
public static function setupWindows ():Void {
LogHelper.println ("\x1b[1mIn order to build native executables for Windows, you must have a");