diff --git a/lime/project/Platform.hx b/lime/project/Platform.hx index bb349e444..3a291980e 100644 --- a/lime/project/Platform.hx +++ b/lime/project/Platform.hx @@ -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"; diff --git a/lime/system/System.hx b/lime/system/System.hx index ca4e4dd6c..e0f1743f0 100644 --- a/lime/system/System.hx +++ b/lime/system/System.hx @@ -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 (); diff --git a/lime/tools/helpers/ElectronHelper.hx b/lime/tools/helpers/ElectronHelper.hx new file mode 100644 index 000000000..c3ac7a092 --- /dev/null +++ b/lime/tools/helpers/ElectronHelper.hx @@ -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]); + } +} diff --git a/lime/tools/platforms/AIRPlatform.hx b/lime/tools/platforms/AIRPlatform.hx index 2655de017..8030154ee 100644 --- a/lime/tools/platforms/AIRPlatform.hx +++ b/lime/tools/platforms/AIRPlatform.hx @@ -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); diff --git a/lime/tools/platforms/ElectronPlatform.hx b/lime/tools/platforms/ElectronPlatform.hx new file mode 100644 index 000000000..5fe577df0 --- /dev/null +++ b/lime/tools/platforms/ElectronPlatform.hx @@ -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 ) { + + 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 (); + + 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 = 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 {} + + +} \ No newline at end of file diff --git a/templates/electron/hxml/Electron.hx b/templates/electron/hxml/Electron.hx new file mode 100644 index 000000000..a8adaed0a --- /dev/null +++ b/templates/electron/hxml/Electron.hx @@ -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 = [ + ::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 +} \ No newline at end of file diff --git a/templates/electron/hxml/debug.hxml b/templates/electron/hxml/debug.hxml new file mode 100644 index 000000000..5351b8b77 --- /dev/null +++ b/templates/electron/hxml/debug.hxml @@ -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 \ No newline at end of file diff --git a/templates/electron/hxml/final.hxml b/templates/electron/hxml/final.hxml new file mode 100644 index 000000000..f5f81be94 --- /dev/null +++ b/templates/electron/hxml/final.hxml @@ -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 \ No newline at end of file diff --git a/templates/electron/hxml/release.hxml b/templates/electron/hxml/release.hxml new file mode 100644 index 000000000..d4b902400 --- /dev/null +++ b/templates/electron/hxml/release.hxml @@ -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 \ No newline at end of file diff --git a/templates/electron/hxswfml.n b/templates/electron/hxswfml.n new file mode 100644 index 000000000..08b25cd86 Binary files /dev/null and b/templates/electron/hxswfml.n differ diff --git a/templates/electron/template/index.html b/templates/electron/template/index.html new file mode 100644 index 000000000..f3da78014 --- /dev/null +++ b/templates/electron/template/index.html @@ -0,0 +1,45 @@ + + + + + + + ::APP_TITLE:: + + + + + ::if favicons::::foreach (favicons):: + ::end::::end:: + + ::if linkedLibraries::::foreach (linkedLibraries):: + ::end::::end:: + + + + + + + + + ::foreach assets::::if (type == "font"):: + ::end::::end:: + +
+ + + + + \ No newline at end of file diff --git a/templates/electron/template/package.json b/templates/electron/template/package.json new file mode 100644 index 000000000..e264c5377 --- /dev/null +++ b/templates/electron/template/package.json @@ -0,0 +1,11 @@ +{ + "name": "::meta.title::", + "version": "::meta.version::", + "main": "ElectronSetup.js", + "scripts": { + "start": "electron ElectronSetup.js" + }, + "devDependencies": { + "electron": "^1.7.9" + } +} diff --git a/tools/CommandLineTools.hx b/tools/CommandLineTools.hx index cf84b9ef3..1925893f6 100644 --- a/tools/CommandLineTools.hx +++ b/tools/CommandLineTools.hx @@ -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; diff --git a/tools/utils/PlatformSetup.hx b/tools/utils/PlatformSetup.hx index 3a97e2345..90b953c73 100644 --- a/tools/utils/PlatformSetup.hx +++ b/tools/utils/PlatformSetup.hx @@ -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 (); + var requiredVariableDescriptions = new Array (); + + 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");