アラートダイアログ上にさまざまなGUI部品を配置してUIを作成する「箱庭UI」シリーズ、WkWebViewを配置してWeb GL+three.jsによる3Dアニメーションコンテンツを表示するAppleScriptです。
# macOS 10.14.6上のWkWebViewで途中のアップデートから、mouse dragのイベントが取れなくなっています。macOS 10.15および11.0では問題なくmouse dragのイベントを取得できているのですが、、、、。ちなみに、Safariで表示させた場合には問題ありません。同様にWkWebViewを使っているとおぼしきFileMaker Pro v19のWebViewerも同様にドラッグ操作が抜けるので、自分が間抜けなせいではないと思いま
# macOS 13.3beta上では、SafariでもWkWebViewでもWebGL+three.jsで作られているこの内容を表示できません。全世界のデベロッパーがWkWebView上でWebGL+three.jsのコンテンツが動かない(表示できない)ことを確認しています。
→ 最新のmacOSで動くバージョンを掲載アラートダイアログ上にWebViewで3Dコンテンツを表示(WebGL+three.js)v3
多項目選択メニュー部品のようであり、画面下部にあるTABLE(少しデータをいじくってPIYOMARU表示)、SPHERE、HELIX、GRIDのボタンから表示メニュー形態を選択可能になっています。操作は、マウスのドラッグとマウスのスクロール(トラックパッドでもスクロール操作を)です。
ひととおり、最初のTABLE表示部分の部品表示座標データをいじくって、表示色をいろいろ変更してみて、表示データをAppleScript側のリスト変数で与えるところまでやりかけて、リストからJSON文字列に問題なく変換できているものの、実際にHTMLコンテンツに合成して再生まで行くと何も表示されないという状況です。何も考えずに横長のデータを生成したので、そのあたり横幅に制限があったりするのかもしれません。
球体にDOM要素をテクスチャマッピングするSPHERE表示では、データ数が想定どおりの個数ないと球体の表示が成立しないため、そのあたりにデモプログラムっぽさが垣間見えます。
現状、ただ表示するだけで項目選択などは行えない状況ですが、これを強化して項目選択UIとして育てていくのか、Xcode上のアプリケーションに統合する方向でブラッシュアップするのか、いろいろ協議した結果、現状で既存の選択UIより高い実用性を確保できない、という結論に至ったため「AppleScriptでここまでやってみた」という技術的なデモンストレーションとしてBlog上で公開しておくぐらいのものだろうという話に。
その後、FileMaker ProのWebViewer上で内容可変+クリック受信機能つきの3D Rotationメニューを実装できるところまで内容の解析ができました。
AppleScript名:アラートダイアログ上にWebViewで3Dコンテンツを表示(WebGL+three.js).scptd |
— – Created by: Takaaki Naganoya – Created on: 2020/06/13 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use framework "WebKit" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSAlert : a reference to current application’s NSAlert property NSString : a reference to current application’s NSString property NSButton : a reference to current application’s NSButton property WKWebView : a reference to current application’s WKWebView property WKUserScript : a reference to current application’s WKUserScript property NSURLRequest : a reference to current application’s NSURLRequest property NSRunningApplication : a reference to current application’s NSRunningApplication property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property WKUserContentController : a reference to current application’s WKUserContentController property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd property returnCode : 0 –https://www.cresco.co.jp/blog/entry/7427/ — By sgi-chang @ UX Design Center set myStr to "<!DOCTYPE html> <html> <head> <meta charset=\"utf-8\"> <meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0\"> <link type=\"text/css\" rel=\"stylesheet\" href=\"https://threejs.org/examples/main.css\"> <style> a { color: #8ff; } #menu { position: absolute; bottom: 20px; width: 100%; text-align: center; } .element { width: 120px; height: 160px; box-shadow: 0px 0px 12px rgba(0, 255, 255, 0.5); border: 1px solid rgba(127, 255, 255, 0.25); font-family: Helvetica, sans-serif; text-align: center; line-height: normal; cursor: default; } .element:hover { box-shadow: 0px 0px 12px rgba(0, 255, 255, 0.75); border: 1px solid rgba(127, 255, 255, 0.75); } .element .number { position: absolute; top: 20px; right: 20px; font-size: 12px; color: rgba(127, 255, 255, 0.75); } .element .symbol { position: absolute; top: 40px; left: 0px; right: 0px; font-size: 60px; font-weight: bold; color: rgba(255, 255, 255, 0.75); text-shadow: 0 0 10px rgba(0, 255, 255, 0.95); } .element .details { position: absolute; bottom: 15px; left: 0px; right: 0px; font-size: 12px; color: rgba(127, 255, 255, 0.75); } button { color: rgba(127, 255, 255, 0.75); background: transparent; outline: 1px solid rgba(127, 255, 255, 0.75); border: 0px; padding: 5px 10px; cursor: pointer; } button:hover { background-color: rgba(0, 255, 255, 0.5); } button:active { color: #000000; background-color: rgba(0, 255, 255, 0.75); } </style> </head> <body> <script src=\"https://threejs.org/examples/jsm/controls/TrackballControls.js\"></script> <script src=\"https://threejs.org/examples/jsm/renderers/CSS3DRenderer.js\"></script> <div id=\"info\"><a href=\"http://piyocast.com/as\" target=\"_blank\">AppleScript 3D UI Demonstration</a> By Piyomaru Software</div> <div id=\"container\"></div> <div id=\"menu\"> <button id=\"table\">TABLE</button> <button id=\"sphere\">SPHERE</button> <button id=\"helix\">HELIX</button> <button id=\"grid\">GRID</button> </div> <!– <script> –> <script type=\"module\"> import * as THREE from ’https://threejs.org/build/three.module.js’; import { TWEEN } from ’https://threejs.org/examples/jsm/libs/tween.module.min.js’; import { TrackballControls } from ’https://threejs.org/examples/jsm/controls/TrackballControls.js’; import { CSS3DRenderer, CSS3DObject } from ’https://threejs.org/examples/jsm/renderers/CSS3DRenderer.js’; var table = [ \"P\", \"Hydrogen\", \"1.00794\", 1, 1, \"P\", \"Helium\", \"4.002602\", 1, 2, \"P\", \"Lithium\", \"6.941\", 1, 3, \"P\", \"Beryllium\", \"9.012182\", 1, 4, \"P\", \"Boron\", \"10.811\", 1, 5, \"P\", \"Carbon\", \"12.0107\", 2, 1, \"P\", \"Nitrogen\", \"14.0067\", 2, 3, \"P\", \"Oxygen\", \"15.9994\", 3, 1, \"P\", \"Fluorine\", \"18.9984032\", 3, 3, \"P\", \"Flerovium\", \"(289)\", 4, 2, \"I\", \"Moscovium\", \"(290)\", 6, 1, \"I\", \"Livermorium\", \"(293)\", 6, 2, \"I\", \"Tennessine\", \"(294)\", 6, 3, \"I\", \"Titanium\", \"47.867\", 6, 4, \"I\", \"Vanadium\", \"50.9415\", 6, 5, \"Y\", \"Chromium\", \"51.9961\", 8, 1, \"Y\", \"Manganese\", \"54.938045\", 9, 2, \"Y\", \"Iron\", \"55.845\", 10, 3, \"Y\", \"Cobalt\", \"58.933195\", 10, 4, \"Y\", \"Nickel\", \"58.6934\", 10, 5, \"Y\", \"Copper\", \"63.546\", 11, 2, \"Y\", \"Zinc\", \"65.38\", 12, 1, \"O\", \"Gallium\", \"69.723\", 14, 1, \"O\", \"Copernicium\", \"(285)\", 14, 2, \"O\", \"Nihonium\", \"(286)\", 14, 3, \"O\", \"Oganesson\", \"(294)\", 14, 4, \"O\", \"Neon\", \"20.1797\", 14, 5, \"O\", \"Sodium\", \"22.98976…\", 15, 1, \"O\", \"Magnesium\", \"24.305\", 15, 5, \"O\", \"Aluminium\", \"26.9815386\", 16, 1, \"O\", \"Silicon\", \"28.0855\", 16, 5, \"O\", \"Phosphorus\", \"30.973762\", 17, 1, \"O\", \"Sulfur\", \"32.065\", 17, 2, \"O\", \"Chlorine\", \"35.453\", 17, 3, \"O\", \"Argon\", \"39.948\", 17, 4, \"O\", \"Potassium\", \"39.948\", 17, 5, \"M\", \"Calcium\", \"40.078\", 1, 7, \"M\", \"Scandium\", \"44.955912\", 1, 8, \"M\", \"Roentgenium\", \"(280)\", 1, 9, \"M\", \"Germanium\", \"72.63\", 1, 10, \"M\", \"Lead\", \"207.2\", 1, 11, \"M\", \"Arsenic\", \"74.9216\", 2, 8, \"M\", \"Selenium\", \"78.96\", 3, 9, \"M\", \"Bromine\", \"79.904\", 3, 10, \"M\", \"Krypton\", \"83.798\", 4, 8, \"M\", \"Rubidium\", \"85.4678\", 5, 7, \"M\", \"Strontium\", \"87.62\", 5, 8, \"M\", \"Yttrium\", \"88.90585\", 5, 9, \"M\", \"Zirconium\", \"91.224\", 5, 10, \"M\", \"Niobium\", \"92.90628\", 5, 11, \"A\", \"Molybdenum\", \"95.96\", 7,8, \"A\", \"Technetium\", \"(98)\", 7, 9, \"A\", \"Ruthenium\", \"101.07\", 7, 10, \"A\", \"Rhodium\", \"102.9055\",7, 11, \"A\", \"Palladium\", \"106.42\", 8, 7, \"A\", \"Silver\", \"107.8682\", 8,9, \"A\", \"Cadmium\", \"112.411\", 9, 7, \"A\", \"Indium\", \"114.818\", 9, 9, \"A\", \"Tin\", \"118.71\", 10, 8, \"A\", \"Antimony\", \"121.76\", 10, 9, \"A\", \"Gadolinium\", \"157.25\", 10, 10, \"A\", \"Terbium\", \"158.92535\", 10, 11, \"R\", \"Dysprosium\", \"162.5\", 12, 7, \"R\", \"Holmium\", \"164.93032\", 12, 8, \"R\", \"Erbium\", \"167.259\", 12, 9, \"R\", \"Thulium\", \"168.93421\", 12, 10, \"R\", \"Ytterbium\", \"173.054\", 12, 11, \"R\", \"Lutetium\", \"174.9668\", 13, 7, \"R\", \"Hafnium\", \"178.49\", 13, 9, \"R\", \"Samarium\", \"150.36\", 14, 7, \"R\", \"Europium\", \"151.964\", 14, 9, \"R\", \"Tantalum\", \"180.94788\", 15, 8, \"R\", \"Tungsten\", \"183.84\", 15, 10, \"R\", \"Rhenium\", \"186.207\", 15, 11, \"U\", \"Osmium\", \"190.23\", 17, 7, \"U\", \"Iridium\", \"192.217\", 17,8, \"U\", \"Platinum\", \"195.084\", 17, 9, \"U\", \"Gold\", \"196.966569\", 17, 10, \"U\", \"Mercury\", \"200.59\", 18, 11, \"U\", \"Thallium\", \"204.3833\", 19, 11, \"U\", \"Bismuth\", \"208.9804\", 20, 7, \"U\", \"Polonium\", \"(209)\", 20, 8, \"U\", \"Astatine\", \"(210)\", 20, 9, \"U\", \"Francium\", \"(223)\", 20, 10, \"U\", \"Radium\", \"(226)\", 22, 9, \"U\", \"Actinium\", \"(227)\", 22, 10, \"U\", \"Thorium\", \"232.03806\", 22, 11, \"A\", \"Protactinium\", \"231.0588\", 22, 7, \"A\", \"Uranium\", \"238.02891\", 23, 9, \"A\", \"Neptunium\", \"(237)\", 23, 8, \"A\", \"Plutonium\", \"(244)\", 23, 9, \"A\", \"Americium\", \"(243)\", 23, 10, \"A\", \"Curium\", \"(247)\", 23, 11, \"S\", \"Berkelium\", \"(247)\", 24, 7, \"S\", \"Californium\", \"(251)\", 24, 8, \"S\", \"Einstenium\", \"(252)\", 24, 9, \"S\", \"Fermium\", \"(257)\", 24, 11, \"S\", \"Mendelevium\", \"(258)\", 25, 7, \"S\", \"Nobelium\", \"(259)\", 25, 9, \"S\", \"Lawrencium\", \"(262)\", 25, 11, \"S\", \"Rutherfordium\", \"(267)\", 26, 7, \"S\", \"Dubnium\", \"(268)\", 26, 9, \"S\", \"Seaborgium\", \"(271)\", 26, 10, \"S\", \"Bohrium\", \"(272)\", 26, 11, \"A\", \"Hassium\", \"(270)\", 27, 8, \"B\", \"Meitnerium\", \"(276)\", 27, 9, \"C\", \"Darmstadium\", \"(281)\", 27, 8, \"D\", \"Tellurium\", \"127.6\", 27, 9, \"E\", \"Iodine\", \"126.90447\", 27, 10, \"F\", \"Xenon\", \"131.293\", 28, 9, \"G\", \"Caesium\", \"132.9054\", 28, 10, \"H\", \"Barium\", \"132.9054\", 28, 11, \"I\", \"Lanthanum\", \"138.90547\", 29, 8, \"J\", \"Cerium\", \"140.116\", 29, 9, \"K\", \"Praseodymium\", \"140.90765\", 29, 10, \"L\", \"Neodymium\", \"144.242\", 29, 8, \"M\", \"Promethium\", \"(145)\", 29, 9, \"PS\", \"Piyomaru Software\", \"(PiyoPiyo)\", 29, 10, \"AS\", \"AppleScript\", \"(osalang)\", 29, 11, ]; var camera, scene, renderer; var controls; var objects = []; var targets = { table: [], sphere: [], helix: [], grid: [] }; init(); animate(); function init() { camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000); camera.position.z = 3000; scene = new THREE.Scene(); // table for (var i = 0; i < table.length; i += 5) { var element = document.createElement(’div’); element.className = ’element’; //element.style.backgroundColor = ’rgba(128,0,64,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’; //element.style.backgroundColor = ’rgba(64,0,128,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’; //element.style.backgroundColor = ’rgba(0,0,0,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’; element.style.backgroundColor = ’rgba(0,127,127,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’; //element.style.backgroundColor = ’rgba(18,77,174,’ + (Math.random() * 0.5 + 0.25) + ’)’; var number = document.createElement(’div’); number.className = ’number’; number.textContent = (i / 5) + 1; element.appendChild(number); var symbol = document.createElement(’div’); symbol.className = ’symbol’; symbol.textContent = table[i]; element.appendChild(symbol); var details = document.createElement(’div’); details.className = ’details’; details.innerHTML = table[i + 1] + ’<br>’ + table[i + 2]; element.appendChild(details); var object = new CSS3DObject(element); object.position.x = Math.random() * 4000 – 2000; object.position.y = Math.random() * 4000 – 2000; object.position.z = Math.random() * 4000 – 2000; scene.add(object); objects.push(object); // var object = new THREE.Object3D(); object.position.x = (table[i + 3] * 140) – 1330; object.position.y = – (table[i + 4] * 180) + 990; targets.table.push(object); } // sphere var vector = new THREE.Vector3(); for (var i = 0, l = objects.length; i < l; i++) { var phi = Math.acos(- 1 + (2 * i) / l); var theta = Math.sqrt(l * Math.PI) * phi; var object = new THREE.Object3D(); object.position.setFromSphericalCoords(800, phi, theta); vector.copy(object.position).multiplyScalar(2); object.lookAt(vector); targets.sphere.push(object); } // helix var vector = new THREE.Vector3(); for (var i = 0, l = objects.length; i < l; i++) { var theta = i * 0.175 + Math.PI; var y = – (i * 8) + 450; var object = new THREE.Object3D(); object.position.setFromCylindricalCoords(900, theta, y); vector.x = object.position.x * 2; vector.y = object.position.y; vector.z = object.position.z * 2; object.lookAt(vector); targets.helix.push(object); } // grid for (var i = 0; i < objects.length; i++) { var object = new THREE.Object3D(); object.position.x = ((i % 5) * 400) – 800; object.position.y = (- (Math.floor(i / 5) % 5) * 400) + 800; object.position.z = (Math.floor(i / 25)) * 1000 – 2000; targets.grid.push(object); } // renderer = new CSS3DRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.getElementById(’container’).appendChild(renderer.domElement); // controls = new TrackballControls(camera, renderer.domElement); controls.minDistance = 500; controls.maxDistance = 6000; controls.addEventListener(’change’, render); var button = document.getElementById(’table’); button.addEventListener(’click’, function () { transform(targets.table, 2000); }, false); var button = document.getElementById(’sphere’); button.addEventListener(’click’, function () { transform(targets.sphere, 2000); }, false); var button = document.getElementById(’helix’); button.addEventListener(’click’, function () { transform(targets.helix, 2000); }, false); var button = document.getElementById(’grid’); button.addEventListener(’click’, function () { transform(targets.grid, 2000); }, false); transform(targets.table, 2000); // window.addEventListener(’resize’, onWindowResize, false); } function transform(targets, duration) { TWEEN.removeAll(); for (var i = 0; i < objects.length; i++) { var object = objects[i]; var target = targets[i]; new TWEEN.Tween(object.position) .to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration) .easing(TWEEN.Easing.Exponential.InOut) .start(); new TWEEN.Tween(object.rotation) .to({ x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration) .easing(TWEEN.Easing.Exponential.InOut) .start(); } new TWEEN.Tween(this) .to({}, duration * 2) .onUpdate(render) .start(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); render(); } function animate() { requestAnimationFrame(animate); TWEEN.update(); controls.update(); } function render() { renderer.render(scene, camera); } </script> </body> </html>" set paramObj to {myMessage:"WebGL & three.js Test", mySubMessage:"This is a WebGL UI using charts.js", htmlStr:myStr}–my browseStrWebContents:paramObj–for debug my performSelectorOnMainThread:"browseStrWebContents:" withObject:(paramObj) waitUntilDone:true on browseStrWebContents:paramObj set aMainMes to myMessage of paramObj set aSubMes to mySubMessage of paramObj set htmlString to (htmlStr of paramObj) set aWidth to 1600 set aHeight to 950 –WebViewをつくる set aConf to WKWebViewConfiguration’s alloc()’s init() –指定HTML内のJavaScriptをFetch set jsSource to pickUpFromToStr(htmlString, "<script src", "</script>") of me set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true set userContentController to WKUserContentController’s alloc()’s init() userContentController’s addUserScript:(userScript) aConf’s setUserContentController:userContentController set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight)) configuration:aConf aWebView’s setNavigationDelegate:me aWebView’s setUIDelegate:me aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true using terms from scripting additions set bURL to |NSURL|’s fileURLWithPath:(POSIX path of (path to me)) end using terms from aWebView’s loadHTMLString:htmlString baseURL:(bURL) — set up alert set theAlert to NSAlert’s alloc()’s init() tell theAlert its setMessageText:aMainMes its setInformativeText:aSubMes its addButtonWithTitle:"OK" –its addButtonWithTitle:"Cancel" its setAccessoryView:aWebView set myWindow to its |window| end tell — show alert in modal loop NSRunningApplication’s currentApplication()’s activateWithOptions:0 my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true –Stop Web View Action set bURL to |NSURL|’s URLWithString:"about:blank" set bReq to NSURLRequest’s requestWithURL:bURL aWebView’s loadRequest:bReq if (my returnCode as number) = 1001 then error number -128 end browseStrWebContents: on doModal:aParam set (my returnCode) to (aParam’s runModal()) as number end doModal: on viewDidLoad:aNotification return true end viewDidLoad: on fetchJSSourceString(aURL) set jsURL to |NSURL|’s URLWithString:aURL set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value) return jsSourceString end fetchJSSourceString on pickUpFromToStr(aStr as string, s1Str as string, s2Str as string) set a1Offset to offset of s1Str in aStr if a1Offset = 0 then return false set bStr to text (a1Offset + (length of s1Str)) thru -1 of aStr set a2Offset to offset of s2Str in bStr if a2Offset = 0 then return false set cStr to text 1 thru (a2Offset – (length of s2Str)) of bStr return cStr as string end pickUpFromToStr –リストを任意のデリミタ付きでテキストに on retArrowText(aList, aDelim) set aText to "" set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to aDelim set aText to aList as text set AppleScript’s text item delimiters to curDelim return aText end retArrowText on array2DToJSONArray(aList) set anArray to current application’s NSMutableArray’s arrayWithArray:aList set jsonData to current application’s NSJSONSerialization’s dataWithJSONObject:anArray options:(0 as integer) |error|:(missing value) –0 is set resString to current application’s NSString’s alloc()’s initWithData:jsonData encoding:(current application’s NSUTF8StringEncoding) return resString end array2DToJSONArray on parseByDelim(aData, aDelim) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to aDelim set dList to text items of aData set AppleScript’s text item delimiters to curDelim return dList end parseByDelim |