Categories
Kwik5 consists of the following two tools,
Kwik exporter for Adobe Photoshop UXP plugin
it publishes images(1x, 2x, 4x) and lua files of layer properties (x, y, width, height) to a selected App/book folder of a Solar2D project.
kwik Visual Code editor for Solar2D
the publsiehd project from Kwik exporter has an editing capabiliy on Solar2D simulator
it attaches animation, button etc to a layer, and updates the project codes(lua files) when you save each properteis of kwik components/events(actions).
circled A, B images are helper objects of Kwik to specify a Layer Animation position from pos A to pos B. You can drag A to starting position, B to ending podistion of a target layer.
Lively preview is available when you enter a new value to component properties.
Solar2D simulator automatically reloads itself when there are updated files. (Kwik4 needed publsihing code to update a solar2d project)
the kwik editor can read single or mutiple books project.
You can create a single book app and mutiple books project with the following project structure.
Please set a App/book folder as the output folde when you publsih images/codes with Kwik exporter UXP plugin.
single book
Kwik5 publshies images/codes to App/book folder. (kwik4 publshied everything under build4 folder)
multiple books (bookstore)
You can work each book folder one by one with Kwik exporter(UXP) to publsih images of psd files of each book. So please select one of book folder when you use Kwik exporter
Kwik5 you can publsih each book under App folder directly. (Kwik4 needed to collect each book project to App folder from build4 folder by a batch file for making a bookshelf app.)
Creating assets
Pr
Podcast
Misc
Podcast talk
audacity 47% audition 33.3
https://twitter.com/MomochiYorozu/status/1660251437644017664?t=QD6pQBMi5VPy_-Ik0JYhiw&s=19
Text to audio
https://huggingface.co/spaces/haoheliu/audioldm-text-to-audio-generation
Free Sound Effects
Adobe Audition
How to organize
https://blog.prosoundeffects.com/how-to-find-the-right-sound-effects
https://blog.prosoundeffects.com/how-to-use-sound-effects
Whisper
https://community.openai.com/t/whisper-api-a-timecodes-b-how-good-is-open-source-vs-api/163882/5
https://github.com/m-bain/whisperX
Photoshopの画像生成AIがすごい ついに商用利用もスタートへ
人物を切り抜いて、背景だけを生成させた場合は、AdobeFireflyの方が圧倒的に自然
https://k-tai.watch.impress.co.jp/docs/column/stapa/1497045.html
Automatic1111
https://ascii.jp/elem/000/004/142/4142488/
Linuxの場合、16GBのVRAMを搭載した互換性のあるAMD製グラフィックボード
https://huggingface.co/blog/lora
Low-Rank Adaptation of Large Language Models
Eagle
https://github.com/bbc-mc/sdweb-eagle-pnginfo/blob/master/README.ja.md
https://github.com/Mikubill/sd-webui-controlnet
https://softwarekeep.com/help-center/best-cloud-provider-for-stable-diffusion
ONE PAYMENT FOR 24 HOURS
Azure HPC VMでStable Diffusionする
画像生成AIに2度目の革命を起こした「ControlNet」
https://zenn.dev/karaage0703/articles/bf86fe4946417b
Stable Diffusionなどの画像生成AI、84,800円(税込)のMac miniがコスパ最強
内蔵GPUで爆速動作するよ
アップルが公式移植している
当然ながらGeForce RTX 4090なんかと比べれば劇遅だけど、コスパと消費電力では圧倒している。
mac mini16GBでautomatic1111動かして512x512の画像で1枚40秒
現状3060のほうが断然速いよ RTX3060で4秒なので約10倍
https://note.com/nice_ixia735/n/nc623d9583357
AMDのRadeon系GPU搭載のゲーミングミニPCでAIイラストが生成できる
10枚のイラスト生成にかかった時間は148秒
how to get an audio file
how to get an image
local url = "https://th.bing.com/th/id/OIG.1AwVedDzsmOL2HECayrn?pid=ImgGn"
function M:create(UI)
local layerProps = self.layerProps or _layerProps
--
self.imagePath = layerProps.name.."." .. (layerProps.type or ".png")
local path = UI.props.imgDir..self.imagePath
-- path = system.pathForFile(UI.props.imgDir..self.imagePath, system.ResourceDirectory)
-- print(url)
local function networkListener( event )
if ( event.isError ) then
print ( "Network error - download failed" )
else
local object = event.target
--object.alpha = 0
transition.to( object, { xScale = 0.2, yScale=0.2, alpha = 1.0, onComplete = function()
local function onColorSample( event )
-- print( "Sampling pixel at position (" .. event.x .. "," .. event.y .. ")" )
-- print( "R = " .. event.r )
-- print( "G = " .. event.g )
-- print( "B = " .. event.b )
-- print( "A = " .. event.a )
object.fill.effect = "filter.chromaKey"
object.fill.effect.sensitivity = 0.1
object.fill.effect.smoothing = 0.1
object.fill.effect.color = { event.r,event.g,event.b, event.a }
end
local posX, posY = object.x - object.contentWidth/2+4, object.y-object.contentHeight/2+4
display.colorSample( posX, posY , onColorSample )
end } )
end
-- print ( "event.response.fullPath: ", event.response.fullPath )
-- print ( "event.response.filename: ", event.response.filename )
-- print ( "event.response.baseDirectory: ", event.response.baseDirectory )
end
--
local obj = display.loadRemoteImage(url, "GET", networkListener, "testOne.jpg", system.TemporaryDirectory, display.contentCenterX, display.contentCenterY )
end
how to get particles
how to get a spritesheet
how to get a video file
TODO
library.psd, dialog.psd, textLabels.psd page_custscene.psd
restore.psd showOverlay will be used
library filter
build.settings
library page portrait
restore
scroll view for library/table page
UXP
cp components/bookstore/booktoreX.lua to bookTOC/scenes/library
add filter in library page
Can create a storybook like? navigation bar?
Test versions
Bookstore app contains the multiple kwik projects(books) inside. The lua files of each book are embedded in the app binary but the assets such as images, audio files of each book are not embedded in the app binary. They are downloadable from a web server. Due to Apple’s regulation, Solar2D does not allow to load the lua files (programming code) via Internet. The lua files must be embedded in the app binary but the other assets can be transfered from http server to the app.
A library page controls which book to be loaded with In App Purchase. User clicks a book icon on thubnail view on a library page, and a purchase dialog appears if user wants to buy one. A project called bookTOC(Table Of Content) contains library.psd and dialog.psd
When you like to add new books, you need to update the model.lua of bookTOC to include a new book information.
You can create as many as books but Bookstore is not designed to hold hundreds of books.
Please download the sample project from here.
Photoshop
├── bookFree
│ ├── page1.psd
│ └── page2.psd
├── bookOne
│ ├── page1.psd
│ └── page2.psd
└── bookTOC
├── dialog.psd
└── library.psd
Solar2D
├── App
│ ├── bookFree
│ ├── bookOne
│ └── bookTOC
│
├── components
| ├── bookstore
| └── model.lua
└── main.lua
BookServer
├── compress_assets
| └── main.lua
└── bookstore
├── bookFree
└── bookOne
BooKServer
copy_books script archives each assets.zip of books into BookServer/bookstore folder.
Using node.js, install http-server and run
cd BookServer
http-server
Set the url of http://localhost:8080/bookstore to Solar2D/components/bookstore/model.lua
local M = require("components.bookstore.model.base")
--
M.debug = true
M.URL = "http://localhost:8080/bookstore/"
...
...
Solar2D
Solar2D project has main.lua in the root directory.
├─App
│ ├─book01
│ ├─book02
│ └─bookTOC
├─extlib
├─lib
build.settings
config.lua
main.lua
main.lua
There is just one line to load “bookTOC” book
require("controller.index").bootstrap({name="bookTOC", sceneIndex = 1}) -- scenes.index
App/bookTOC/scenes/index.lua has a table of page names, so sceneIdex = 1 means to open library page.
local scenes = {
"library",
"dialog"
}
return scenes
build.settings
You may need to add permissions for your app. The following permissions are default
android = {
usesPermissions = {
"android.permission.INTERNET",
"android.permission.WRITE_EXTERNAL_STORAGE",
"com.android.vending.BILLING",
},
},
config.lua
please add google license key for android device.
license =
{
google =
{
key = "Please set your google license key",
},
},
library.psd shows the thumbnail of books and the buttons for purchase and download
Each {{bookName}}Icon
it is a placeholder and the following layers are copied to the position of each {{bookName}}Icon
restoreBtn
it restores a book purchased history from one of online store(Apple, Google, Amazon)
dilaog.psd shows a book image and the IAP buttons and information text
page1.psd
page2.psd
shows the following goto buttons
Goto TOC
GOto page1
Goto Next Book
page1.psd
Please use the script to zip the image, audio files as zip files
compress_assets/main.lua for Corona Simulator
command.setServerFolder("macos", "bookstore")
--command.setServerFolder("win32", "bookstore")
local books = {
{project = "bookFree", serverFolder = "bookFree"},
{project = "bookOne", serverFolder = "bookOne"},
}
local onLineImages = {
{project = "bookFree", serverFolder = "bookFree", image = "assets/images/page1/bg@4x.png"},
{project = "bookOne", serverFolder = "bookOne", image = "assets/images/page1/bg@4x.png"},
}
...
...
bookstore consists of the following files and modules
App/bookTOC
├── App
│ ├── bookFree
│ ├── bookOne
│ ├── bookTOC
| ├── components
| | ├── library
| | ├── layers
| | | ├── bg.lua
| | | ├── bookFreeIcon.lua
| | | ├── bookOneIcon.lua
| | | ├── downloadBtn.lua
| | | ├── purchaseBtn.lua
| | | ├── restoreBtn.lua
| | | ├── savedBtn.lua
| | | └── savingTxt.lua
| | ├── page
| | | └── bookstore.lua ⭐️
| | └── index.lua ⭐️
| └── dialog
| └── layers
| ├── bg.lua
| ├── bookIcon.lua
| ├── downloadBtn.lua
| ├── hideOverlayBtn.lua
| ├── infoTxt.lua
| ├── purchaseBtn.lua
| ├── savedBtn.lua
| ├── savingTxt.lua
| └── inddx.lua
│
├── components
├── bookstore
├── controller
├── model
├── smc
├── view
├── index.lua
└── model.lua ⭐️
bookstore component is defined in components.pages table. This calls bookstore.lua
App/bookTOC/scenes/library/index.lua
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "library",
components = {
layers = {
{ bg ={ } },
{ bookIcon ={ } },
...
...
},
audios = { },
groups = { },
timers = { },
variables = { },
pages = { "bookstore" }
},
commands = { },
onInit = function(scene) print("onInit") end
})
--
return scene
App/bookTOC/components/libary/pages/bookstore.lua
local _M = {}
local bookstore = require("components.bookstore.index").new()
--
function _M:init(UI)
end
--
function _M:create(UI)
local sceneGroup = UI.scene.view
local layers = UI.layers
--
--for k, v in pairs(UI.props) do print(k, v) end
bookstore:init(sceneGroup, UI.props)
end
--
function _M:didShow(UI)
local layers = UI.layers
bookstore:setSceneGroup(UI.scene.view)
end
--
--
function _M:toDispose(UI)
bookstore:destory()
end
--
return _M
bookstore.lua calls components.bookstore.model
Please fill the bookstore model with values of each book
compoents/bookstore/index.lua
local M = {}
--
local pageCommand = require("components.bookstore.controller.pageCommand")
local model = require("components.bookstore.model.base")
--
model.debug = true
model.URL = "http://localhost:8080/bookshop/"
-- model.URL = nil means simple IAP store without network download
-- downloadBtn, savingTxt won'T be used. You don't need to create them.
----------
model.TOC = "bookTOC"
model.LIBRARY_PAGES = {en = "scenes.library"}
model.DIALOG_PAGES = {en = "scenes.dialog"}
--
model.name = "catalog01"
--
model.books = {
bookFree = {
name = "bookFree",
versions = {},
titles = {en="bookOne"},
descriptions = {en="desc"},
isFree = true,
isOnlineImg = true,
isDownloadable = true,
image = "App/bookFree/assets/images/page1/bg.png",
productNames = {apple = "bookFree", google = "bookFree", amazon = "bookFree"},
},
bookOne = {
name = "bookOne",
versions = {},
titles = {en="bookOne"},
descriptions = {en="desc"},
isFree = false,
isOnlineImg = true, -- true
isDownloadable = true, -- true
image = "App/bookOne/assets/images/page1/bg.png",
productNames = {apple = "bookOne", google = "bookOne", amazon = "bookOne"},
}
}
--
model.purchaseAlertMessages = {en="Your purchase was successful"}
model.restoreAlertMessages = {en="Your items are being restored"}
model.downloadErrorMessages = {en="Check network alive to download the content"}
model.descriptions = {en=""}
model.titles = {en=""}
--
model.gotoSceneEffect = "slideRight"
model.showOverlayEffect = "slideBottom"
--
M.new = function()
return pageCommand.newBookstore(model)
end
--
M.model = model
--
return M
debug
if true, IAPBadger is in debug mode.
URL
if book assets are downlodable, please set the server url
LIBRARY_PAGES
bookstore shows one of library page with lang value
DIALOG_PAGES
bookstore shows one of dialog with lang value
name
the name for IAP catalog. You may create another model.lua with a differnet name
books
name
versions
you can put language codes for multilingual books.
you may invent a new lang code for versions. For example, instead of language code, use blue, red, gree of colors as version
titles
descriptions
isFree
true or falase
isOnlineImg
if true, thumbnail image is loaded with the following given path in App folder
if false, from URL .."/"..image
image
thumbnail image path
productNames
if debug is false, productNames should be replaced with values of Apple store, Google play, Amazon store
You would like to customize the looks
marker
updateText
components/bookstore/view/marker.lua
local M = {}
--
function M.new (dst, group)
print(dst, group)
if dst.updateMark == nil then
local obj = display.newCircle(0,0,4)
obj.x = dst.x + dst.width/2
obj.y = dst.y - dst.height/2
obj:setFillColor(1,0,0)
group:insert(obj)
dst.updateMark = obj
else
dst.updateMark.alpha = 1
end
end
---
return M
components/bookstore/view/spinner.lua
local M = {}
--
function M.new (host)
local obj = {}
local spinner
obj.host = host
--
function obj:show(host)
if not spinner then
spinner = display.newGroup()
local hostName = host or obj.host or ""
--Place a progress spinner on screen and tell the user the app is contating the store
local spinnerBackground = display.newRect(0,0,360,600)
spinnerBackground:setFillColor(1,1,1,0.75)
--Spinner consumes all taps so the user cannot tap the purchase button twice
spinnerBackground:addEventListener("tap", function() return true end)
local spinnerText = display.newText("Contacting " .. hostName .. "...", 0, -20, native.systemFont, 18)
spinnerText:setFillColor(0,0,0)
--Add a little spinning rectangle
local spinnerRect = display.newRect(0, 0,35,35)
spinnerRect:setFillColor(0, 0)
spinnerRect:setStrokeColor(1,1,1)
spinnerRect.strokeWidth = 2
transition.to(spinnerRect, { time=4000, rotation=360, iterations=999999, transition=easing.inOutQuad})
--Create a group and add all these objects to it
spinner:insert(spinnerBackground)
spinner:insert(spinnerText)
spinner:insert(spinnerRect)
spinner.x, spinner.y = _W/2, _H/2
spinner.spinnerText = spinnerText
end
end
updateText
function obj:updateText()
local percent = self.size/self.bookSize
local sec = os.difftime( os.time(), self.startTime)
local remain = math.floor(sec * (1.0/percent))
local time = os.date("*t", remain)
self.spinnerText.text = math.floor(percent*100).." % (" ..self.size .."/" ..self.bookSize .." Mb) \n left " ..time.min..":"..time.sec
end
These functions are available for page&book navigation. You can set it for an event action.
After published your Book’s projects, you can use compress_assets/main.lua
BookServer/compress_assets/main.lua
the script makes json and zip files
compress_assets/main.lua
local command = require("compress_assets")
command.setServerFolder("macos", "bookstore")
--command.setServerFolder("win32", "bookstore")
-- the name of Kwik project and the name of In App Purchase product
--
local books = {
{project = "Book01", serverFolder = "book01"},
{project = "Book02", serverFolder = "book02"},
}
-- Use Online Images needs an image file
--
local onLineImages = {
{project = "Book01", serverFolder = "book01", image = "assets/images/page1/bg@4x.png"},
{project = "Book02", serverFolder = "book02", image = "assets/images/page1/bg@4x.png"},
}
Notice: For bookstoe with multiple languaes, and Use Online Image is true, a thumbnail image from online server will be requested with lang ID. For instance,
http://localserver:8080/bookshefl/book01en/bg.png
For this case, BookServer needs book01en, book01jp folders. Please modify the script with lang ID for onLineImages table
local onLineImages = {
{project = "Book01", serverFolder = "book01en", image = "assets/images/page1/bg@4x.png"},
{project = "Book01", serverFolder = "book01jp", image = "assets/images/page1/bg@4x.png"},
{project = "Book02", serverFolder = "book02en", image = "assets/images/page1/bg@4x.png"},
{project = "Book02", serverFolder = "book02jp", image = "assets/images/page1/bg@4x.png"},
}
Please prepare a http-server and put the contents of BookServer folder.
http-server
cd BookServer
http-server
http://127.0.0.1:8080
Hit CTRL-C to stop the server
http://localhost:8080/bookstore/book01/assets.json
Please check the url in components/bookstore/model.lua
local YourHost = "http://localhost:8080
M.URL = YourHost.."/bookstore/"
compress_assets/main.lua has command.updateAsset function
--
-- update page1, videos
--
local project = "Book02"
local serverFolder = "book02"
local page = 1
local type = "images"
-- command.updateAsset(project, serverFolder, page, type)
--[[
"audios"
"read2me"
"PNGs"
"sprites"
"particles"
"WWW"
"thumbnails"
"images"
"shared"
]]
After everything all right with simulator, turn off debug mode IAP.lua and set the valid product IDs of apple, google or amazon in the following lua files of BookShelfTOC project. You may edit the files in App/TOC.
components/store/IAP.lua
function M:init(catalogue, restoreAlert, purchaseAlert)
print("iap init")
self.catalogue = catalogue
self.restoreAlert = restoreAlert
self.purchaseAlert = purchaseAlert
local iapOptions = {
catalogue = catalogue,
filename = "episodes_inventory.txt",
--Salt for the hashing algorithm
salt = "something tr1cky to gue55!",
failedListener = failedListener,
cancelledListener = failedListener,
--Once the product has been purchased, it will remain in the inventory. Uncomment the following line
--to test the purchase functions again in future. It's also useful for testing restore purchases.
--doNotLoadInventory=true,
debugMode = true,
}
iap.init(iapOptions)
print("iap init end")
end
components/store/model.lua
M.catalogue = {
products = {
Episode02 = {
productNames = { apple="Episode02_apple", google="Episode02_googgle", amazon="Episode02_amazon"},
productType = "non-consumable",
onPurchase = function() IAP.setInventoryValue("unlock_Episode02") end,
onRefund = function() IAP.removeFromInventory("unlock_Episode02") end,
},
Episode03 = {
productNames = { apple="Episode03_apple", google="Episode03_googgle", amazon="Episode03_amazon"},
productType = "non-consumable",
onPurchase = function() IAP.setInventoryValue("unlock_Episode03") end,
onRefund = function() IAP.removeFromInventory("unlock_Episode03") end,
},
},
inventoryItems = {
unlock_Episode02 = { productType="non-consumable" },
unlock_Episode03 = { productType="non-consumable" },
}
}
Project folders
Photsohop/bookShopX
Solar2D
main.lua
require("controller.index").bootstrap({name="book", sceneIndex = 1}) -- scenes.index {"library", "dialog"}
components/bookstore/model.lua
Host URL of yours if you set up your own server.
local YourHost = "http://localhost:8080"
M.URL = YourHost.."/bookshelf/"
M.backgroundImg = "bg.png"
books model
M.books = {
bookFree = {
name = "bookFree",
versions = {"en", "jp"},
titles = {en="bookOne", jp=""},
descriptions = {en="desc", jp =""},
isFree = true,
isOnlineImg = false,
image = "App/bookFree/assets/images/title/bg.png",
productNames = {apple = "bookFree", google = "bookFree", amazon = "bookFree"},
},
bookOne = {
name = "bookOne",
versions = {"en", "jp"},
titles = {en="bookOne", jp=""},
descriptions = {en="desc",jp=""},
isFree = false,
isOnlineImg = true,
image = "App/bookOne/assets/images/title/bg.png",
productNames = {apple = "bookOne", google = "bookOne", amazon = "bookOne"},
}
}
BookServer
compress_assets/main.lua
compress and copy each assets of books to BookServer/bookstore folder with assets.json
edit the book table and run it with Solar2D simulator
local books = {
{project = "bookFree", serverFolder = "bookFree"},
{project = "bookOne", serverFolder = "bookOne"},
}
local onLineImages = {
{project = "bookFree", serverFolder = "bookFree", image = "assets/images/page1/bg@4x.png"},
{project = "bookOne", serverFolder = "bookOne", image = "assets/images/page1/bg@4x.png"},
}
bookstore folder
run http-server and it check json is available
http://localhost:8080/bookstore/bookFree/assets.json
menu > show sand box folder
you need to clean the following folders if you want to recover the initial state. * Application Support * Documents * TemporaryFiles
For device build, you must set the valid product IDs from apple, google or amazon. For IAP Debug Mode, set the same name value to each filed.
IAP Badger https://github.com/happymongoose/iap_badger
When you test it with debug as true for components/bookstore/model.lua , you need to set the book names as its dummies for the productNames.
Don’t use the official product names from apple, google or amazon. With official IDs, debug mode fails to return a book name and IAP not work correctly.
productNames = {apple = "bookFree", google = "bookFree", amazon = "bookFree"},
if you use http server instead of https, please set your domain in build.settings
NSExceptionDomains
https://docs.coronalabs.com/guide/hardware/appleATS/index.html
It is a feature to download a different language version of a book. A user pays for a book and choose to download a book with his/her language
Modified sample project is here.
library.psd
dialog.psd
BookSever/compress_assets/main.lua
command.setServerFolder("macos", "bookshelf")
--command.setServerFolder("win32", "bookshelf")
local books = {
{project = "Book01en", serverFolder = "book01en"},
{project = "Book01jp", serverFolder = "book01jp"},
{project = "Book02en", serverFolder = "book02en"},
{project = "Book02jp", serverFolder = "book02jp"},
}
local onLineImages = {
{project = "Book01en", serverFolder = "book01en", image = "build4/assets/images/p1/bg@4x.png"},
{project = "Book01jp", serverFolder = "book01jp", image = "build4/assets/images/p1/bg@4x.png"},
{project = "Book02en", serverFolder = "book02en", image = "build4/assets/images/p1/bg@4x.png"},
{project = "Book02jp", serverFolder = "book02jp", image = "build4/assets/images/p1/bg@4x.png"},
}
Solar2D/components/bookstore/model.lua
the versions are added
M.books = {
bookFree = {
name = "bookFree",
versions = {"en", "jp"},
titles = {en="Book Free", jp="ブック フリー"},
descriptions = {en="free", jp ="無料"},
isFree = true,
isOnlineImg = false,
image = "App/bookFree/assets/images/title/bg.png",
productNames = {apple = "bookFree", google = "bookFree", amazon = "bookFree"},
},
bookOne = {
name = "bookOne",
versions = {"en", "jp"},
titles = {en="book", jp="ブック"},
descriptions = {en="$10",jp="1000円"},
isFree = false,
isOnlineImg = true,
image = "App/bookOne/assets/images/title/bg.png",
productNames = {apple = "bookOne_apple", google = "bookOne_google", amazon = "bookOne_amazon"},
}
}
when user purchase template, user can download the default of template or en, jp. Kwik internally downloads template.zip or bookXen/asset zip files or bookXjp/asset zip files
TODO direct open a book according to the language code of the page
when user click purchase button and the transaction is completed, next user needs to click download-version button to get an asset zip of a selected language from Book server
library.psd
purchase button is not available in library page. It is removed (alpha=0)
user can not directly open a book from a thumbnail icon. Click the icon to pop up the dilaog, and then user choose one of version of a book
NFT OpenSea
graphics assets from books
|
Interactive Picture/Video Book
IAP per book download
AI generated video/images/stories
Genre
Artist/Author
Way of Life
to earn Art like DJ/VJ
kwik app
graphics -- nft market
Lulu, possible trees, Will Terry etc
free to play
free to edit a page component and Share & Earn!
playing piano to configure animation parameters
draw to earn needs GPU brushes
HX80G/StableDiffusion/ControlNet
コミティア
Project model
Tools
image exporter
PS/XD UXP
visual editor (frontEnd)
Solar2D
(Maybe) React
REST server
pegasus-harness
pegasus-launcher
frontEnd app sends REST API requests to harness of pegasus lua server
this server offers the renderer of .lua for components and commands
other tools
Sample projects
Image Exporter plugin for Adobe Photoshop
Project Model
sample-projects/Pegasus
test/base-proj/Solar2D/App/bookFree
Visual Editor(React)
{{webview html panel}}
Visual Live Editor(Solar2D)
Solar2D/tools/kwik-editor/App/kwikEditor to be copied into a project? Or make them as components like a AtoB?
books/pages(scenes)/layers
menu
tools/actions with icons
selectPage
selectLayer with filter
selectComponent
add a component to a layer or a page selected
selectAction
list actions
cross references to components
Action Editor
Pegasus & httpYac to get/post properteis of components and actions? yaml props in .http
Copy/(optional)EditRawJSON/Apply
audio/group/timer/var are independent from layers
components w/o events
page
layer replacements
interactions
physics
components with events
timer
audio
layer (replacements)
onComplete
transition
sprite
sync audio text
interactions
button
drag
swipe
pinch
spin
shake
parallax
physics
create ‘select book/page/layer’ menu at the bottom
layers table for a seclection . showOverlay?
layer name with ‘Add Class’ button?
create tools/actions with icons at the bottom
save/load
scene/layers
create?
read
update
crud a class
save
table in lua into json
publish
json to lua
TODO
Tools
develop/Solar2D/tools/pegasus-harness
develop/Solar2D/tools/pegasus-launcher
REST API requests from httpYac are sent to pegasus
generate_scene_index
Exporrter plugin for PS, XD
UXP
traverse photoshop layers
components/pageX/layers/**/*.lua
kwik-exporter\src\photoshop\publishCode.ts
traverse App/contentX/
generate_scene_index\generate_models.lua
merge the both results to ouput components/pageX/index.lua ⭐️
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page01",
components = {
layers = {{bg = {}}, {layerX = {class={button}}}},
audios = {},
groups = {},
others = {},
timers = {},
variables = {}
},
commands = {"bg.clickLayer"},
onInit = function(scene) print("onInit") end
})
return scene
develop/UXP/kwik-exporter
A layer set is exported as one single image if assets/images/pageX has the foler with the same name of the layer set.
export images
export props
develop\UXP\kwik-exporter\plugin\kwik\templates\components\
(TODO) Update develop\Solar2D\template to sync with the two files.
(TODO) UXP/plugin/kwik/template is a clone of template/App/contentX
├─Solar2D
│ ├─robotlegs
│ │ ├─App
│ │ │ ├─book
│ │ │ │ ├─assets
│ │ │ │ │ ├─images
│ │ │ │ │ │ └─page01
│ │ │ │ ├─commands
│ │ │ │ │ └─page01
│ │ │ │ │ └─layers
│ │ │ │ ├─components
│ │ │ │ │ └─page01
│ │ │ │ ├──models
│ │ │ │ └─page01
│ ├─template
│ │ ├─App
│ │ │ └─contentX
│ │ │ ├─assets
│ │ │ ├─commands
│ │ │ │ └─pageX
│ │ │ ├─components
│ │ │ │ ├─pageX
│ │ │ │ │ ├─audios
│ │ │ │ │ ├─layers
│ │ │ │ │ │ ├─animations
│ │ │ │ │ │ ├─images
│ │ │ │ │ │ ├─interactions
│ │ │ │ │ │ ├─physics
│ │ │ │ │ │ └─replacements
│ │ │ │ │ │ ├─particles
│ │ │ │ │ │ ├─sprites
│ │ │ │ │ │ ├─syncAudioText
│ │ │ │ │ │ ├─videos
│ │ │ │ │ │ └─www
│ │ │ │ │ ├─groups
│ │ │ │ │ ├─page
│ │ │ │ │ │ ├─controls
│ │ │ │ │ ├─timers
│ │ │ │ │ └─variables
│ │ │ │ └─store ?
│ │ │ ├─models
│ │ │ │ └─pageX
│ │ │ │ ├─commands
│ │ │ │ └─components
│ └─tools
│ ├─kwik-editor
└─UXP
└─kwik-exporter
├─plugin
│ ├─icons
│ └─kwik
│ ├─templates
│ │ ├─components
│ │ │ └─kwik
│ │ ├─model
│ │ │ ├─components
│ │ │ ├─events
│ │ │ └─layers
│ │ └─scenes
Editor
Solar2D Desktop App
(Maybe) Web App (React)
REST Server
receives Props and Commands and then renders .lua/.json
/develop/Solar2D/tools/pegasus-harness
/develop/Solar2D/tools/pegasus-launcher
Utilities
generating scene/pageX/index.lua
the table in the index.lua is created by interating files in editor.template/components and template/commands
scafolding (optional)
it outputs .lua files to components and commands folder by reading scene/pageX/index.lua
/develop/Solar2D/tools/generate_scene_index
See get_started/custom_class as well
GET
returns .json of layer components(classes) or events/commands. It also returns default values of compoent properties
@host=http://localhost:9090
### run pegasus server if not running, and return books
GET /app
###
GET /bookFree
###
GET /bookFree/page1
### selectLayer
GET /bookFree/page1/title
###
GET /bookFree/page1/title/linear
POST
receives Props of layers and Commands and then renders .lua components/commands. It also stores the request params in .json
### modify layer props
POST /bookFree/page1/title/?command=preview
Content-Type: application/yaml
{
alpha=0.5
}
### save layer props with the current value
POST /bookFree/page1/title/?command=save
Content-Type: application/yaml
PUT
creates a new lua file in App directory if not exist. scafolding a lua file
GET /app
###
GET /newBook
PUT /newBook
PUT /newBook/newPage
### update index.lua too
PUT /newBook/newPage/newLayer
PUT /newBook/newPage/newLayer/?class=linear
### custom class
GET /components/custom
PUT /components/custom/newClass
PUT /newBook/newPage/newLayer/?class=newClass
### events
GET /newBook/commands/newPage
PUT /newBook/commands/newPage/newEvent
DELETE
removes a lua file
###
DELETE /newBook/newPage/newLayer
DELETE /newBook/newPage/newLayer/?class=linear
### custom class?
DELETE /components/custom/newClass
DELETE /newBook/newPage/newLayer/?class=newClass
### events
DELETE /newBook/commands/newPage/newEvent
/develop/Solar2D/tools/yaml2lua
there is a vscode extention to convert yaml to json , json to yaml etc
- name: Loading01
from: from
to:
x: 100
y: 100
alpha: 1
duration: 2000
xScale: 1.5
yScale: 1.5
rotation: 0
controls:
restart: false
easing: Linear
reverse: false
delay: 1000
loop: 1
angle: 45
xSwipe: 0
ySwipe: 0
referncePoint: Center
breadcrumbs:
dispose: true
shape: ""
color:
- 1
- 0
- 1
interval: 300
time: 2000
width: 30
height: 30
lua
{
breadcrumbs = {
color = { 1, 0, 1 },
dispose = true,
height = 30,
interval = 300,
shape = "",
time = 2000,
width = 30
},
controls = {
referncePoint = "Center",
angle = 45,
delay = 1000,
easing = "Linear",
loop = 1,
restart = false,
reverse = false,
xSwipe = 0,
ySwipe = 0
},
from = "from",
name = "Loading01",
to = {
alpha = 1,
duration = 2000,
rotation = 0,
x = 100,
xScale = 1.5,
y = 100,
yScale = 1.5
}
} }
/develop/Solar2D/tools/generate_scene_index (TODO update ⭐️)
generating scene/pageX/index.lua
index.lua is created by iterating files from
scafolding (optional)
it outputs .lua files to components and commands folder by reading scene/pageX/index.lua
UXP panel
flowchart LR
Designer((fas:fa-user Designer))
Developer((fas:fa-user Developer))
User((fas:fa-user User or AI))
subgraph Photoshop[Photoshop UXP]
graphics(images/lua renderer<br>scaffolder)
end
subgraph Editor[Kwik Visual Editor]
subgraph App
assets[(assets/images<br>models/json)]
lua[(Source .lua)]
end
tools(GUI tools<br><br>renderer<br>scaffolder)
httpServer
RestApi(RestApi<br>transform<br>animation)
RestApi -.- tools
end
subgraph RestApi[REST API]
form(Properties <br> CRUD)
end
subgraph VSCode
httpYac(httpYac)
coding
end
Photoshop -.img/json.-> assets
RestApi <-.img/json.-> httpServer
httpServer <-.-> assets
httpServer -.- tools
tools -.- App
User -.- Browser
Browser-.maybe in future.- RestApi
httpYac -.- RestApi
coding -.- lua
RestApi -.- assets
Designer --- tools
Designer --- Photoshop
Developer --- VSCode
Rest api
pegasus is runningin Editor
run Editor
upload an image to pegasus server
Kwik Visual Editor
outputs images/json to App/book
run App/book
Data store
flowchart RL
subgraph Editor[kwik editor]
subgraph nanostores
layerstore[layers]
actions
assets
audios
end
GUI[ menu > Layer]
subgraph asset
assetSelectBox
classProps
buttons
end
subgraph parts
selectors
bookTable
pageTable
layerTable
propsTable
buttons
subgraph controller
selectLayer
end
controller -.- selectors
end
selectors -. 1 click .- GUI
subgraph action
actionTable
actionCommandTable
actionPropsTable
end
subgraph classEditors
subgraph animation
selectBox
classProps
controller
end
audio
end
controller
template
scripts
end
subgraph framework
controller
subgraph components_kwik[components]
bookstore
common[Common <br> myClass]
custom
layer
anim
button
end
command_kwik[command]
plugin
extlib
end
subgraph App/bookX
assets
subgraph components
page(pageX.index <br> - layers <br> - audios <br> - ........)
end
subgraph commands
pageX
end
scene(index.lua returns scenes as pageXs)
end
Select Layer to load the layer table
flowchart RL
subgraph Editor[kwik editor]
GUI[ menu > Layer]
subgraph asset
assetSelectBox
classProps
buttons
end
action
classEditors
subgraph parts
selectors
bookTable
pageTable
layerTable
propsTable
buttons
subgraph controller
selectLayer
end
controller -.- selectors
end
selectors -. 1 click .- GUI
controller
template
scripts
subgraph nanostores
layerstore[layers data]
actions
assets
audios
end
end
subgraph framework
end
subgraph App/bookX
assets
subgraph components
page(pageX.index <br> - layers <br> - audios <br> - ........)
end
subgraph commands
pageX
end
scene(index.lua returns scenes as pageXs)
end
selectLayer -. 2 read .-> components
selectLayer -. 3 set .-> layerstore
layerTable -. 4 listening for createTable .- layerstore
Kwik functions
Command
File
(TBI) Project New
scafold app/bookX
Project Open
Project Recent
(TBI) Page New
bookX/index.lua for user to change the order of pages
local scenes = {
"page1",
"page2",
"page3",
"page4",
"canvas",
"pageTimer",
"pageVariable",
"page_portrait"
}
return scenes
(TBI) New layer as Shape
add Icon
props {rect, circle, text, image}
local layerProps = {
shape = "rect"
height = 100,
width = 100 ,
x = 100,
y = 100,
alpha = 1,
blendMode = "normal",
name = "myShape",
}
Shape uses fill to use an optional image file for texture
App/bookX/components/pageX/layers/myShape.lua
local M = require("components.kwik.layer_shape").new()
...
...
function M:create(UI)
UI.layers[layerProps.name] = self.createShape(UI)
end
In vscode menu, howerver you can open App/bookX/components/pageX/index.lua. You should not manually edit the order of layers in the index.lua. Instead change the weight of each .lua, and then you can use index generator tool to updat the index.lua
A nested/tree structure of App/bookX/components/pageX/layers is capable for manging files there manually by file editor like Finder(Mac) or FileExplorer(Windows). The index generator tool is better to use for composing manually files/folders.
It is not reccomended editing index.lua at first and then try to match the file and folder structure in Finder or File explorer
The order of files in a folder is specified in $weight value in a .lua file. The index generator tool read $weight for making the table(array) in index.lua
for example, gotoBtn.lua
-- $weight=2
--
local app = require "controller.Application"
local M = require("components.kwik.layer_image").new()
local layerProps = {
blendMode = "normal",
height = 134 - 0,
width = 134 - 0 ,
kind = solidColor,
name = "gotoBtn,
type = "png",
x = 134 + (0 -134)/2,
y = 0 + (134 - 0)/2,
alpha = 100/100,
}
index.lua
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={
} },
{ gotoBtn={ --class={"animation"}
} },
{ title={ class={"linear"} } },
-- { title={ class={"myClass"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})
--
return scene
(TBI) Up/Down/Top/Bottom
change the weight of a layer
Edit
if multi select and set same values the properties of selected components
Edit & open in vscode/folder
components
layer
audio
group/timers and actions
asset
App/bookX/assets folder is opened
page
App/bookX/compoents/pageX/index.lua
Paste/Copy
assets are not supported
Delete
(History)
create 100 copies at the time of saving in Sandbox? with updated file names?
Publish
Importer for a Kwik3 project
License
Template – TBI as auto update from kwiksher.com and store it as global
Auto update - TBI
Language
Samples
Structure
Project Properties
Extended
Normal or Component
Page Properties
Page components
Layer Properties
Common
Set Language – TBI as State function gloal, local, object
Animation
Replacements
Interactions
Physics
Kwik4 created a project folder where psd files are placed. Kwik5 you can creat a folder for your psd files on your own. When Kwik Exporter opens, it asks for the folder location.
Steps to publsh images of layers of . psd files
Manually create a project folder with Finder(mac) or Explorer (win), and put your psd files there.
In Photoshop, extension > Kwik
Open button for selecting your folder of .psd files
Click to open one psd file
New or Select Book button
please select a Solar2D project folder
Publish button
specify which .psd files to be processed
For Active Document, you can choose
develop/UXP/kwik-exporter
the folder name/path is saved in a json
Photoshop Files
Open psd files from a folder
Drag & Drop to change the order of psd files
one psd file corresponds one scene in Solar2D code.
a psd can be marked for being ignored. .ignore file?
Solar2D Project
App/book folder should be selected to where the pluigin publish images of photshop layers
Publish
selected psd files
Active Document
(Option) Layer Groups
Color code On/Off for export option of a layer folder
Publish all images of selected documents
mark checkboxes of document’s names you want to publish
https://developer.adobe.com/xd/uxp/uxp/reference-spectrum/User%20Interface/
spectrum tableview is not yet supported in UXP https://react-spectrum.adobe.com/react-spectrum/TableView.html
clcik
opendFileDialog asks a book folder under App folder.
Active Document exports Images for an active document document
open .psd by cliking the psd name in the list
Kwik4_1280x1920.psd with background image 1440x2776
kwik5 does not request the canvas size of .psd as 1280x1920 when publising
clcik Export Images
openFileDialog asks a book folder under App folder.
Layer Groups - Logic in Kwik
It controls exporting option of a layerSet(layer group) by creating a sub folder in a Solar2D project. This is done by chekcing the color code of a layer in Layers panel. If a color code for unmerge is set, it wiil export the chidlren of a layer set.
create a folder with same name as layerSet in book/assets/images/FILE_NAME_OF_PSD
for instance, “bg” is a layer group of kwik4_1280x1920.psd
you can manually create the bg folder under App/book/assets/images/kwik4_1280x1920 so the export images function knows where to put images of sub layers of a group. So you can have each imagge of the member of a layer group.
If such no folder with the same name as a layer group, the image of a layer group is exported.
> A concept of Kwik5 is to use App folder as a project base. It is a kind of file-based database where .json, .lua and assets files are placed. No more .kwk xml file of Kwik4 is there.
> Direclty Editing a file under App folder while running Solar2D Simulator means a live editing. There is no build4 folder of Kwik4 either
TODO Active Document > Layer Selection Only
test/base-proj/
Photoshop/bookshop
├── bookFree
│ ├── page1.psd
│ ├── page2.psd
├── bookOne
├── page1.psd
└── page2.psd
Solar2D
TODO add a layer group
.
├── AndroidResources
├── App
│ ├── bookFree
│ ├── assets
│ │ ├── images
│ │ │ ├── page1
│ │ │ │ ├── bg.png
│ │ │ │ ├── bg@2x.png
│ │ │ │ ├── bg@4x.png
│ │ │ │ ├── gotoBtn.png
│ │ │ │ ├── gotoBtn@2x.png
│ │ │ │ ├── gotoBtn@4x.png
│ │ │ │ ├── title.png
│ │ │ │ ├── title@2x.png
│ │ │ │ └── title@4x.png
│ │ │ ├── page2
│ │ └── thumbnails
│ ├── commands
│ │ ├── page1
│ │ │ ├── eventOne.lua
│ │ │ └── eventTwo.lua
│ │ └── page2
│ ├── components
│ │ ├── page1
│ │ │ ├── audios
│ | | ├── layers
│ | | │ ├── bg.lua
│ | | │ ├── gotoBtn.lua
│ | | │ ├── gotoBtn_animation.lua
│ | | │ ├── index.lua
│ | | │ ├── title.lua
│ | | │ └── title_animation.lua
│ │ │ ├── groups
│ │ │ ├── page
│ │ │ ├── times
│ │ │ └── variables
│ | ├── page2
│ │ └── index.lua
│ ├── models
│ ├── page1
│ │ ├── audios
│ │ ├── layers
│ │ ├── groups
│ │ ├── page
│ │ ├── times
│ │ ├── variables
│ │ ├── bg.json
│ │ ├── commands
│ │ │ └── eventOne.json
│ │ ├── gotoBtn.json
│ │ ├── gotoBtn_animation.json
│ │ ├── index.json
│ │ ├── title.json
│ │ └── title_animation.json
│ └── page2
│
├── Images.xcassets
├── LaunchScreen.storyboardc
├── assets
├── build.settings
├── commands
│ ├── app
│ ├── common
│ │ └── myEvent.lua
│ └── kwik
├── components
│ ├── bookstore
│ ├── common
│ │ ├── bookstoreNavigation.lua
│ │ ├── index.lua
│ │ ├── keyboardNavigation.lua
│ │ ├── myComponent.lua
│ │ └── thumbnailNavigation.lua
│ ├── editor
│ └── kwik
├── config.lua
├── controller
├── en.lproj
├── extlib
├── lib
├── mySplashScreen.png
└── main.lua
audios
(fonts)
(images)
particles
https://forums.solar2d.com/t/roaming-gamer-particle-editor-2-now-free/342432
sprites
(thumbnails)
videos
www
TBI
SVG, WEBP and QOI
https://forums.solar2d.com/t/graphics-extensions-for-solar2d/354890
https://github.com/ANSH3LL/Graphics-Extensions-for-Solar2D
Webp
QOI
TODO: resvg for macOS/iOS
physics
Physics editor
collision editor in tile map editor
https://doc.mapeditor.org/en/stable/manual/editing-tilesets/#tile-collision-editor
joints?
https://github.com/labolado/Labo-2D-Game-Level-Editor-For-Solar2D/tree/master
path animation
normal map
Kwik4 how to use a normal map for dynamic lighting effect
Spirte Illuminator to create a normal map https://www.codeandweb.com/spriteilluminator
normal map of image sheet is not supported yet in Corona SDK. you can only use a single image of normal map
https://kwiksher.com/blog/2020/06/19/relighting-a-photo-image/
Spine
http://kwiksher.com/doc/kwik_tutorial/animations/spine_animation/
papagoya lip sync with Spine
PNGs
Adobe Character Animator
https://kwiksher.com/blog/2019/08/28/character-animator-png-sequence/
jig-saw puzzle
https://www.kwiksher.com/doc/kwik_tutorial/interactions/drag_kwikcatpuzzle/
tiledmaps
Berry
Qiso Isometric
crossword?
https://kwiksher.com/blog/2020/11/13/finger-tracing-crossword/
Inkey
https://github.com/kwiksher/inky-lua-src
<=> chatGPT ⭐️
Storyboad Editor
Fountain script
storyboader
https://wonderunit.com/storyboarder/
<=> StableDiffusion ⭐️
charecterizer
script assitaant
Comic
UI.editor.assets = {
audios = {
{
name = "click.mp3",
path = "audios/short",
links = {{page = "page1"}}
}
},
videos = {
{
name = "videoA.mp4",
path = "videos",
links = {{page= "page01", layers = {"layerOne"}}}
},
{
name = "videoB.mp4",
path = "videos",
links = {{page= "page01", layers = {"layerTwo"}}}
}
},
sprites = {}
}
TBI
Create a new layer with a selected media
select filename
preview video?
select the icon on the top of asset table swith component view.
Please select a layer for replacement or hold + key to add a new layer
TODO if assetTable is open, don’t show layer props Table
save button will add a layer as same as the filename w/o extension
renaming a layer needs to update layerName_class.lua, models and the entry in layers table in index.lua. Use layer properties for renaming
edit cotnrol props
TODO hide the icons of assset table
TODO update video props
save button
TODO
editor asset selectors.tree new, modiy, delete (hide filter for selectbox) show (doGet) > assetsTable
/book/assets
/book/assets/audios
/book/assets/audios/short
/book/assets/audios/long
/book/assets/audios/sync
/book/assets/images
/book/assets/sprites
/book/assets/videos
doPut
assets audio, video, spritesheet, synctext
fetch it from url
openapi plugin whisper with timecode? ⭐️
dummy assets from sample and rename to use it
doPost for an asset to preview?
this needs a position to tell where to insert it in layers’tree so use
POST /book/page/newLayer/?class=video
file: video.mp4
save it to App/book/models/assets.json
lfs to traverse files in asset folder except images. This table is also updated from replacement tools for instance, user selects a video replacement and choose a videoA.mp4 to layerOne on page1,
page1/layerA_video.json
assets.json from the lua table editor.assets
{
audios = {},
fonts = {},
-- images = {}
particles = {},
sprites = {},
videos = {
{
name = "videoA.mp4",
path = "videos",
links = {{page= "page01", layers = {"imageOne"}},
{page= "page02", layers = {"iamgeTwo"}}}
},
},
}
editor > assets
select
videoA.mp4 layers = page1/layerOne, page2/layerTwo
option only current page
add
a new layer is added to index.lua with the asset class with deafult props on a current page
modify
edit and save properties
delete
remove layers, option to delete from a disk
https://github.com/siudesu/BinaryArchive
Use BookServer/compress_assets/main.lua
wordTouch: clicking a word in a line, one mp3 for one word is played
file structure (physical)
TODO:rename sentenceDir to wordTouch?
── sync
├── alphabet
│ ├── a.mp3
│ ├── b.mp3
│ └── c.mp3
├── alphabet.mp3
├── alphabet.txt
lua table (logic)
audioProps = {
language = nil,
folder = nil,
filename = "alphabet.mp3",
sentenceDir = "alphabet",
}
textProps = {
filename = "alphabet.txt",
}
alphabet.txt
0.000 0.500 A
0.500 1.000 B
1.000 1.500 C
en and jp
── sync
├── en
│ ├── i_am_a_cat
│ │ ├── am.mp3
│ │ ├── cat.mp3
│ │ └── i.mp3
│ ├── i_am_a_cat.mp3
│ ├── i_am_a_cat.txt
└── jp
├── i_am_a_cat
│ ├── am.mp3
│ ├── cat.mp3
│ └── i.mp3
├── i_am_a_cat.txt
├── i_am_a_cat.mp3
audioProps = {
folder = nil
language = {"en", jp"},
filename = "i_am_a_cat.mp3",
sentenceDir = "i_am_a_cat",
}
textProps = {
filename = "i_am_a_cat.txt",
}
en and jp
mp3 and txt are in page3 folder
no word touch
── sync
├── en
│ └── page3
│ ├── my_name_is_kwik.mp3
│ └── my_name_is_kwik.txt
└── jp
└── page3
├── my_name_is_kwik.mp3
└── my_name_is_kwik.txt
audioProps = {
language = {"en", jp"},
folder = page3
filename = "my_name_is_kwik.mp3",
sentenceDir = nil,
}
textProps = {
filename = "my_name_is_kwik.txt",
}
Asset and Component Releation
graph RL
subgraph editor
tool[video replacement tool]
tool -.- default([defautprops])
readAssetFunc[readAsset]
end
subgraph App.bookX
subgraph assets
short_ballsCollision.mp3
video.mp4
json[(assets.json for editor)]
end
subgraph components.pageX
audio_short_ballsCollision.lua
subgraph layers
bg.lua
imageOne.lua -.- layerProps([props table])
imageOne_video.lua -.- videoProps([props table])
end
end
end
readAssetFunc -. 1 traverse with lfs .-> App.bookX
readAssetFunc -. 2 generate .-> json
YourInput -. 3 selecting layer <br> and video .-> tool
tool -. 4-1 read/write .- json
json -. 4-2 link for publish .- video.mp4
tool -. 5 render when publshing .-> imageOne_video.lua
subgraph photoshop
kwik([kwik plugin])
end
generativeAI[[Generative AI]]
kwik -. images .-> assets
kwik -. lua files .-> layers
generativeAI -. images audios videos .-> assets
{
audios = {},
fonts = {},
-- images = {}
particles = {},
sprites = {},
videos = {
{
name = "videoA.mp4",
path = "videos",
links = {{page= "page01", layers = {"imageOne"}},
{page= "page02", layers = {"iamgeTwo"}}}
},
},
}
Four ways to compose layers with assets.
select a layer
select a layer replacement tool (ex video tool)
save the props of video replacement
the selected layer is linked to the selected media
pubish
//TODO screenshots
//TODO readAsset to merge/validate assets.json with /components/pageX/index.lua?
assets.json will be generated automatically by editor
vs code to compose App/booxX/components/pageX/index.lua
add a layer element to components.layers table with a class (ex “video”)
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={
} },
{ layerOne={ class={"video"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})
App/booX/comonents/pageX/layers
add the lua files with the props
local layerProps = {
blendMode = "normal",
height = 1280 - 0,
width = 1920 - 0 ,
kind = pixel,
name = "bg",
type = "png",
x = 1920 + (0 -1920)/2,
y = 0 + (1280 - 0)/2,
alpha = 100/100,
}
local props = {
actionName = "{{actionName}}",
delay = {{delay}},
iterations = {{iterations}},
name = "{{name}}",
}
return require("components.kwik.layer_video").new(props)
when Solar2D simulator refreshes automatically, and then editor read pageX/index.lua for updating assets.json
readAsset() uses lfs to read media files in App.bookX/assets folder, and generate layer names of links table for assets.json out of pageX/index.lua
you can scafold .lua files with the asset command/bat. The asset_lua_generator(.command/.bat) generates lua files into the following directories with assets.json/yml/lua
App/booxX/components/pageX/index.lua
App/booX/comonents/pageX/layers/layerX.lua
App/booX/comonents/pageX/layers/layerX_xxx.lua
assets.lua
{
audios = {},
fonts = {},
-- images = {}
particles = {},
sprites = {},
videos = {
{
name = "videoA.mp4",
path = "videos",
links = {{page= "page01", layers = {"imageOne"}},
{page= "page02", layers = {"iamgeTwo"}}}
},
},
}
put media files into App.bookX/assets folder
prepare assets.json (yml, lua are supported) to spefify layers with media files
scafold with the asset_lua_generator in the vs code terminal
edit each lua manually for positions and props
you may use common/align.lua for auto layout
or use editor/httpYac to modify the props of layers and components
// TODO Move/Transform tool in editor
// TODO asset_lua_generator
if you don’t use kwik plugin in Photoshop, scafolding with assets.json will help you work wtih media files
if layes are not found in page/index.lua, sset_lua_generator will add them to index.lua
you can use asset_lua_generator after publshing images/lua files from Kwik plugin in Photoshop, and then manually associateing layers and media files for replacements
if you run asset_lua_generator for the second time, it will not overwirte existing lua files. It will scafold a new lua file for a new component which is not found in pageX/index.lua
put media files into App.bookX/assets folder
put (create) a layer and layer_xxx lua files with httpYac
## PUT
### creates a new lua file in App directory if not exist. scafolding a lua file
@host=http://localhost:9090
GET /app
###
GET /book
PUT /book
GET /book/page01
### update index.lua too
PUT /book/page01/newLayer
PUT /book/page01/newLayer/?class=video
POST/book/page01/newLayer
Content-Type: application/yaml
{
alpha=0.5
x=display.contentCenterX
y=display.contentCenterY
width=100
height=100
}
post(update) a layer with class
GET /book/assets
POST /book/page/newLayer/?class=video
url: video.mp4
POST /book/page/newLayer/?class=video
url: https://kwiksher.com/tutorials/Video/kwikplant.mp4
//TODO update assets.json
memo
selectors.tree new, modiy, delete (hide filter for selectbox)
show (doGet) > assetsTable
/book/assets
/book/assets/audios
/book/assets/audios/short
/book/assets/audios/long
/book/assets/audios/sync
/book/assets/images
/book/assets/sprites
/book/assets/videos
lua table (asset.json) will be saved into asset folder
file info(fixed props) // should we reference props table in App/bookX/components/pageX?
// yes, they are tangible props of a component in a page
- audio_props.lua
```
audioProps = {
language = nil,
folder = nil,
filename = "alphabet.mp3",
sentenceDir = "alphabet",
}
textProps = {
filename = "alphabet.txt",
}
```
- spritesheet_props.lua
```
{
filename = "butflysprite.png",
sheetInfo = {
width = 188,
height = 188,
sheetWidth = 376,
sheetHeight = 188,
}
}
```
- video_props.lua
```
```
doPut
assets audio, video, spritesheet, synctext
fetch it from url
openapi plugin whisper with timecode? ⭐️
dummy assets from sample and rename to use it
doPost for an asset to preview?
this needs a position to tell where to insert it in layers'tree so use
```
POST /book/page/newLayer/?class=video
file: video.mp4
```
save it to App/book/models/assets.json
lfs to traverse files in asset folder except images. This table is also updated from replacement tools
for instance, user selects a video replacement and choose a videoA.mp4 to layerOne on page1,
- page1/layerA_video.jspn
- assets.json
```
{
audios = {},
fonts = {},
-- images = {}
particles = {},
sprites = {},
videos = {
{
name = "videoA.mp4",
path = "videos",
links = {{page= "page01", layers = {"imageOne"}},
{page= "page02", layers = {"iamgeTwo"}}}
},
},
}
```
editor > assets
- select
videoA.mp4 layers = page1/layerOne, page2/layerTwo
option only current page
- add
a new layer is added to index.lua with the asset class with deafult props on a current page
- modify
edit and save properties
- delete
remove layers, option to delete from a disk
doPost
for transition2.to, it needs a generic form
PUT to set a class for a layer
- add it to index.lua, model.json
- scafold layer_class.lua with a template
it entry exists, and no params, get values from controller (controlProps) which has been updated by POST?
POST to add properties
GUI
if it is not one of kwik's classes, first create a new custom class
PUT components/custom/newClass
this class.lua does not have a template, props is a lua table that can be encoded into json
Particles asset
Spritsheet asset
TODO rename it as Imagesheet?
Sample slots.png
slots imagesheet is donwloaded from https://roaminggamer.github.io/RGDocs/pages/Plugins/texturepackerhelpers/ https://github.com/roaminggamer/RG_FreeStuff/raw/master/myPluginSamples/texturepackerhelpers/sample.zip
{
filename = "slots.png",
sheetInfo = "slots.lua",
}
Sample butflysprite.png
Frame 188 x 188
Sheet Width 376 Height 188
{
filename = "butflysprite.png",
sheetInfo = {
width = 188,
height = 188,
sheetWidth = 376,
sheetHeight = 188,
}
}
the above example has sheetInfo value but it could be empty for the first time when user just puts an imagesheet file in the assets folder. The sheet info is specified by Spritesheet replacment tool in editor later.
Adobe Animate
https://kwiksher.com/blog/2019/08/09/sprite-sheet-from-adobe-animate/
Video asset
App/bookX/assets/video folder
── video
├── videoA.mp4
├── animals
├─ cat.mp4
├─ dog.mp4
lua table
{
isLocal = true,
url = "videoA.mp4",
}
{
isLocal = true,
url = "animals/dog.mp4",
}
https://kwiksher.com/tutorials/Video/kwikplant.mp4
{
isLocal = false,
url = "https://kwiksher.com/tutorials/Video/kwikplant.mp4",
}
Web html asset
youtube sample
Vue sample?
M.commands = {
action = {
play = {_trigger = ""},
playAll = {actions = {}, random = true},
},
animation = {
pause = {_target = ""},
resume = {_target = ""},
play = {_target = ""},
playAll = { animations = {}}
},
audio = {
record = {
duration = 0,
mmFile = "",
malfa = "",
audiotype = ""
},
muteUnmute = {
_target = {}
},
play = {
_target = "",
type = "",
channel = "",
repeatable = "",
delay = "",
loop = "",
fade = "",
volume = "",
tm = "", -- timer id
_trigger = "",
},
rewind = {
_target = "",
type = "",
channel = "",
repeatable = "",
},
...
local ActionCommand = {}
local AC = require("commands.kwik.actionCommand")
---
function ActionCommand:new()
local command = {}
--
function command:execute(params)
local UI = params.UI
local sceneGroup = UI.scene.view
local layers = UI.layers
local obj = params.obj
{{#actions}}
{{#animation}}
--
-- target layer :sceneGroup[layerName]
-- target animation : layer.animations[index]
--
{{#pause}}
AC.Animation:pause("{{target}}")
{{/pause}}
{{#resume}}
AC.Animation:resume("{{target}}")
{{/resume}}
{{#play}}
AC.Animation:play("{{target}}", {{index}})
{{/play}}
{{/animation}}
{{#button}}
{{#onOff}}
AC.Button:onOff("{{target}}", {{enable}}, {{toggle}} ) -- enable, toggle
{{/onOff}}
{{/button}}
{{/actions}}
end
return setmetatable( command, {__index=AC})
end
animationAction.lua
local M = {}
--
function _M:pause(anim)
end
--
function _M:resume(anim)
end
--
function _M:play(anim)
end
--
return _M
action editor > Interactions > Canvas
//defined in editor.action.model.lua
M.commands{
...
canvas = {
brush = {
size = {size = 10, alpha = 1},
color = {0,0,0,1}
},
erase = {},
undo = {},
redo = {}
},
...
```
index.lua
.
├── actionCommand
│ ├── cancel.lua
│ ├── delete.lua
│ └── save.lua
├── actionCommandButtons.lua
├── actionCommandPropsTable.lua
├── actionCommandTable.lua
├── actionTable.lua
├── buttons.lua
├── commandbox.lua
├── controller
│ ├── cancel.lua
│ ├── copy.lua
│ ├── create.lua
│ ├── delete.lua
│ ├── index.lua
│ ├── paste.lua
│ ├── save.lua
│ ├── selectAction.lua
│ └── selectActionCommand.lua
├── index.lua
├── model.lua
└── selector.lua
local M = {name = name, views = {
"index", -- this creates actionIcon button
"selector", -- context:mapCommands "selectAction", "selectActionCommand"
"actionTable", -- lists actions in a page
"actionCommandTable",
"actionCommandPropsTable",
"commandbox", -- animation.play, pause, ..
"buttons",
"actionCommandButtons"
}}
...
...
function M:create(UI)
if self.sceneGroup then return end
--
self.sceneGroup = UI.editor.sceneGroup
controller:init(UI, self.sceneGroup, categoryMap, selectbox)
-- print("create", self.name)
local posX, posY = display.contentCenterX/2 + 42, -2
-----------------------------------------
self:createIcon(UI, posX, posY)
-----------------------------------------
self:createSelectbox(UI, posX, posY)
-----------------------------------------
local scrollListener = function(e) end
local scrollView = widget.newScrollView{
top = 22,
left = display.contentCenterX,
width = 100,
height = 240,
-- height = #models*18,
scrollWidth = display.contentWidth*0.5,
scrollHeight = display.contentHeight*0.8,
hideBackground = false,
isBounceEnabled = false,
verticalScrollDisabled = false,
backgroundColor = {1.0},
listener = scrollListener
}
self.sceneGroup:insert(scrollView)
-----------------------------------------
self:createTable(scrollView, models)
-----------------------------------------
UI.editor.actionEditor = self
self.sceneGroup.actionEditor = scrollView
--
self:hide()
-- self:show()
end
action editor > Interactions > screenshot
//defined in editor.action.model.lua
M.commands{
...
screenshot = {
take = {
ptit = "",
message = "",
shutter = true,
hideLayers = {}
}
},
```
template.components.pageX.index
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "{{name}}",
components = {
layers = {
{{#layers}}
{{>recursive}}
{{/layers}}
},
audios = {
{{#audios}}
long={ {{#long}} "{{.}}", {{/long}} }, short={ {{#short}}"{{.}}", {{/short}} }
{{/audios}}
},
groups = { {{#groups}} "{{.}}", {{/groups}} },
timers = { {{#timers}} "{{.}}", {{/timers}} },
variables = { {{#variables}} "{{.}}", {{/variables}} },
page = { {{#page}}"{{.}}", {{/page}} }
},
commands = { {{#events}} "{{.}}", {{/events}} },
onInit = function(scene) print("onInit") end
})
--
return scene
editor.util
please notice saveLua func uses model.components
function M.renderIndex(book, page, model)
local dst = "App/"..book.."/"..page .."/components/index.lua"
local tmplt = "editor.template/components/pageX/index.lua"
...
...
M.saveLua(tmplt, dst, {name = model.name, events = model.commands,
layers = model.components.layers,
audios = model.components.audios,
timers = model.components.timers,
groups = model.components.groups,
variables = model.components.variables,
page = model.components.page,
}, partial)
return dst
end
bookFree.timer.index
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "timer",
components = {
layers = {
{ bg={} },
},
audios = {},
groups = {},
timers = {"timerOne", "timerTwo"},
variables = {"varOne", "varTwo"},
page = {}
},
commands = {
"timerAction", "variableAction",
},
onInit = function(scene) print("onInit") end
})
--
return scene
example bookFree/timer
the editor files
example bookFree/page1
.
├── assets
├── commands
├── components <==== page components
│ ├── page1
│ │ ├── audios
│ │ ├── layers
│ │ │ ├── bg.lua
│ │ │ ├── gotoBtn.lua
│ │ │ ├── gotoBtn_animation.lua <=== animation component lua
│ │ │ ├── title.lua
│ │ │ └── title_animation.lua <=== animation component lua
│ │ ├── groups
│ │ ├── page
│ │ ├── times
│ │ ├── variables
│ │ └── index.lua
│ └── page2
├── models
│ ├── page1
│ │ ├── bg.json
│ │ ├── events
│ │ │ └── eventOne.json
│ │ ├── gotoBtn.json
│ │ ├── gotoBtn_animation.json <=== animation component json
│ │ ├── index.json
│ │ ├── title.json
│ │ └── title_animation.json <=== animation component json
│ └── page2
UI
index template
template
editor.template.components.pageX.animations
layer_animation
module
editor
components.editor.animation
tree .lua
defaults
UI
index template
editor.template.componetns/pageX/index.lua
{
name = "pageX",
components = {
layers = {
{background={}},
},
audios = {
long = {"streamOne", "streamTwo"},
short = {"shortOne", "shortTwo"}
},
},
commands = {},
}
template
editor.template.components.pageX.audios.aduio
local props = {
autoPlay = {{autoPlay}},
channel = {{channel}},
delay = {{delay}},
filename = "{{filename}}",
folder = "{{folder}}",
loops = {{loops}}, -- 1 + 3 = 4 times
name = "{{name}}",
type = "{{type}}",
}
return require("components.kwik.page_audio").new(props)
module
components.kwik.page_audio
function M:create(UI)
...
...
if self.type == "stream" then
self.loader = audio.loadStream
self.filename = "long/"..self.filename
else
self.loader = audio.loadSound
self.filename = "short/"..self.filename
end
local path = App.getProps().audioDir..self.filename
self.audioObj = self.loader(path , App.getProps().systemDir)
...
...
end
function M:play()
audio.setVolume(self.volume or 8, { channel=self.channel });
if self.allowRepeat then
self.repeatableChannel = audio.play(self.audioObj, { channel=self.channel, loops=self.loops, fadein = self.fadein } )
else
audio.play(self.audioObj, {channel=self.channel, loops=self.loops, fadein = self.fadein } )
end
end
...
...
audio
├── audioTable.lua
├── buttons.lua
├── controlProps.lua
├── controller
│ ├── cancel.lua
│ └── save.lua
├── defaults
│ └── audio.lua
├── index.lua
comonents.editor.audio.index
local model = {
id ="audio",
props = {
{name="autoPlay", value=true},
{name="channel", value = ""},
{name="delay", value=0},
{name="filename", value = ""},
{name="folder", value=""},
{name="loops", value = ""},
{name="name", value = ""},
{name="type", value = ""},
}
}
...
...
function controller:render(book, page, type, name, model)
local dst = "App/"..book.."/"..page .."/components/audios/"..type.."/"..name ..".lua"
local tmplt = "editor.template/components/pageX/audios/audio.lua"
util.mkdir("App", book, page, "components", "audios", type)
util.saveLua(tmplt, dst, model)
return dst
end
function controller:save(book, page, type, name, model)
local dst = "App/"..book.."/models/"..page .."/audios/"..type.."/"..name..".json"
util.mkdir("App", book, "models", page, "audios", type)
util.saveJson(dst, model)
return dst
end
components.editor.audio.defaults
local M = {
name = "audio_",
class="audio",
controls = {
autoPlay=false,
channel = 0,
delay=0,
filename = "",
-- folder="",
loops = 0,
name = "",
type = "",
}
}
PSD
Unit Test
Kwik4
index
genereated from editor.template.componetns/pageX/index.lua
{
name = "pageX",
components = {
layers = {
{ back={ } },
{ painting={} },
{ butBlue={ class={"button"}} },
{ butWhite={class={"button"}} },
{ butOrange={class={"button"}} },
{ butCamera={class={"button"}} },
{ butLarge={class={"button"}} },
{ butMedium={class={"button"}} },
{ bigCandice={class={"button"}} },
{ Candice={class = {"canvas"}},
},
},
commands = {},
}
editor/template/components/pageX/interaction/defaults/canvas.lua
// TODO add attributes
local M = {
name = "canvas",
class="canvas",
controls = {
isActive = true
}
}
return M
template
editor/template/components/pageX/interaction/layer_canvas
//TODO create it from kwik4’s components.kwik.layer_canvas.lua
local name = ...
local parent,root = parent_root(name)
local layerProps = require(parent.."{{layer}}")
local M = {
name ="{{name}}",
class = "{{class}}", -- button, drag, canvas ...
--
{{#controls}}
UI.canvas.name = "UI.canvas"
UI.canvas.cR, UI.canvas.cG, UI.canvas.cB = {{bc}}
UI.canvas.brushSize = {{bs}}
UI.canvas.brushAlpha = 1
UI.canvas.lineTable = {}
UI.canvas.undone = {}
{{/controls}}
layerProps = layerProps
}
function M:create(UI)
local sceneGroup = UI.scene.view
local obj = self:createCanvas(UI)
UI.layers[self.name] = obj
sceneGroup[self.name] = obj
sceneGroup:insert(obj)
end
...
...
return require("components.kwik.layer_canvas").new(M)
module
editor
// editor.ihdex:getTool() returns a component editor from id of editor.model
components.editor.interaction.index
// TODO implement a color selector for the attributes with Color
Please see design/project_model/commands/canvas and screenshot
TODO page4’s groups = {“SubA”, “GroupA”, “myGroup” }
page1
page4 for demonstrating layers and groups
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page4",
components = {
layers = {
{ bg={} },
{ copyright={} },
{ star={} },
{ GroupA={
{ Ellipse = {} },
{ SubA = {
{ Triangle = {} },
}
},
}
},
{ hello={} },
},
audios = {},
groups = {"SubA", "GroupA", "myGroup" },
timers = {},
variables = {},
page = {}
},
commands = {
-- "myAction", "myEvents.testHandler",
},
onInit = function(scene) print("onInit") end
})
--
return scene
scenes/pageX/background.lua
local _K = require "Application"
local _M = require("components.kwik.layer_image").new()
_M.weight = 1
local Props = {
blendMode = "normal",
height = 520,
width = 1000,
kind = pixel,
name = "bg",
x = 1000 -1000/2,
y = 520/2,
alpha = 100/100,
}
--
_M.imageWidth = Props.width/4
_M.imageHeight = Props.height/4
_M.mX, _M.mY = _K.ultimatePosition(Props.x, Props.y, "")
_M.randXStart = _K.ultimatePosition()
_M.randXEnd = _K.ultimatePosition()
_M.dummy, _M.randYStart = _K.ultimatePosition(0, )
_M.dummy, _M.randYEnd = _K.ultimatePosition(0, )
_M.infinityDistance = (parseValue() or 0)/4
....
....
....
--
function _M:localVars(UI)
end
--
function _M:localPos(UI)
end
--
function _M:didShow(UI)
end
--
function _M:toDispose(UI)
end
--
function _M:toDestory()
end
--
return _M
‘_M.weight = num’ controlls the order of display objects for kwik-genereate-index tool that outputs scenes/pageX/index.lua
scenes/pageX/groupOne/index.lua
_M = {}
_M.weight = 1
--
-- this index.lua is for kwik-generate-model
-- you may put additional code here
--
return _M
scenes/pageX/groupOne/imageOne.lua
local _K = require "Application"
local _M = require("components.kwik.layer_image").new()
_M.weight = 1
local Props = {
...
....
}
scenes/pageX/groupOne/imageTwo.lua
local _K = require "Application"
local _M = require("components.kwik.layer_image").new()
_M.weight = 2
local Props = {
...
....
}
scenes/pageX/index.lua
Bottom to Top order
{
name = "pageX",
layers = {
{background={}},
{groupOne = {
{imageTwo},
{imageOne},
}},
},
components = {},
events = {},
}
create main.lua file
display.setStatusBar( display.HiddenStatusBar )
local particleDesigner = require( "particleDesigner" )
local kaboom = particleDesigner.newEmitter( "'+jname+'_temp.json" )
kaboom.x = display.contentWidth / 2
kaboom.y = display.contentHeight / 2
Model
{
configName = '',
textureFileName = '',
common = {
duration = 0,
sourcePositionVariancex = 0,
sourcePositionVariancey = 0,
maxParticles = 0,
},
emitter = {
emittyerType = 0, -- 0=grevity, 1=radial
angle = 0,
angleVariance = 0,
gravity = {
speed = 0,
speedVariance = 0,
gravityx = 0,
gravityy = 0,
radialAcceleration = 0,
radialAccelVariance = 0,
tangentialAcceleration = 0,
tangentialAccelVariance = 0,
},
radial = {
maxRadius = 0,
maxRadiusVariance = 0,
minRadius = 0,
minRadiusVariance = 0,
rotatePerSecond = 0,
rotatePerSecondVariance = 0,
}
},
particles = {
particleLifespan = 0,
particleLifespanVariance = 0,
startParticleSize = 0,
startParticleSizeVariance = 0,
finishParticleSize = 0,
finishParticleSizeVariance = 0,
rotationStart = 0,
rotationStartVariance = 0,
rotationEnd = 0,
rotationEndVariance = 0,
},
colors = {
startColorAlpha = 0,
startColorRed = 0,
startColorGreen = 0,
startColorBlue = 0,
startColorVarianceRed = 0,
startColorVarianceGreen = 0,
startColorVarianceBlue = 0,
startColorVarianceAlpha = 0,
finishColorAlpha = 0,
finishColorRed = 0,
finishColorGreen = 0,
finishColorBlue = 0,
blendFuncSource = 0,
finishColorVarianceRed = 0,
finishColorVarianceGreen = 0,
finishColorVarianceBlue = 0,
finishColorVarianceAlpha = 0,
blendFuncDestination = 0,
},
}
var st = String(myParticles.blendFuncSource)
switch (st) {
case '0':
st=0;
break;
case '1':
st=1;
break;
case '774':
st=2;
break;
case '775':
st=3;
break;
case '770':
st=4;
break;
case '771':
st=5;
break;
case '772':
st=6;
break;
case '773':
st=7;
break;
case '776':
st=8;
break;
}
//
var st2 = String(myParticles.blendFuncDestination)
switch (st2) {
case '0':
st2=0;
break;
case '1':
st2=1;
break;
case '774':
st2=2;
break;
case '775':
st2=3;
break;
case '770':
st2=4;
break;
case '771':
st2=5;
break;
case '772':
st2=6;
break;
case '773':
st2=7;
break;
case '776':
st2=8;
break;
}
//Default
//image/texture
if (oriParticleFile != true && preview == true) {
myParticles.textureFileName = '"'+newParticleFile+'"';
} else if (oriParticleFile != true && preview != true) {
ico = dlg.partName.e.text+".png";
ico = ico.replace(/"/gi, "")
myParticles.textureFileName = '"'+ico+'"';
} else if (oriParticleFile == true && preview != true) {
ico = dlg.partName.e.text+".png";
ico = ico.replace(/"/gi, "")
myParticles.textureFileName = '"'+ico+'"';
} else {
ico = ico.replace(/"/gi, "")
myParticles.textureFileName = '"'+ico+'"';
}
//particles content
//Color content
switch (st) {
case 'Zero':
st=0;
break;
case 'One':
st=1;
break;
case 'Dst_Color':
st=774;
break;
case 'One_Minus_Dst_Color':
st=775;
break;
case 'Src_Alpha':
st=770;
break;
case 'One_Minus_Src_Alpha':
st=771;
break;
case 'Dst_Alpha':
st=772;
break;
case 'One_Minus_Dst_Alpha':
st=773;
break;
case 'Source_Alpha_Saturate':
st=776;
break;
}
myParticles.blendFuncSource = st;
switch (st1) {
case 'Zero':
st1=0;
break;
case 'One':
st1=1;
break;
case 'Dst_Color':
st1=774;
break;
case 'One_Minus_Dst_Color':
st1=775;
break;
case 'Src_Alpha':
st1=770;
break;
case 'One_Minus_Src_Alpha':
st1=771;
break;
case 'Dst_Alpha':
st1=772;
break;
case 'One_Minus_Dst_Alpha':
st1=773;
break;
case 'Source_Alpha_Saturate':
st1=776;
break;
}
myParticles.blendFuncDestination = st1;
function jsonXML(file) {
if (garb == "{") {
toxml += garb.replace("{", "<particles>");
} else if (garb == "}") {
toxml += garb.replace("}", "</particles>");
} else if (garb.search("configName") > 0 ) {
toxml += '<configName>"'+configName+'"</configName>';
} else if (garb.search("textureFileName") > 0 ) {
toxml += '<textureFileName>"'+imagefile+'"</textureFileName>';
} else {
garb = garb.replace('"', "<")
var para = garb.substring(1,garb.search('":'))
garb = garb.replace('":', ">");
garb = garb.replace(',', "");
garb = garb+"</"+para+">";
toxml += garb;
}
}
function xmJSON(file) {
var j = "{ \r\n"
for (var xn=0;xn<=myParticles.elements().length()-1;xn++) {
if (xn!=myParticles.elements().length()-1) {
j += ' "'+myParticles.child(xn).name()+'" : '+myParticles.child(xn)+', \r\n';
} else {
j += ' "'+myParticles.child(xn).name()+'" : '+myParticles.child(xn)+' \r\n';
}
}
j += "}"
}
TODO
UI
index template
template
editor.template.components.pageX.replacement
layer_spritesheet
module
editor
components.editor.replacement
.
├── controller
│ ├── add.lua
│ ├── cancel.lua
│ ├── delete.lua
│ ├── index.lua
│ ├── save.lua
│ └── select.lua
├── index.lua
├── listButtons.lua
├── listPropsTable.lua
├── listbox.lua
├── model.lua
├── onCompletebox.lua
├── particles
│ └── controller
└── videobox.lua
defaults
editor.template.components.pageX.replacement.defaults.spritesheet
local M = {
name = "spritesheet",
class = "spritesheet",
type = "uniform-sized", -- TexturePacker, Animate
controls = {
filename = "imagesheet.png",
sheetInfo = "spritesheet",
sheetContentWidth = 376, -- same size or loaded from sheetInfo
sheetContentHeight = 188, -- same size or loaded from sheetInfo
numFrames = 2, -- same size or loaded from sheetInfo
width = 188, -- same size, disable for TP, Aniamte
height = 188, -- same size, disable for TP, Animate
},
sequenceData = {
{
name = "default",
count = 2,
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
start = 1,
time = 1000,
},
{
name = "test",
frames = {1,2},
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
time = 1000,
}
},
actioneName = "",
-- event.phase
-- began
-- ended
-- bounce — The animation bounced from forward to backward while playing.
-- loop — The animation looped from the beginning of the sequence.
-- next - The animation played a subsequent frame that's not one of the above phases.
}
TODO
the latest model defaults.sync has audioProps and textProps but UI does not have them yet
update page3/layers
props.text –> props.line
props.languge –> props.line.en, props.line.jp and textProps.language = {“en”, “jp”}
UI
default
local M = {
name = "alphabet",
class = "sync",
controls = {
autoPlay = true,
delay = nil,
fadeDuration = 1000,
speakerIcon = true,
wordTouch = true,
},
audioProps = {
filename = "alphabet.mp3",
channel = 2,
volume = 10,
},
textProps = {
folder = nil,
font = nil1,
fontColor = { 1,1,1 },
fontColorHi = { 1, 1, 0 },
fontSize = 36,
language = nil,
padding = 10,
readDir = "leftToRight",
sentenceDir = "alphabet", -- wordTouch
}
}
M.line = {
{ start = 0, out = 1000, dur = 0, name = "A", file = "a.mp3", action = "onComplete"},
{ start = 1000, out = 2000, dur = 0, name = "B", file = "b.mp3", action = "onComplete"},
{ start = 2000, out = 3000, dur = 0, name = "C", file = "c.mp3", action = "onComplete"},
}
test/base-proj/Solar2D/App/bookFree/components/page3/index.lua
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page3",
components = {
layers = {
{ test={}},
{ alphabet = {class={"sync"}}},
{ iamacat = {class={"sync"}}},
{ mynameiskwik = {class={"sync"}}},
},
audios = {
long = {"GentleRain", "Tranquility" },
short ={"ballsCollision"} ,
},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "onComplete", "play" },
onInit = function(scene) print("onInit") end
})
--
return scene
files(mp3 and text) are structeud in the following patterns A-D. See the examples of sync audio text in page3/index.lua
A: alphabet
not multi-lingual
word touch enabled and files are in alphabet folder
clicking a word in a line, the word.mp3 is played
── sync
├── alphabet
│ ├── a.mp3
│ ├── b.mp3
│ └── c.mp3
├── alphabet.mp3
├── alphabet.txt
B: iamcat
── sync
├── en
│ ├── i_am_a_cat
│ │ ├── am.mp3
│ │ ├── cat.mp3
│ │ └── i.mp3
│ ├── i_am_a_cat.mp3
│ ├── i_am_a_cat.txt
└── jp
├── i_am_a_cat
│ ├── am.mp3
│ ├── cat.mp3
│ └── i.mp3
├── i_am_a_cat..txt
├── i_am_a_cat.mp3
C: mynameiskwik
── sync
├── en
│ └── page3
│ ├── my_name_is_kwik.mp3
│ └── my_name_is_kwik.txt
└── jp
└── page3
├── my_name_is_kwik.mp3
└── my_name_is_kwik.txt
D: my father is nice
── sync
├── en
│ ├── my_father_is_nice.mp3
│ ├── my_father_is_nice.txt
└── jp
├── my_father_is_nice.mp3
├── my_father_is_nice.txt
local props = {
name = "alphabet",
filename = "alphabet.mp3",
type = "sync",
autoPlay = true,
channel = 2,
folder = nil
}
props.text = {
{ start = 0, out = 1000, dur = 0, name = "A", file = "a.mp3", action = "onComplete"},
{ start = 1000, out = 2000, dur = 0, name = "B", file = "b.mp3", action = "onComplete"},
{ start = 2000, out = 3000, dur = 0, name = "C", file = "c.mp3", action = "onComplete"},
}
props.wordTouch = true
props.sentenceDir = "alphabet" -- wordTouch
props.layer = "alphabet"
local props = {
name = "i_am_a_cat",
filename = "i_am_a_cat.mp3",
type = "sync",
language = {},
autoPlay = true,
channel = 2,
folder = nil
}
props.language.en = {
{ start = 0, out = 500, dur = 0, name = "I", file = "i.mp3"},
{ start = 500, out = 1000, dur = 0, name = "am", file = "am.mp3"},
-- { start = 1000, out = 1000, dur = 0, name = "a", file = nil},
{ start = 1000, out = 1500, dur = 0, name = "a cat.", file = "cat.mp3", action = "onComplete"},
}
props.language.jp ={
{start = 0, out = 500, dur = 0, name = "ぼく", file = "i"},
{start = 500, out = 1000, dur = 0, name = "ねこ", file = "cat", action = "onComplete"},
}
props.wordTouch = true
props.sentenceDir = "i_am_a_cat" -- wordTouch
props.layer = "iamacat"
local props = {
name = "my_name_is_kwik",
filename = "my_name_is_kwik.mp3",
type = "sync",
language = {},
folder = "page3",
}
--
-- action is added by sync props
--
props.language.en = {
{ start = 0, out = 500, dur = 0, name = "My", file = "" },
{ start = 500, out = 1000, dur = 0, name = "name", file = ""},
{ start = 1000, out = 1000, dur = 0, name = "is", file = ""},
{ start = 1000, out = 1500, dur = 0, name = "Kwik.", file = "", action = "onComplete"},
}
props.language.jp = {
{start = 0, out = 500, dur = 0, name = "なまえ", file = ""},
{start = 500, out = 500, dur = 0, name = "は", file = ""},
{start = 500, out = 1000, dur = 0, name = "くいっく", file = "", action = "onComplete"},
}
props.wordTouch = false
props.sentenceDir = nil -- wordTouch
props.readDir = "leftToRight"
props.layer = "mynameiskwik"
local props = {
name = "myfatherisnice",
filename = "my_father_is_nice.mp3",
type = "sync",
language = {},
folder = nil,
}
--
-- action is added by sync props
--
props.language.en = {
}
props.language.jp = {
}
props.wordTouch = false
props.sentenceDir = nil -- wordTouch
props.readDir = "leftToRight"
props.layer = "myfatherisnice"
UI
template
editor.template.components.pageX.timers.timer
local props = {
actionName = "{{actionName}}",
delay = {{delay}},
iterations = {{iterations}},
name = "{{name}}",
}
return require("components.kwik.page_timer").new(props)
module
components.kwik.page_timer
local M = {}
...
...
function M:didShow(UI)
self.timerObj = timer.performWithDelay( self.delay, function()
if self.actionName then
UI.scene:dispatchEvent({name = self.actionName })
end
end, self.iterations)
end
--
timer
├── buttons.lua
├── controller
│ ├── cancel.lua
│ └── save.lua
├── defaults
│ └── timer.lua
├── index.lua
└── timerTable.lua
comonents.editor.timer.index
local model = {
id ="timer",
props = {
{name="actionName", value=""},
{name="delay", value=0},
{name="iterations", value = 1},
{name="name", value = ""},
}
}
...
...
function controller:render(book, page, type, name, model)
local dst = "App/"..book.."/"..page .."/components/timers/"..type.."/"..name ..".lua"
local tmplt = "editor.template/components/pageX/timers/timer.lua"
util.mkdir("App", book, page, "components", "timers", type)
util.saveLua(tmplt, dst, model)
return dst
end
function controller:save(book, page, type, name, model)
local dst = "App/"..book.."/models/"..page .."/timers/"..name..".json"
util.mkdir("App", book, "models", page, "timers", type)
util.saveJson(dst, model)
return dst
end
components.editor.timer.defaults
the order of the table must be as same as the model.props table in index and must be sorted in alphavetical order
local M = {
actionName = "",
delay = 0,
iterations = 1,
name = "timer-new",
}
UI
(TBI) click the field name ‘url’ in the prop’s table to open asset table
template
editor.template.components.pageX.videos.video
local props = {
actionName = "{{actionName}}",
delay = {{delay}},
iterations = {{iterations}},
name = "{{name}}",
}
return require("components.kwik.layer_video").new(props)
Video Replacement allows you to configure a video that will appear in replacement of an image layer. For example, you can draw a blank rectangle image in photoshop, and then you publish images to Solar2D project.In Kwik visiual editor, select the blank rectanglelayer you want to replace for a video and click Video replacement on Kwik editor.
Properties about the current layer content will appear when you click a layer in the list. It is important to match the same layer content size and position with the final video size.
File
Use the Browser button to point to your external file (you may simply write an URL here if your video is available from a website, for example).
Width and Height
these fields allow you to change the auto captured layer settings.
Auto Play
When enabled, it will play the video in the moment the page starts.
Action
It allows you to select an previously made action to be triggered when the video completes.
Loop
it loops the video playing.
Rewind at end
the video timeline is rewind back to the beginning.
module
components.kwik.layer_video
local M = {}
...
...
function M:didShow(UI)
self.videoObj = video.performWithDelay( self.delay, function()
if self.actionName then
UI.scene:dispatchEvent({name = self.actionName })
end
end, self.iterations)
end
--
components.editor.video.index
local model = {
id ="video",
props = {
{name="actionName", value=""},
{name="delay", value=0},
{name="iterations", value = 1},
{name="name", value = ""},
}
}
components.editor.video.defaults
the order of the table must be as same as the model.props table in index and must be sorted in alphavetical order
local M = {
actionName = "",
delay = 0,
iterations = 1,
name = "video-new",
}
test/base-proj/Solar2D/editor.template
obsolete develop/UXP/kwik-exporter/plugin/kwik/base-proj/Solar2D/editor.template
.
├── assets
│ ├── audios
│ │ ├── long
│ │ ├── short
│ │ └── sync
│ │ ├── en
│ │ ├── jp
│ ├── fonts
│ ├── images
│ │ └── pageX
│ ├── model.json
│ ├── particles
│ ├── sprites
│ ├── thumbnails
│ ├── videos
│ └── www
├── build.settings
├── commands
│ └── pageX
├── components
│ └── pageX
│ ├── audios
│ ├── layers
│ │ ├── layer_image.lua
│ ├── groups
│ ├── page
│ │ └── controllers
│ ├── timers
│ ├── variables
│ └──index.lua
├── config.lua
├── mediators
├── models
example bookFree/page1
.
├── assets
│ ├── images
│ │ ├── page1
│ │ │ ├── bg.png
│ │ │ ├── bg@2x.png
│ │ │ ├── bg@4x.png
│ │ │ ├── gotoBtn.png
│ │ │ ├── gotoBtn@2x.png
│ │ │ ├── gotoBtn@4x.png
│ │ │ ├── title.png
│ │ │ ├── title@2x.png
│ │ │ └── title@4x.png
│ │ └── page2
│ └── thumbnails
│ ├── page1.png
│ └── page2.png
├── commands
│ ├── page1
│ │ └── eventOne.lua
│ └── page2
├── components
│ ├── page1
│ │ ├── audios
│ │ ├── layers
│ │ │ ├── bg.lua
│ │ │ ├── gotoBtn.lua
│ │ │ ├── gotoBtn_animation.lua
│ │ │ ├── title.lua
│ │ │ └── title_animation.lua
│ │ ├── groups
│ │ ├── page
│ │ ├── times
│ │ ├── variables
│ │ └── index.lua
│ └── page2
├── main.lua
├── mediators
│ ├── page1Mediator.lua
│ └── page2Mediator.lua
├── models
├── page1
│ ├── bg.json
│ ├── events
│ │ └── eventOne.json
│ ├── gotoBtn.json
│ ├── gotoBtn_animation.json
│ ├── index.json
│ ├── title.json
│ └── title_animation.json
└── page2
https://forums.solar2d.com/t/new-icon-size-for-ios/354492
iOS loads Apple’s launch images of LaunchScreen.storyboardc made by Xcode. You can create it with Xcode.
On Android:
On iOS:
Kwik4 https://kwiksher.com/doc/kwik_toolset/project_and_pages/project_properties/publish/
Copy these png files to ./build folders ?
iOS
https://docs.coronalabs.com/guide/distribution/buildSettings/index.html#custom-app-icons
├── Images.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── Icon-1024.png
│ ├── Icon-120.png
│ ├── Icon-152.png
│ ├── Icon-167.png
│ ├── Icon-180.png
│ ├── Icon-40.png
│ ├── Icon-58.png
│ ├── Icon-76.png
│ ├── Icon-80.png
│ └── Icon-87.png
└── Contents.json
├── launchimage-master
├── Launch.png
├── Launch@2x.png
├── Launch@3x.png
├── LaunchImage
├── LaunchImage.xcodeproj
└── README.md
Android
TBI for Adaptive Icons Android 8 and Later
https://docs.coronalabs.com/guide/distribution/androidResources/index.html
AndroidResources
├── AndroidResources
└── res
├── mipmap-anydpi-v26
│ └── ic_launcher.xml
├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
└── values
└── values.xml
Icon-xxxhdpi.png 192 × 192
Icon-xxhdpi.png 144 × 144
Icon-xhdpi.png 96 × 96
Icon-hdpi.png 72 × 72
Icon-mdpi.png 48 × 48
Icon-ldpi.png 36 × 36
HTML5
https://forums.solar2d.com/t/home-screen-icon-bookmark/347798/4
57×57 px
60×60 px
72×72 px
76×76 px
114×114 px
120×120 px
128×128 px
144×144 px
152×152 px
180×180 px
192×192 px
<!-- To support old sizes -->
<link rel="apple-touch-icon" sizes="57x57" href="http://www.example.com/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="72x72" href="http://www.example.com/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="114x114" href="http://www.example.com/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="144x144" href="http://www.example.com/apple-touch-icon-144x144.png">
<!– To support new sizes –>
<link rel=”apple-touch-icon” sizes=”60×60″ href=”http://www.example.com/touch-icon-iphone-60×60.png”>
<link rel=”apple-touch-icon” sizes=”76×76″ href=”http://www.example.com/touch-icon-ipad-76×76.png”>
<link rel=”apple-touch-icon” sizes=”120×120″ href=”http://www.example.com/touch-icon-iphone-retina-120×120.png”>
<link rel=”apple-touch-icon” sizes=”152×152″ href=”http://www.example.com/touch-icon-ipad-retina-152×152.png”>
<link rel=”apple-touch-icon” sizes=”180×180″ href=”http://www.example.com/apple-touch-icon-180×180.png”>
<!– To support Android –>
<link rel=”icon” sizes=”192×192″ href=”http://www.example.com/touch-icon-192×192.png”>
<link rel=”icon” sizes=”128×128″ href=”http://www.example.com/niceicon.png”>
Linux
[Desktop Entry]
Name=Solar2DTux
Comment=Solar2DTux SDK
Exec=usr/bin/Solar2DSimulator
Icon=Solar2DTux
Terminal=false
Type=Application
StartupNotify=true
StartupWMClass=Solar2DSimulator
Categories=Utility;
> ./linuxdeploy-x86_64.AppImage --appdir AppDir --executable ./foobar --icon-file ./icon.png --output appimage
Windows
macOS
android(FireTV)
tvos(appleTV)
assets/tvAsset/tvosLaunch-assets/Icon-tvOS-Launch.png
assets/tvAsset/tvosLaunch-assets/Icon-tvOS-TopShelf.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-Background.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoA.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoB.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoC.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoD.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-Background.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoA.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoB.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoC.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoD.png
https://www.ios-resolution.com/
iPhone 13 Pro Max 1284x2778
iPad Pro 2048 x 2732
iPhone X 1125 x 2436
iPhone 11 Pro 1242 x 2688
https://forums.solar2d.com/t/iphone-11-pro-max-screen-shots-in-simulator/151266/11
Kwik4 Ulitimate Config
1280 x 1920
https://kwiksher.com/doc/getting_startted/guidelines/blend_mode/
https://kwiksher.com/doc/getting_startted/guidelines/screenshot/
background
1440 x 2280
iPhone X 1440 × 2772
https://forums.solar2d.com/t/how-to-define-application-working-area-on-the-screen/351066/11
https://forums.solar2d.com/t/safe-area-on-iphone-x-and-11-problem/353608
note
https://www.photoshopessentials.com/basics/pixels-image-size-resolution-photoshop/
resolution only affects the size of the printed version of the image. It has no effect at all when viewing the image on screen.
figma to photoshop
https://docs.coronalabs.com/guide/distribution/buildSettings/index.html
https://docs.coronalabs.com/guide/distribution/advancedSettings/index.html
When orientation supports both of landscape and portrait, the main content area (a display group) should be translated to the center of screen.
each layers generated from Photoshop has fixed number of coordiate (x, y), for instance the center position of landscape 1920x1080 is at (1920/2, 1080/2) in landscape. If the actual device is rotated to portrait, the center position is (1080/2, 1920/2), for screen rotation event is detected, let’s move each layer by (1920/2 - 1080/2, 1080/2- 1920/2)
sceneGroup.x, sceneGroup.y = display.contentCenterX, display.contentCenterY
orientation
{
default = "landscapeRight",
supported = {
"portrait", "portraitUpsideDown",
"landscapeLeft", "landscapeRight"
},
plguins
splashScreen
{
enable = true,
image = "mySplashScreen.png"
},
android
https://docs.coronalabs.com/guide/distribution/buildSettings/index.html#android-build-settings
{
versionCode = "11",
usesPermissions =
{
"android.permission.INTERNET",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION",
"com.android.vending.CHECK_LICENSE",
"com.android.vending.BILLING",
},
usesExpansionFile = true,
usesFeatures =
{
{ name="android.hardware.camera", required=true },
{ name="android.hardware.location", required=false },
{ name="android.hardware.location.gps", required=false },
},
}
iphone
https://docs.coronalabs.com/guide/distribution/buildSettings/index.html#iOSsettings
{
xcassets = "Images.xcassets",
plist =
{
-- CFBundleIconFiles = {}, -- Obsolete!
UILaunchStoryboardName = "LaunchScreen", -- Required!
UIStatusBarHidden = true,
CFBundleDisplayName = "Solar2D App",
CFBundleName = "Solar2D App",
NSCameraUsageDescription = "This app would like to access the camera.",
NSPhotoLibraryUsageDescription = "This app would like to access the photo library.",
NSPhotoLibraryAddUsageDescription = "This app would like to add the photo library.",
NSAppTransportSecurity = {
NSExceptionDomains = {
[""] = {
NSIncludesSubdomains = true,
NSThirdPartyExceptionAllowsInsecureHTTPLoads = true
},
}
},
}
window
{
defaultMode = "fullscreen",
defaultViewWidth = 640,
defaultViewHeight = 960,
resizable = true,
minViewWidth = 320,
minViewHeight = 480,
enableCloseButton = true,
enableMinimizeButton = true,
enableMaximizeButton = true,
suspendWhenMinimized = true
showWindowTitle = false -- (macOS only)
titleText = {
default = "Window Title Test",
["jp"] = "Window タイトル",
},
}
macos
https://docs.coronalabs.com/guide/distribution/macOSBuild/index.html
{
bundleResourcesDirectory = "osx-resources",
plist =
{
CFBundleURLTypes =
{
{
CFBundleURLName = "My URL Scheme",
CFBundleURLSchemes = {
"myscheme",
},
},
},
CFBundleDocumentTypes =
{
{
CFBundleTypeExtensions =
{
"png",
},
CFBundleTypeIconFile = "app.icns",
CFBundleTypeName = "public.png",
LSHandlerRank = "Alternate",
LSItemContentTypes =
{
"public.png",
},
},
},
entitlements = {
["com.apple.security.personal-information.location"] = true,
},
NSHumanReadableCopyright = "Copyright © 2017 XYZ Company"
},
},
win32
https://docs.coronalabs.com/guide/distribution/win32Build/index.html
{
preferenceStorage = "registry",
singleInstance = false,
}
splashScreen
{
ios = {
enable = true,
image = "mySplashScreen_iOS.png"
},
android = {
enable = true,
image = "mySplashScreen_Android.png"
}
},
excludeFiles
{
all = { "Icon*.png", "Images.xcassets", "Icon*.ico", "Icon*.icns" },
android = { "LaunchScreen.storyboardc", "*.aac" },
ios = { "*.ogg" },
macos = { "Icon*.ico" },
win32 = { "Icon*.icns" },
}
config.lua
scale = "letterBox",
fps = 30,
google license key
Application.lua
function M.getPosition(x, y)
local mX = x and (x * 0.25 - 480 * 0.5) or 0
local mY = y and (y * 0.25 - 320 * 0.5) or 0
return mX, mY
end
scene.lua
it handles the orientation change event
function scene:create(event)
...
self.UI.sceneGroup.x = display.contentCenterX
self.UI.sceneGroup.y = display.contentCenterY
self.UI.sceneGroup.anchorX = .5
self.UI.sceneGroup.anchorY = .5
self.view:insert(self.UI.sceneGroup)
...
end
local function onOrientationChange (event)
if scene.view then
local sceneGroup = scene.UI.sceneGroup
local currentOrientation = event.type
local ratio = 480/320
local reverse = 320/480
....
sceneGroup.x, sceneGroup.y = display.contentCenterX, display.contentCenterY
if event.type =="portrait" and event.delta == -90 then
sceneGroup:scale(ratio, ratio)
elseif event.type =="portraitUpsideDown" and event.delta == -90 then
...
end
TODO this plugin locks the screen orientation but can not find the document.
{
["plugin.orientation"] = {
publisherId = "tech.scotth",
},
},
TODO save & cancel buttons goes out at the bottom in landscape
android
iOS
Linux
https://forums.solar2d.com/c/beta-testing/linux/118
https://forums.solar2d.com/t/how-to-build-solar2d-on-linux/354787/3
audio.loadSound() won’t work https://forums.solar2d.com/t/audio-loadsound-wont-work/355589
Linux and Zerobrane Setup https://forums.solar2d.com/t/linux-and-zerobrane-setup/354500
Icon
these .ico and .icns files are created with the following commands in Termnal app. Plese make icon_xx.png files and install image magic to Window PC
rm -r icon.iconset
cp -r desktop_icon-assets icon.iconset
cd icon.iconset
rm icon_48x48.png
cp icon_32x32.png icon_16x16@2x.png
mv icon_64x64.png icon_32x32@2x.png
cp icon_256x256.png icon_128x128@2x.png
cp icon_512x512.png icon_256x256@2x.png
mv icon_1024x1024.png icon_512x512@2x.png
cd ..
iconutil --convert icns --output Icon-osx.icns icon.iconset
Icons
android(FireTV)
tvos(appleTV)
assets/tvAsset/tvosLaunch-assets/Icon-tvOS-Launch.png
assets/tvAsset/tvosLaunch-assets/Icon-tvOS-TopShelf.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-Background.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoA.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoB.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoC.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Large-LogoD.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-Background.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoA.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoB.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoC.png
assets/tvAsset/tvosPallax-assets/Icon-tvOS-Small-LogoD.png
Parallax Previewer App
Solar2D does not need .lsr file to buld TVOS app. it needs those Logo.png
PSD files - Image assets from layers
Icon
assets/DeskTopIcon/Icon-win32.ico
win_icon.bat
set MYDIR=desktop_icon-assets
set ICON_FILE=Icon-win32.ico
magick ./%MYDIR%/icon_16x16.png ./%MYDIR%/icon_32x32.png ./%MYDIR%/icon_48x48.png %ICON_FILE%
Image Magick http://www.imagemagick.org/
assets/model/schema
embedded in codes – TBI to be extracted
TODO:add ext codes
extCodes
libs
p1
user_codes.lua
ext_001.lua
ext_002.lua
commands/
button_name_001.lua
action_name_001.lua
user_codes.lua
p2/
=> build4/
```lua
function ActionCommand:new()
local command = {}
--
function command:execute(params)
local UI = params.UI
local sceneGroup = UI.scene.view
local layer = UI.layer
local phase = params.event.phase
local event = params.event
{{#vvar}}
{{vvar}}
{{/vvar}}
{{#arqCode}}
{{arqCode}}
{{/arqCode}}
end
return command
end
```
or
ext_lib_codes.lua
```
local _K = require "Application"
{{#extLib}}
local {{name}} = requireKwik("{{libname}}")
{{/extLib}}
--
{{#TV}}
local kInputDevices = require("extlib.tv.kInputDevices")
{{/TV}}
function _M:localVars(UI)
local sceneGroup = UI.scene.view
local layer = UI.layer
{{#extCodeTop}}
{{ccode}}
{{arqCode}}
{{/extCodeTop}}
end
```
model.json
{
"page":1,"alias":"title","isTmplt":false,
"audios":[null],
"read2me":[null],
"videos":[null],
"PNGs":[null],
"sprites":[null],
"particles":[null],
"WWW":[null],
"thumbnails":[null],
"images":[
"bg@4x.png", "bg@2x.png", "bg.png",
null
],
"shared":[
null
]
}
{
"page":{{page}},"alias":"{{alias}}","isTmplt":{{isTmplt}},
{{#layers}}
"{{layer}}":{
"x":{{x}}, "y":{{y}},
"width":{{width}}, "height":{{height}},
"alpha":{{alpha}}, "ext":"{{ext}}" },
{{/layers}}
"audios":[
{{#audios}}"{{filename}}",{{/audios}} null],
"read2me":[
{{#read2me}}{"foldername":"{{foldername}}", "filenames":[{{#filenames}}"{{.}}",{{/filenames}} null] },{{/read2me}} null],
"videos":[
{{#videos}}"{{filename}}",{{/videos}} null],
"PNGs":[
{{#PNGs}}"{{foldername}}",{{/PNGs}} null],
"sprites":[
{{#sprites}}"{{filename}}",{{/sprites}} null],
"particles":[
{{#particles}}{"filename":"{{filename}}","PNG":"{{PNG}}"},{{/particles}} null],
"WWW":[
{{#WWW}}{"filename":"{{filename}}","foldername":"{{foldername}}"},{{/WWW}} null],
"thumbnails":[
{{#thumbnails}}{{#filenames}}"{{.}}",{{/filenames}} {{/thumbnails}}null],
"images":[
{{#images}}
"{{filename}}@4x.{{filetype}}", "{{filename}}@2x.{{filetype}}", "{{filename}}.{{filetype}}",
{{/images}}
null
],
"shared":[
{{#shared}}
"{{filename}}@4x.{{filetype}}", "{{filename}}@2x.{{filetype}}", "{{filename}}.{{filetype}}",
{{/shared}}
null
]
}
├── App
├── contentX
├── assets
│ ├── audios
│ │ ├── short
│ │ ├── long
│ │ └── sync
│ ├── images
│ └── model.json
├── commands
│ └── pageX
├── components
│ ├── pageX
│ │ ├── audios
│ │── layers
│ │ ├── animations
│ │ ├── images
│ │ ├── interactions
│ │ ├── physics
│ │ └── replacements
│ ├── groups
│ ├── page
│ ├── timers
│ ├── variables
│ └── index.lua
│
├── defaults
│ ├── audio_properties.xml
│ ├── layer_properties.xml
│ ├── linear_anim_properties.xml
│ └── spritesheet_properties.xml
├── models
├── assets
│ ├── audios
│ │ ├── short
│ │ ├── long
│ │ └── sync
│ ├── index.json
│ ├── particles
│ ├── sprites
│ ├── videos
│ └── www
├── lproj
└── pageX
├── components
│ ├── audios
│ ├── layers
│ ├── groups
│ ├── page
│ ├── timers
│ └── variables
├── commands
├── index.json
https://github.com/joehinkle11/Automated-Corona-Builder
Windows
https://forums.solar2d.com/t/command-line-build/347047/20
cd “%CORONA_PATH%” Native\Corona\win\bin\CoronaBuilder.exe build –lua Z:\Projects\b2-win-html.lua
Mac
https://forums.solar2d.com/t/command-line-build-documentation-macos/348547/5
~/Library/Application\ Support/Corona/Native/Corona/mac/bin/CoronaBuilder.app/Contents/MacOS/CoronaBuilder build /path/to/params.lua
--Android:
local params = { platform='android', appName='appName', appVersion='1.1.2',
certificatePath = 'path\_to\_keystore', keystorePassword = '\*\*\*\*\*',
keystoreAlias = 'alias', keystoreAliasPassword = '\*\*\*\*\*\*',
androidVersionCode = '3', dstPath='/path/to/destination/folder',
projectPath='/path/to/project/folder', androidAppPackage='com.example.myapp',
androidStore = 'none' } return params
--HTML:
local params = { platform='html5', appName='My app', appVersion='3.2.1',
dstPath='/path/to/destination/folder', projectPath='/path/to/project/folder',
includeStandardResources = 'true', } return params
--iOS:
local params = { platform='ios', appName='My App', appVersion='1.1.1',
certificatePath = '/path/to/certificate/myapp.mobileprovision',
dstPath='/path/to/destination/folder', projectPath='/path/to/project/folder',
} return params
-- Mac:
{ platform='macos', appName='My App', appVersion='3.2.1',
certificatePath = '/path/to/certificate/myapp.mobileprovision',
dstPath='/path/to/destination/folder', projectPath='path/to/project/folder',
} return params
Node.js ( HTML5 Builder )
Solar2D API to be included to Kwik framework
Audio
Social
transition.*
mesh
2.5D — Perspective and Depth
game controllers
Solar2D video texture
Tiled
https://forums.solar2d.com/t/smooth-scrolling-of-top-down-tiled-map/150199/2
“cull based on a 3x3 area of sub-screens centered around player. "
https://www.dynetisgames.com/2018/02/24/manage-big-maps-phaser-3/
Texture atlas function in kwik?
Audio sprites?
Interactive
Lottie ts to lua? ⭐️
Overview
put .png/.jpg into App/contentX/assets/images manually or use kwik-export plugin for Ps/XD to publish images to App folder
kwik-editor traverses the assets folder to output .json for models and .lua for scenes/pageX and components/pageX
you can edit props or positions of images or attach a type of class such as animation, button …
you can creat an event and a corresponding action of code such as playAnimation, hideLayer, playAudio …
if you manually add a .lua file, you need to update scenes/pageX/index.lua too. You can use kwik-generate-index that traverses pageX folder of commands, components and scenes for synclonizing the lisf of .lua for pageX context defined in the index.lua.
├── App
├── contentX
├── assets
├── audios
│ ├── short
│ ├── long
│ └── sync
├── images
│ └── pageX
│ ├── bg.png
├── folder
| ├─ .png
| └── .png
└── .png
Manually put images in App/contentX/images/pageX folder
kwik-generate-model
it creates .json under models folder
Alternatively
Photoshop
create folders for a layer group in order to export each image of the group
the plugin exports index.json too
├── models
│ ├── assets
│ │ ├── audios
│ │ │ ├── short
│ │ │ ├── long
│ │ │ └── sync
│ │ ├── index.json
│ │ ├── images
| | ├── pageX
| | ├── layerX.json (ReadOnly)
│ ├── pageX
│ ├── index.json
│ └── layers
│ ├── layerX.json (RW from KwikLiveEditor)
later you edit it with KwikLiveEditor, then layerX.json is created under models/pageX/layers folder. images/pageX/layerX.json is readonly.
put audio files under assets/audios folder
.
├── assets
│ ├── audios
│ │ ├── short
│ │ │ └── ballsCollide.mp3
│ │ ├── long
│ │ │ ├── Gentle-Rain.mp3
│ │ │ └── Tranquility.mp3
│ │ └── sync
│ │ ├── en
│ │ │ ├── cat.mp3
│ │ │ ├── kwik.mp3
│ │ │ └── narration.mp3
│ │ ├── jp
│ │ │ ├── cat.mp3
│ │ │ ├── kwik.mp3
│ │ │ └── narration.mp3
│ │ ├── pageX_Text1.mp3
│ │ ├── pageX_Text1.txt
│ │ ├── page02_Text1.mp3
│ │ └── page02_Text1.txt
kwik-generate-model
creates .json under models/assets/audios
├── models
│ ├── assets
│ │ ├── audios
│ │ │ ├── short
| | | | ├── audioX.json
│ │ │ ├── long
│ │ │ └── sync
│ │ ├── index.json
│ │ ├── images
│ ├── pageX
│ ├── index.json
kwik-editor
audio files are not subjected to a page yet. So you can assign an audio to any pages with the tool.
edit audio properties
models/assets/audios/sounds/audioX.json
{
"name":"audioX",
"autoPlay":false,
"channel":2,
"type":"sound"
}
you assign an audio entry to a page with kwik-editor. Then the tool adds the entry in models/pageX/index.json
pageX/index.json
{
"components": {
"audios": {"short": [{"name":"audioX"}],
"layers": [{}]
},
"commands": [],
}
you don’t need to use kwik-generate-model nor kwik-editor to output lua files. You can skip making .json files of these tools, and you create a lua file manually into a folder, and append a name of additional file to scenes/pageX/index.lua
At runtime, Kwik Code Framework reads scenes/pageX/index.lua to load each .lua files of pageX. The object names for commands, compnents, scenes are defined in the index.lua.
kwik-genereate-index is a tool to update the index.lua from traversing the folders above.
you don’t need to use kwik-generate-index tool. You can manually edit it but it would be better to generate the index.lua with the tool.
Alternatively, there is another tool named kwik-scaffold-lua. This tool scafolds .lua files from components/pageX/index.lua. The tool does not overwrite .lua if exists, and may delete .lua if not defined in index.lua
Which Workflow do you like?
A
use kwik-generate-index everytime after you update commands, components, senes lua files.
B
use kwik-scaffold-lua to create a lua for commands, components then edit the lua file.
I like A because thinking about files/folders strcure with a file explorer, and coping/pasting an exsiting file could be easier when coding is in progress.
To initiate a project, B would work quicky to make a skelton structure.
put images in App/demo/assets/page01
Tool
display.loadRemoteImage
https://solar2d.com/images/logo.png
access images of XD shared view? or able to load them all?
network.download
Create layer structure
frontEnd uses Adobe React Spectrum (in future, support screen reader etc)
REST API to get the list of images and post the orderd list back
GET /layers
- background: []
- button: []
- logo: []
- message: []
- panel:
Change the order of layers
POST /layers
- logo: []
- panel:
- message: []
- button: []
- background: []
Set animation and button
test KwikTheCat for evaluation
one by one
POST /layers/logo
classes:
- animation
Or
POST /layers/logo
Content-Type: application/yaml
- transition: bounce
- params:
height: 400
width: 200
time: 1000
iterations: 0
GUI in Edtior(Solar2D) for transformation, draggable to set a position
POST /layers/panel/button
classes:
- button
All together
POST /layers
- logo: []
classes:
- animation
- panel:
- message: []
- button: []
classes:
- button
- background: []
TODO
swipe page or A/D keys for navigation
test layer groups and add how to work with a layer group in get started
clean image template lua
add animation and button
add audio & video
nagivation & thumnail images
Open Adobe UXP Developer Tool
Add Plugin by selecting develop/UXP/kwik-exporter/dist/manifest.json
Kwik Exporter Panel apppears
From the UXP plugin
Photoshop Files > Open
Double Click the one of .psd in the list to open it for editing.
Solar2D Project > Select Book or create a new Solar2D project
Publish
selected psd files are publihsed to App/book folder
├── Photoshop
│ └── book
│ ├── kwikconfig.json
│ ├── page1,psd
│ └── page2.psd
└── Solar2D
├── rel
├── src
│ ├── App
│ │ └── book <==== the published images/lua files are inside this folder
│ ├── Images.xcassets
│ ├── LaunchScreen.storyboardc
│ ├── assets
│ ├── build.settings
│ ├── commands
│ ├── components
│ ├── config.lua
│ ├── controller
│ ├── en.lproj
│ ├── extlib
│ ├── jp.lproj
│ ├── main.lua
│ └── mySplashScreen.png
├── templates
└── tools
Open main.lua in Solar2D/src with Solar2D Simulator
Kwik Visual Code Editor wiil be loaded and your content(App/book) of your psd files are previewd
For instance, animtion editing
Action editing
App/book/assets/audios/long/GentleRain.mp3
App/book/components/page3/index.lua
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page3",
components = {
layers = { },
audios = {
long = {"GentleRain", "Tranquility" },
short ={} ,
},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "onComplete", "play" },
onInit = function(scene) print("onInit") end
})
--
return scene
App/book/components/page3/audios/long/GentelRain.lua
local name = ...
local props = {
name = "GentleRain",
type = "stream",
autoPlay = false,
channel = 1,
folder = nil,
filename = "GentleRain.mp3",
}
local M = {
-- name = {{aname}},
-- type = {{atype}},
-- language = nil -- or {"en", "jp"},
-- filename = "{{fileName}}",
-- folder = nil
-- allowRepeat = false,
-- autoPlay = {{aplay}},
-- deplay = {{adelay}},
-- volume = {{avol}},
-- channel = {{achannel}}
-- loops = {{aloop}},
-- fadein = {{tofade}},
-- retain = {{akeep}}
}
-- you can play it with UI.audios[self.name]:play()
return require("components.kwik.page_audio").set(props)
TODO reame controls to asset?
For instance, a spritesheet consists of an imagesheet png in assets/sprites folder, properties like sequence data are defined in layerA_sprite.lua in components/page/layers folder, and components/page/index.lua declears the sprite class for layerA
App/book/assets/sprites/imagesheet.png
spritesheet.lua in the same folder has the sheetInfo properites.
App/book/components/index.lua
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={} },
{ layerA={class={"sprite"}} },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = {}
},
commands = {},
onInit = function(scene) print("onInit") end
})
App/book/components/layers/layerA.lua
-- layer properties
local Props = {
blendMode = "normal",
height = 100,
width = 100,
kind = pixel,
name = "layerA",
x = display.contentCenterX,
y = display.contentCenterY,
alpha = 100/100,
}
App/book/components/layers/layerA_sprite.lua
sheetInfo “spritesheet” loads spritesheet.lua in the same folder
local M = {
name = "spritesheet",
class = "spritesheet",
type = "uniform-sized", -- TexturePacker, Animate
assets = {
filename = "imagesheet.png",
sheetInfo = "spritesheet",
sheetContentWidth = 376, -- same size or loaded from sheetInfo
sheetContentHeight = 188, -- same size or loaded from sheetInfo
numFrames = 2, -- same size or loaded from sheetInfo
width = 188, -- same size, disable for TP, Aniamte
height = 188, -- same size, disable for TP, Animate
},
sequenceData = {
{
name = "default",
count = 2,
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
start = 1,
time = 1000,
},
{
name = "test",
frames = {1,2},
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
time = 1000,
}
},
actioneName = "",
}
For instance components/pageX/page/swip.lua enables a page transition, and components/pageX/audios folder can have multiple audio lua files for instance short/audioOne.lua, long/audioTwo.lua. short audio file is loaded in to memory with audio.loadSound(), and long audio file is streaming with audio.loadStream().
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "pageX",
components = {
layers = {
{ bg={} },
},
audios = { short = {"audioOne"}, long= {"audioTwo" },
groups = { },
timers = { },
variables = { },
page = { "controllers.swipe" }
},
commands = { },
onInit = function(scene) print("onInit") end
})
--
return scene
You can add a common component to components/common directory
And let it pass in arguments of bootstrap function in main.lua
local common = {commands = {"myEvent"}, components = {"myComponent"}}
require("controller.index").bootstrap({
name="book", sceneIndex = 1, position = {x=0, y=0},
common =common
})
Common components are executed after all the layer components of a scene are rendered. So you can access a layer component by UI.layers table.
for instance, myComponent.lua attaches a tap listener
function _M:didShow(UI)
local sceneGroup = UI.scene.view
UI.layers.bg:addEventListener("tap", function(event)
print("bg is tapped")
end)
end
Put a lua file of your own class in commompinets/custom folder
Set your classname to a layer in App/bookX/pageX/index.lua
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={ } },
{ gotoBtn={ } },
{ title={ class={"myClass"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { },
onInit = function(scene) print("onInit") end
})
--
return scene
myClass.lua will be called with a display image object of a layer associated with
local yourLibrary = require("lib.yourLibrary")
--
--
local M = {}
--
function M:init(UI)
end
--
function M:create(UI)
local obj = UI.sceneGroup[self.name]
end
--
function M:didShow(UI)
end
--
function M:didHide(UI)
end
--
function M:destroy(UI)
end
M.set = function(instance)
return setmetatable(instance, {__index=M})
end
--
return M
properties can be defined in App/bookX/pageX/components/layers/layerX_myClass.lua
M.params={
mytext = "hello my class",
}
...
...
return M
you can use a classname as name space
For instance, transition2.lua is a custom class and it has functions like “to”, “from”, “moveBy” etc.
you can speficy a function name as classoption with “.” in App/bookX/components/pageX/index.lua
syntax is className.classOption
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ title={ class={"transition2.to"} } },
},
...
...
})
--
return scene
title layer now uses transition2 class, you can create your own class and write codes in components/common folder. For instance,
components/custom/transition2.lua
classOption contains “to”
local parent,root, M = newModule(...)
--
local transition2 = require("extlib.transition2")
local _layerProps = {
name = M.name,
text = "hello my class",
}
--
function M:init(UI)
end
--
function M:create(UI)
end
--
function M:didShow(UI)
local classOption = self.classOption or "to"
local obj = UI.sceneGroup[self.name]
if obj then
self.transition_id = transition2[classOption](obj, self.params or {x=10})
end
end
--
function M:didHide(UI)
--transtiion2.cancel(self.transition_id)
end
--
function M:destroy(UI)
end
--
function M:new(props)
return self:newInstance(props, _layerProps)
end
--
return M
self.params for transition2.lua should be defined in in App/bookX/pageX/layers/title_transiton2.lua
M.params ={
{x = 260, delay = 400},
{y = 260, xScale = -0.8, delay = 800},
{xScale = -1, yScale = 1, delay = 400},
{x = 60, xScale = -0.6, yScale = 0.6, delay = 1200},
{rotation = 135, delay = 400},
{x = 260, y = 60, alpha = 0, delay = 400},
{rotation = 0, alpha = 1, delay = 800},
{x = 60, delay = 400},
{xScale = 0.8, yScale = 0.8, delay = 400}
}
use classOption with an opion value for instance “to” for trasition2 in M.props
local M={
path = "bookTest.components.page2.layers.three.index",
name = "three",
}
M.props = {
five_transition2 = {classOption="to"},
}
return require("components.kwik.importer").new(M)
ToDo
### selectLayer with class if exists or not
GET /bookFree/page1/title/?class=transition2.to
###
POST /bookFree/page1/title/?class=transition2.to
Content-Type: application/lua
{
{x = 260, delay = 400},
{y = 260, xScale = -0.8, delay = 800},
{xScale = -1, yScale = 1, delay = 400},
{x = 60, xScale = -0.6, yScale = 0.6, delay = 1200},
{rotation = 135, delay = 400},
{x = 260, y = 60, alpha = 0, delay = 400},
{rotation = 0, alpha = 1, delay = 800},
{x = 60, delay = 400},
{xScale = 0.8, yScale = 0.8, delay = 400}
}
The editable tables for spritesheet, and sync text&audio looks lik this
commands/myAction.lua are triggeded with a dispatchEvent fucntion
UI:dispatchEvent({
name = "myAction",
UI = UI
})
this myAction is defined in commands table in components/pageX/index.lua
For a button, you see in components/pageX/index.lua, buttonOne layer has two events. they are tap and drag. These tap and drag events are handleld with commands/buttonOne/tap.lua and commands/buttonOne/drag.lua
commands are called as Action in Kwik. You can dispatchEvent with params to myAction.lua, myEvents.testHandler.lua
{
name = "kwik4_1280x1920",
layers = {
{ bg={} },
{ buttonOne={ events = {tap, drag}} },
},
components = {
audios = { },
groups = { },
timers = { },
variables = { },
others = { }
},
commands = { "myAction", "myEvents.testHandler" },
onInit = function(scene) print("onInit") end
}
The assicated lua files are located in the commands folder
create a command lua in commands/common directory, for example
commands/common/myEvent.lua
local instance = require("commands.kwik.baseCommand").new(
function (params)
local UI = params.UI
print("myEvent")
end)
return instance
And let it pass in arguments of bootstrap function in main.lua
local common = {commands = {"myEvent"}, components = {myComponent={}}}
require("controller.index").bootstrap({
name="book", sceneIndex = 1, position = {x=0, y=0},
common =common
})
context:init function of controller/ApplicationContext.lua automtaically adds it
this context init is called everytime when a scene is loaded. You can dispatch an event to executre myEvent.lua like this
UI:dispatchEvent({
name = "common.myEvent",
UI = UI
})
index.lua
layers = {
...
rectCopied = {class = {"imported"}
...
}
rectCopied_imported.lua
local props={
path = "bookTest.components.parts.layers.buttonGroup.redRect",
name = "rectCopied",
class = {"button", "linear"},
text = "hello importer class",
}
local layerProps = {
x = display.contentCenterX - 150,
y = display.contentCenterY - 100,
color = {1, 1, 0}
}
props.layerProps = layerProps
props.classProps = {
button = {},
linear = {}
}
return require("components.kwik.importer").new(props)
graph TB
subgraph parts
bg
square
subgraph buttonGroup
redRect
greenRect
blueRect
end
subgraph redGroup
rectCopied
circleRed
end
rectCopied -.import.-> redRect
end
subgraph page1
bg1[bg]
square1[square]
buttonGroup1[buttonGroup]
end
square1 -.import.-> square
buttonGroup1 -.import.-> buttonGroup
TODO
REST API
layerProps, classProps
group props
props = {
redRect = {layerProps = {x=display.contentCenterX+50, color = {0.5,0,0}}},
redRect_button = {},
}
GUI class “imported”
buttonGroup1, bouttonGroup2 are imported from parts/buttonGroup
index.lua
layers = {
{ bg={} },
{ buttonGroup1={ class={"imported"} } },
{ buttonGroup2={ class={"imported"} } },
components/layers
page1 buttonGroup1_import.lua
local M={
path = "bookTest.components.parts.layers.buttonGroup.index",
name = "buttonGroup1",
}
M.props = {
redRect = {layerProps = {x=display.contentCenterX+50, color = {0.5,0,0}}},
redRect_button = {},
redRect_linear = {},
-- greenRect = {layerProps = {x=200}},
greenRect = {layerProps = {x=display.contentCenterX+100, color = {0,0.5, 0}}},
blueRect = {layerProps = {x=display.contentCenterX+150, color = {0,0,0.5}}},
}
return require("components.kwik.importer").new(M)
page1 square_imported.lua
local M={
path = "bookTest.components.parts.layers.square",
name = "square",
}
return require("components.kwik.importer").new(M)
redGroup/rectCopied is imported from buttonGroup/Rect
local layerProps = {
x = display.contentCenterX - 150,
y = display.contentCenterY - 100,
color = {1, 1, 0}
}
index.lua
layers = {
{ bg={} },
{ buttonGroup={
{redRect ={class={"button", "linear"}}},
{greenRect = {}},
{blueRect = {} }}
},
{ redGroup= {
{rectCopied = {class = {"imported"}}},
{circleRed = {}}
}
}
components/layers files
bg.lua
square.lua
square_animation.lua
buttonGroup
redGroup
parts redGroup/rectCopied_imported.lua
props.layerProps
props.classProps
local M={
path = "bookTest.components.parts.layers.buttonGroup.redRect",
name = "rectCopied",
class = {"button", "linear"},
text = "hello importer class",
}
local layerProps = {
blendMode = "normal",
height = 50,
width = 50,
kind = solidColor,
name = "redRect",
type = "png",
x = display.contentCenterX - 150,
y = display.contentCenterY - 100,
alpha = 100/100,
color = {1, 1, 0}
}
props.layerProps = layerProps
props.classProps = {
button = {},
linear = {}
}
return require("components.kwik.importer").new(props)
-- this import need to call redRect.lua, redRect_button.lua redRect_linear.lua
the syntax is {{parent}}.{{name}}. You can access it like
local obj = UI.sceneGroup(“three.eight”)
A parent.name should be unique in all the layers in a page
Application.lua
function newModule(name)
local M = {}
local parent = name:match("(.-)[^%.]+$")
local root = parent:sub(1, parent:len()-1):match("(.-)[^%.]+$")
M.name = name:sub(root:len()+1)
local isLayers = M.name:find("layers.")
if isLayers then
M.name = M.name:sub(8)
end
M.newInstance = newInstance
return parent, root, M
end
for example, let’s make an instance of layer group - three
layers = {
{one = {}},
{two={class={"linear"}}},
{three = {
{ four={}},
{ five={class={"myClass"}}},
{ six={
{ seven = {class={"myClass", "linear"}}},
{ eight = {}}
}
}
}}
},
You can import it to another page like this
layers = {
{ bg={} },
{ three={ class={"imported", "linear"} } },
},
three_imported.lua
local M={
path = "bookTest.components.page2.layers.three.index",
name = "three",
}
M.props = {
four={layerProps = {color={1,0,1}}},
five={layerProps = {color={0,1,0}}},
five_myClass = {},
six={
seven = {layerProps = {color={1,0,0}}},
seven_myClass = {},
seven_linear = {},
eight = {layerProps = {color={1,1,0}}}
}
}
return require("components.kwik.importer").new(M)
three_linear.lua
function M:didShow(UI)
local obj = UI.sceneGroup["six.eight"]
local linearOne, linearTwo
linearOne = function()
transition.to(obj, {xScale = 2, yScale=2, onComplete = linearTwo})
end
linearTwo = function()
transition.to(obj, {xScale = 1, yScale=1, onComplete = linearOne})
end
linearOne()
end
Project and file names
Avoid long names for your projects and files and, DO NOT use characters like +-<>%,#;!.
Layer names
Before you start adding buttons and animations, follow the rules below. It is much easier to add interactivity with finalized names rather than to edit all of them afterward. Basic rules include:
Only user Western characters are allowed for layer names.
DO NOT use characters such as + - <> %,#;!. for naming layers
These characters conflict with Lua language. Kwik removes these “strange” characters but, if they were used before in any button actions, it would generate errors during export.
Do NOT name your layers starting with numbers.
For example, a layer named “1”, will generate an error. A layer named “01_Name” will also generate an error. However, a layer named “Name_01” is correct.
Do not use Lua commands as names.
For example, a layer named “if” will conflict with the command if. Some Lua commands are: if, end, local, transition, play. A full list of Lua commands is here. Kwik will generate an alert error if it finds layers named with Lua commands
Avoid having multiple layers with the same name.
Kwik will not export the second layer with the same name as it will overwrite the first one, (which will make the Lua code crazy). Kwik will provide an alert error if it finds layers with identical names.
Avoid long names.
All layer names become variables when exported, meaning they use more memory and are more difficult to read. Also, they are going to be shortening anyways, as Lua will not allow variable names with more than 15 characters.
Keep Layer Names Short for text layers
Text layer names are originally created using the content of the text from the canvas in Photoshop. If you have a long paragraph, the layer name will be the entire content of the long paragraph! Long layer names cause problems in the generated code, so edit the layer names to make them shorter. (This is the most common problem we have seen from our users reporting issues.) Don’t forget all text, besides the ones used in Sync audio feature, also are exported as images.
Grouped layers
Kwik can export grouped layers as a single image. Use them to create more complex elements (for example, a multi-layered button with text, shadows, etc.), or for the creation of static elements. A common issue is to have all page elements in a group layer that images are not rendered separately. If you have a group with several layers, try to flatten them whenever it is possible. It will make the export process much faster.
Button and Animation names of Kwik components
Although Kwik offers an auto-naming feature, try to enter your own names (follow the rules above). It will help you to quickly edit your actions from the project view. This is a time saver for a project with several actions.
width = 320,
height = 480,
scale = "letterBox"
https://forums.solar2d.com/t/image-scaling/150116/10?u=ymmtny
width 1080, height 1620
https://coronalabs.com/blog/2018/08/08/understanding-content-scaling-in-corona/
https://forums.solar2d.com/t/from-the-blog-understanding-content-scaling-in-corona/348296
config.lua is always written assuming a portrait orientation
https://forums.solar2d.com/t/image-scaling/150116/
The 1.5 is a subtile change, but but instead of waiting until you have a 2160 to use the 2x images, you use them starting at 1620px screens
https://github.com/SpyricGames/Solar2D-Plugins-Public
Screen
iPad Air 1536 x 2048
=> landscape to portrait
User can see display objects at the center only when the device is rotated to portrait mode
iPhone X 1125x2436
=> landscape to portrait
=> landscape to portrait
iPad 1536 x 2048
=> portrait to landscape
User can see the resized display objects and the black area both side
iPhone X 1125x2436
=> portrait to landscape
=> portrait to landscape
you can check out App/book folder from github instead of using Kwik UXP plugin to publish a book folder.
// TODO github url
The checked out book folder has page1 folder without any *.lua nor image files. If you want to rename the page name, just use Finder or File Explorerer, and change the table value in index.lua
assets/images/page1
commands/page1
components/page1
index.lua
local scenes = {"page1"}
return scenes
components/page1/index.lua
empty layers
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
components = {
layers = {},
audios = {
long={ },
short={ }
},
groups = { },
timers = { },
variables = { },
page = { }
},
commands = { },
onInit = function(scene) print("onInit") end
})
--
return scene
Adobe
Photshop
UXP Developer Tool
https://developer.adobe.com/photoshop/uxp/2022/guides/devtool/installation/
Solar2D
Visual Studio Code
Open Adobe UXP Developer Tool
Add Plugin
Add Plugin to select develop/UXP/kwik-exporter/dist
TODO alpla release folder structure may be different to the current develop
Load com.kiwksher.kwik5.dev-ps
The UXP panel apppears in Photoshop
you can select the following folder that comes with demo .psd files and kwikconfig.json. You may open a folder of yours which contains psd files.
sample-projects/SingleBook/book01
{
"name":"book one",
"output":"../../Solar2D/src/App/book",
"pages":[
{"psd":"page01.psd", "name":"page one"},
{"psd":"page02.psd", "name":"page two"}
]
}
TODO Project Name Text Box to display a selected folder name as default.
TODO User can change it and Kwik uses it for a folder name when publshing
TODO kwikconfig.json will be created in the selected folder.
TODO igonred
```
"pages":[
{"psd":"page01.psd", "name":"page one"},
{"psd":"page02.psd", "name":"page two"}
],
"ignored":[
{"psd":"page01_copy.psd", "name":"page 01 backup"},
]
```
Double Click the one of .psd in the list. The psd file is opened for editing.
You may create a new project or select one that has been made.
New
it scafolds the files and folders of a Solar2D template-project of kwik/base-proj/Solar2D into the folder you has selected in the dialog
{TARGET FOLDER}/App/book is the folder which code and assets like images are stored
It is free to rename {TARGET FOLDER}/App/book as you. Please follow the file naming rule
you may create a second book folder manually by using Finder(Mac) or File explore(Win)
TODO current implementation copies Solar2D folder w/o App
TODO it would be better to scafold the entire structure of SingleBook sample
Option box portrait or landscape
when Select Book, then read build.config to mark portrait or landscape readonly
Select Book
default: App/book
select a book folder where images and lua files are generated
TODO select portrait or landscape
This is a sample folder structure
├── Photoshop
│ └── book
│ ├── kwikconfig.json
│ └── page01.psd
└── Solar2D
├── App
│ └── book
│ ├──assets
│ ├──commands
│ ├──components
│ ├──models
│ └── index.lua
├── Images.xcassets
├── LaunchScreen.storyboardc
├── assets
├── build.settings
├── commands
├── components
├── config.lua
├── controller
├── en.lproj
├── extlib
├── jp.lproj
├── main.lua
└── mySplashScreen.png
Kwik4 could not choose a folder for publishing, it was fixed to use “build4” folder, Kwik5 can select any photoshop files on your PC, and can publish to a App/book folder.
Select Book
please select an output folder where each psf files are published. It must be under App folder.
selected psd files are publihsed to App/book folder
checkbox all
it will select all the psd files in the list
Input text box
you can input the index number of psd files to be published for example,
0, 2-3
Publish button
Export Settings Dialog appears, click Export
TODO show the project name with the default output folder
Browse button
Please make sure the target book folder for output. If you like to change the destination folder.
The last output folder could be stored in kwikconfig.json
{
"name":"book free",
"output":"../../../Solar2D/App/bookFree",
"pages":[
{"psd":"page01.psd", "name":"page01"},
{"psd":"page02.psd", "name":"page02"}
],
}
Kwik will publish images/source code to the output folder. You can find them, images in assets folder and lua files in
TODO change dist to src with SimpleBook sample
You can publish images/codes from active document only.
TODO Kwik4_1280x1920 to be renamed as page1
You can export images of each member of a layer group.
The default behavior is to publish one mergerd image of a layer group. You need to select a layer group to be unmereged
Select layer groups and then click Unmerge button.
If you want to cancel a layer group to be unmerged, Check it and click Cancel button. It will disapper from the list.
The source files and the images of each layer in a layer group are exported when published.
TODO fix the bug that unmerged group is not indexed at the right position in index.lua
local scene = require('controller.scene').new(sceneName, {
name = "kwik4_1280x1920",
layers = {
{ bg={} },
{ copyright={} },
{ star={} },
{ hello={} },
{ mycircle={} },
{ myrect={},
{ GroupA={
{ SubA = {} },} },
},
you have published .psd files in step 1. The images are the code for Solar2D are available in the output folder. Here you will load it to Solar2D simulator. You may build an app for iOS, Android, Desktop(mac or win).
Navigation
Open main.lua in the output folder - Solar2D project
Simulator > Window > View As
There is an option to open the simualtor in Publish command in Kwik UXP plugin when publish is completed.
TODO UXP::Publish to include swipe page or A/D keys for navigation ⭐️
kwiconfig.lua
debug = {enable = true, navigation= "swipe/keys"}
Auto open after publish in Kwik UXP plugin
from publishing pages
click page name to change the page view
from publishing the active document
Use up/down or right/left keys to go previous/next page
how to change page transition effect by editing a lua code in vs code
disiable the editor or only enable the editor simulator
if ( system.getInfo( "environment" ) == "simulator" ) then
print( "You're in the Solar2D Simulator." )
end
main.lua
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
local lldebugger = loadfile(os.getenv("LOCAL_LUA_DEBUGGER_FILEPATH"))()
lldebugger.start()
end
inspect = require("extlib.inspect")
local common = {
commands = {"myEvent"},
components = {
-- "align",
"myComponent",
"thumbnailNavigation",
"index" -- this loads editor!
}
}
Solar2D/components/common/index.lua
local editor = require("editor.index")
--
function _M:init(UI)
editor:init(UI)
end
--
function _M:create(UI)
editor:create(UI)
end
--
function _M:didShow(UI)
editor:didShow(UI)
end
--
function _M:didHide(UI)
editor:didHide(UI)
end
--
function _M:destroy(UI)
editor:destroy(UI)
end
--
return _M
Mac Corona simulator _TIPropertyValueIsValid warning
2023-12-15 14:39:26.982 Corona Simulator[48574:2941064] _TIPropertyValueIsValid called with 4 on nil context!
2023-12-15 14:39:26.982 Corona Simulator[48574:2941064] imkxpc_getApplicationProperty:reply: called with incorrect property value 4, bailing.
2023-12-15 14:39:26.982 Corona Simulator[48574:2941064] Text input context does not respond to _valueForTIProperty:
From Mac’s preference, add English input for keyboard, and Use it
update the test-proj/Solar2D/component/editor to develop/Solar2D/tools/kwik-editor
This tool visually edits Soar2D/src files of a Kwik project and is harnessed by Pegasus http-server
↑ Select a layer or an event to review. You can edit values of properties
TODO each kwik component with default values
TODO ui components like checkbox/selectors … for each component ⭐️
react compnents on webview – can be shared with UXP panel which may send params via http to pegasus in kwik editor
current editPorpsTable.lua servers as plain table viewer & editor
TODO save/copy params to .http (YAML) for httpYac
TODO how to send text to clipboard from Solar2D ⭐️
develop/Solar2D/tools/pegasus-launcher
You can open a solar2D project from VS Code with httpYac
Solar2D/server/tests
You can post params with httpYac in VS Code to a Solar2D project
TODO set Layer varaible with samples
TODO create models: animation, transition2, button …, and pegasus-receiver in kwik-editor ⭐️
Animation_bounce.http
POST /layers/logo
Content-Type: application/yaml
- transition: bounce
- params:
height: 400
width: 200
time: 1000
iterations: 0
you can put your own code(.lua) into commands/pageX/ and componets/pageX/layers folder.
Kwik Exporter traverses folders of Solar2D project to integrate your additons. Or you can manually add the file names to components/pageX/index.lua
for instance, myrect.lua calls myEvents.testHandler when user taps the rect.
The $weight in a comment line at the top is a variable for Kwik. A scene componet(layer or your custom code)with lower value will be placed upper. The top layer from Photoshop is zero. Then values are increases to until the background layer. For your custom code , you can use minus or positive with decimal. For example, myrect is -2, mycircle is -1. If you change weight values of custom code files, don’t forget to publish code again.
-- $weight=-2
--
local M = {}
--
function M:init(UI)
end
--
function M:create(UI)
local sceneGroup = UI.scene.view
local obj = display.newRect( sceneGroup, display.contentCenterX, display.contentCenterY-100, 100, 100 )
obj:setFillColor(0.2,0.2,0.2);
obj:addEventListener("tap", function()
UI.scene:dispatchEvent({
name = "myEvents.testHandler",
UI = UI
})
end)
end
--
function M:didShow(UI)
end
--
function M:didHide(UI)
end
--
function M:destory()
end
--
return M
myEvents.testHandker.lua
local instance = require("commands.kwik.baseCommand").new(
function (params)
local UI = params.UI
print("commands.myEvents.testhander")
UI.scene:dispatchEvent({
name = "myAction",
UI = UI
})
end
)
--
return instance
You can find your custom code are inserted in components/pageX/index.lua. The layers are sorted internally by values of $weight variable.
local sceneName = ...
--
local model = {
name = "page01",
components = {
layers = {
{bg = {}},
{mycircle={}},
{myrect={}}
},
audios = {},
groups = {},
others = {},
timers {},
variables = {}
},
commands = {
"myAction",
"myEvnets.testHandler"
},
onInit = function(scene) print("onInit") end
}
--
local scene = require('components.kwik.scene').new(sceneName, model)
return scene
create .lua for commands or components
run the follwoing tool to update scenes/pageX/index.lua to append the new .lua to the index.lua
/develop/Solar2D/tools/generate_scene_index is a Solar2D application. You can open the main.lua in Solar2D simulator.
the table in the index.lua is updated by iterating files in editor.template/components and template/commands
Representational State Transfer (REST) model for a programming framework
server/tests
bookTest01_editor.http
@host=http://localhost:9090
### run pegasus server if not running, and return books
GET /app
###
GET /bookTest01
###
GET /bookTest01/page1
### selectLayer
GET /bookTest01/page1/imageOne
###
GET /bookTest01/page1/imageOne/?class=linear
### modify layer props
POST /bookTest01/page1/imageOne/
Content-Type: application/yaml
{
alpha=0.5
}
### save layer props with the current value
PUT /bookTest01/page1/imageOne/
Content-Type: application/yaml
you can create a lua file and index.lua will be updated too
bookTest01_editor_put.http
PUT /bookTest01
PUT /bookTest01/page1
### update index.lua too
PUT /bookTest01/page1/imageOne
PUT /bookTest01/page1/imageOne/?class=linear
### events
GET /bookTest01/commands/page1
PUT /bookTest01/commands/page1/eventOne
// create page2 and copy *.lua from bookTest/page2 by the copy script
PUT /bookTest01/page2?copyFrom=bookTest/page2
components/editor/index.lua
local unitTestOn = true
components/editor/tests/index.lua
require "extlib.lunatest"
local M = {
run = function (props)
print("============ lunatest =============")
lunatest.suite("components.editor.tests.suite_controller")
lunatest.suite("components.editor.tests.suite_selector", props)
lunatest.run()
print("============ end =============")
end
}
return M
it tests editor/controller/index.lua
controller:render(book, page, layer, tool, class, props)
controller:save(book, page, layer,tool, nil, props ) assert_string(dst, “fail”)
…
module(..., package.seeall)
function suite_setup()
controller = require "components.editor.controller.index"
files = {}
updatedScene = nil
end
function setup()
book = "bookFree"
page = "page1"
tool = "interaction"
layer = "butWhite" -- Update scenes.components.layers.butWhite with the props
class = "button"
props = {name="helloBTN", class="button", kind="tap", actionName = "onClick", over="helloOver", btaps = 1, mask=""}
scene = {
name = "canvas",
components = {
layers = {
{ back={
} },
{ butBlue={ class={"button"}, {A={}}, {B={}}
} },
{ butWhite={
} },
},
audios = { },
groups = { },
timers = { },
variables = { },
others = { }
},
commands = { "blueBTN" },
onInit = function(scene) print("onInit") end
}
end
function teardown()
end
function test_render()
local dst = controller:render(book, page, layer, tool, class, props)
assert_string(dst, "fail")
--
local path = system.pathForFile(dst, system.TemporaryDirectory )
local file = io.open( path, "r" )
assert_userdata(file, "fail")
files[#files + 1] = dst
end
automatically it loads eventOne.json in page1
local M = {}
local selectors
local UI
local bookTable
local pageTable
function M.init(props)
selectors = props.selectors
UI = props.UI
bookTable = props.bookTable
pageTable = props.pageTable
end
function M.suite_setup()
selectors.projectPageSelector:show()
selectors.projectPageSelector:onClick(true)
--
UI.scene.app:dispatchEvent {
name = "editor.selector.selectApp",
UI = UI
}
-- appFolder = system.pathForFile("App", system.ResourceDirectory) -- default
-- useTinyfiledialogs = false -- default
---
bookTable:commandHandler({book="bookFree"}, nil, true)
pageTable:commandHandler({page="page1"},nil, true)
end
function M.setup()
end
function M.teardown()
end
-- function M.test_component()
-- selectors.projectPageSelector:show()
-- end
function M.test_action()
selectors.componentSelector:show()
selectors.componentSelector:onClick(true, "actionTable")
UI.editor.currentAction = "eventOne"
UI.scene.app:dispatchEvent {
name = "editor.action.selectAction",
UI = UI
}
end
return M
https://code.visualstudio.com/docs/setup/mac
URLs
https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls
vscode://file/{full path to file}
### extensions
the following extensions helps your coding with Solar2D simulator.
- Solar2D Autocomplete
- Solar2d Companion for Visual Studio Code
- Local Lua Debugger
- Run Terminal Command
## Local Lua Debugger
For debugging with Local Lua Deubgger, launch.json should be placed in your workspace folder. For instance,
Assuming Kwik projects are located in ~/Kwik folder and this is your workplace folder of Visual Studio Code. launch.json will be created under .vscode/ folder
- ~/Kwik/.vscode/launch.json
> Corona Simulator.app is specified and ${file} is in the last argurment
this is for Mac
```json
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "lua-local",
"request": "launch",
"program": {
"command": "/Applications/Corona/Corona Simulator.app/Contents/MacOS/Corona Simulator",
},
"args": [
"-no-console"
"YES"
"-debug"
"1"
"-project"
"${file}"
]
}
]
}
for Windwos
{
"version": "0.2.0",
"configurations": [{
"name": "Debug",
"type": "lua-local",
"request": "launch",
"program": {
"command": "C:\\Program Files (x86)\\Corona Labs\\Corona\\Corona Simulator.exe",
},
"args": [
"/no-console",
"/debug",
"${file}"
]
}]
}
SingleBook project contains Photsohop/book/*.psd and lua files under Solar2D/src folder
open main.lua in Visual Studio Code and press F5 (Run > Start Debugging)
main.lua needs lldebugger.start()
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
local lldebugger = loadfile(os.getenv("LOCAL_LUA_DEBUGGER_FILEPATH"))()
lldebugger.start()
end
this is a utility to run terminal commands. You can select a folder and right click opens a dialog to select a terminal command
You can add a terminal command to settings.json
for Mac
{
"command":"\"/Applications/Corona/Corona Simulator.app/Contents/MacOS/Corona Simulator\" ./ -no-console YES",
"name":"Solar2D"
},
TODO
editor/controller/save.lua
editor/parts/controller/properties/save.lua
the following functions in editor.controller.index class are mainly used for CRUD of json/lua files in select, save, delete etc. See editor.controller.save.
separate controllers for the audio, group, timer and variable components are implemented i.e
See editor.parts.controller directory. selectXXX lua reads json and displays a UI table. The data is passed by nanostore such as bookStore:set, pageStore:set.
nanostore is used as an experimental usage
selectApp.lua
local appFolder = params.appFolder or system.pathForFile( "App", system.ResourceDirectory )
if params.useTinyfiledialogs then
appFolder = tfd.selectFolderDialog({
title = "select App folder",
default_path = path
})
-- print("", appFolder)
end
if success then
local books = {}
for file in lfs.dir( appFolder ) do
if util.isDir(file) then
-- print("", "Found file: " .. file )
-- set them to nanostores
if file:len() > 3 and file ~="kwikEditor" then
table.insert(books, {name = file, path= util.PATH(appFolder.."/"..file)})
end
end
end
if #books > 0 then
UI.editor.bookStore:set(books)
end
selectBook.lua
local path =system.pathForFile( "App/"..bookName.."/models", system.ResourceDirectory)
UI.editor.currentBook = bookName
local success = lfs.chdir( path ) -- isDir works with current dir
if success then
local pages = {}
for file in lfs.dir( path ) do
if util.isDir(file) then
-- set them to nanostores
if file:len() > 3 and file ~='assets' then
table.insert(pages, {name = file, path= util.PATH(path.."/"..file)})
end
end
end
if #pages > 0 then
UI.editor.pageStore:set(pages)
end
end
selectPage.lua
if params.page:len() > 0 and UI.page ~= params.page then
local app = App.get()
app:showView("components." .. params.page .. ".index", {effect = "slideDown"})
end
selectLayer.lua
local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/"..params.path..getFileName(layerName, className)..".json", system.ResourceDirectory)
...
propsTable:setValue(decoded)
...
instead of nanostore, a traditional set/get functions are used here
selectTool.lua
this loads class’s properties of a layer
if params.layer then -- this measn user clicks one of class, anim, button, drag ...
UI.editor.currentLayer = params.layer
end
tool.controller:read(UI.editor.currentBook, UI.page, UI.editor.currentLayer, params.isNew, params.class)
if params.isNew then it loads default values to propsTable
edtior.controller.index
function M:read(book, page, layer, isNew, class)
print("read", page, layer, isNew, class)
-- the values are used in useClassEdtiorProps()
self.page = page
self.layer = layer
self.isNew = isNew
self.class = class
if isNew then
local path = "editor.template.components.pageX."..self.layerTool..".defaults."..class
local template = require(path)
self:reset()
self:setValue(template, nil, true)
self:redraw()
elseif layer then
-- this comes from clicking layerTable.class
local layerName = layer or "index"
--local path = page .."/"..layerName.."_"..self.layerTool..".json"
local path = system.pathForFile( "App/"..book.."/models/"..page .."/"..layerName.."_"..self.layerTool..".json", system.ResourceDirectory)
if self.lastSelection ~= path then
self.lastSelection = path
local decoded, pos, msg = json.decodeFile( path )
if not decoded then
print( "Decode failed at "..tostring(pos)..": "..tostring(msg) )
else
print( "File successfully decoded!" )
end
self:reset()
self:setValue(decoded, 1)
self:redraw()
else
self.view.isNew = true
toolbar:toogleToolMap()
end
end
end
For layer’s class, the json is retrived by tool.controller:read above and the read function calls setValue(decoded) inside to display the data to controlProps table.
editor.controller.index
function M:setValue(decoded, index, template)
if decoded == nil then return end
if not template then
print(json.encode(decoded[index]))
self.selectbox:setValue(decoded, index) -- "linear 1", "rotation 1" ...
self.controlProps:setValue(decoded[index].controls)
self.onCompletebox:setValue(decoded[index].actionName)
else
self.selectbox:setTemplate(decoded) -- "linear 1", "rotation 1" ...
self.controlProps:setValue(decoded.controls)
self.onCompletebox:setValue(decoded.actionName)
end
end
generic setValue is implemented in edtior.controller.index, and components can have own setValue for their UI table. for instance,
editor.controller.index
the command() reads json with util.decode() from params.
function M:command()
local instance = require("commands.kwik.baseCommand").new(
function (params)
local UI = params.UI
local name = params[params.class] or ""
local decoded = util.decode(params) -- this reads models/xx.json
--
print("From selectors")
self.controlProps:didHide(UI)
self.controlProps:destroy(UI)
self.controlProps:init(UI)
self.controlProps:setValue(decoded)
self.controlProps.isNew = params.isNew
--
self.controlProps:create(UI)
self.controlProps:didShow(UI)
--
-- self:show()
self.controlProps:show()
self.onCompletebox:show()
self.buttons:show()
...
function M.decode(params)
local UI = params.UI
if params.isNew then
local path = "editor.template.components.pageX."..params.class..".defaults."..params.class
return require(path)
elseif params.isDelete then
print(params.class, "delete")
return {}
else
local name = params[params.class] or ""
if params.subclass then
name = params.subclass.."/"..name
end
local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/"..params.class.."s/"..name..".json", system.ResourceDirectory)
decoded, pos, msg = json.decodeFile( path )
if not decoded then
print( "Decode failed at "..tostring(pos)..": "..tostring(msg), path )
decoded = {}
end
return decoded or {}
end
end
selectAudio.lua
require(“editor.audio.index”).controller:command()
selectTimer.lua
require(“editor.timer.index”).controller:command()
selectVariable.lua
require(“editor.variable.index”).controller:command()
selectGroup.lua
require(“editor.group.controller.selectGroup”)
selectGroup returns a command() for Group
local command = function (params)
local UI = params.UI
local name = params.group or ""
print (params.class)
print("selectGroup", name, path, params.show)
--print(debug.traceback())
local tableData
if params.isNew then
local boxData = util.read( UI.editor.currentBook, UI.page)
--
tableData = {
name = "(new-group)",
layers = {},
children = {},
alpha = nil,
xScale = nil,
yScale = nil,
rotation = nil,
isLuaTable = nll
}
UI.editor.groupLayersStore:set(tableData) -- layersTable
UI.editor.layerJsonStore:set(boxData.layers) -- layersbox
elseif params.isDelete then
elseif name:len() > 0 then
--
-- layersTable
--
local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/groups/"..name..".json", system.ResourceDirectory)
tableData, pos, msg = json.decodeFile( path )
if not tableData then
print( "Decode failed at "..tostring(pos)..": "..tostring(msg), path )
tableData = {}
end
--
-- layersbox
--
local boxData = util.read( UI.editor.currentBook, UI.page, function(parent, name)
-- let's remove entries of tableData from boxData
-- layers = ["GroupA.Ellipse", "GroupA.SubA.Triangle"]
for i=1, #tableData.layers do
local _name = tableData.layers[i]
if parent then
if parent .."."..name == _name then
return true
end
elseif name == _name then
return true
end
end
return false
end)
UI.editor.layerJsonStore:set(boxData.layers) -- layersbox
UI.editor.groupLayersStore:set(tableData) -- layersTable
end
--
editor:show()
selectPageProps.lua
local command = function (params)
...
local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/settings.json", system.ResourceDirectory)
...
settingsTable:setValue(decoded)
...
create a new component from toolbar
UI.scene.model is set when selectPage is called
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={
} },
{ gotoBtn={ --class={"animation"}
} },
{ title={ class={"linear"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})
util.read() function parses “App/”..book.."/models/"..page .."/index.json"
...
ret.layers = parser(decoded)
...
setFiles(ret.audios, "/audios/short")
setFiles(ret.audios, "/audios/long")
setFiles(ret.groups, "/groups")
setFiles(ret.commands, "/commands")
return ret
{
layers = {
{name = "layerOne", parent="", children = {
{name="childOne}, parent="layerOne", children = {}}
}
},
{name = "layerTwo", parent="", children = {}},
},
audios = {}
}
CRUD operations on json/lua files are mainly for commands and components in a selected page
the editor does not support to create a new entry of App, Book, Page. The properties can be modified for App, Book, Page.
layer names must be unique in a scene model.
```lua
util.createIndexModel(UI.scene.model, UI.editor.currentLayer, classname)
```
```lua
scene = {
name = "canvas",
components = {
layers = {
{ back={
} },
{ butBlue={ class={"button"}, {A={}}, {B={}}
} },
{ butWhite={
} },
},
audios = { },
groups = { },
timers = { },
variables = { },
others = { }
},
}
```
- render App.booxX.components.pageX.index.lua
- save json
local name = ...
local parent, root = parent_root(name)
local util = require("editor.util")
local json = require("json")
--
-- save command performs on one entry.
-- If user switch to another entry without saving, the previous change will be lost.
--
local instance =
require("commands.kwik.baseCommand").new(
function(params)
local UI = params.UI
local props = params.props
local tool = UI.editor:getTool(props.class) -- each tool.contoller can overide render/save. So page tools of audio, group, timer should use own render/save
if tool then
local files = {}
local toolName = UI.editor:getToolName(props.class)
local filename = props.name
local classname = props.class:lower()
-------------
-- save lua
files[#files+1] = tool.controller:render(UI.editor.currentBook, UI.page, UI.editor.currentLayer, toolName, classname, props)
-----------
--- save json
local decoded = params.decoded or {}
decoded[props.index] = props
--
files[#files+1] = tool.controller:save(UI.editor.currentBook, UI.page, UI.editor.currentLayer,toolName, decoded)
-----------
--- Update components/pageX/index.lua model/pageX/index.json
local updatedModel = util.createIndexModel(UI.scene.model, UI.editor.currentLayer, classname)
files[#files+1] = tool.controller:renderIndex(UI.editor.currentBook, UI.page, updatedModel)
files[#files+1] = tool.controller:saveIndex(UI.editor.currentBook, UI.page, UI.editor.currentLayer,classname, updatedModel)
----------
-- publish
util.executePubish(files)
else
print("tool not found for", props.class)
end
end
)
--
return instance
media files of audio, layer replacements(video, spritesheet, particle, syncText, web) in App/bookX/assets folder are indexed with linked layers in assets.json
So tool.controller:save() also performs a write operation on assets.json
editor/index.lua and baseTable.lua handles user’ selection from xxxTable commands/selectors/selectXXX.lua reads a json
selectors.lua handles to read values of UI.scene.model.components for xxxTable.
editor/models.lua
this defines layer related components. it does not include audio, group, timer, variables and action.
local models = {
{
name = "Animations",
icon = "toolAnim",
tools = {
{name = "Linear", icon = "animLinear"},
{name = "Blink", icon = "animBlink"},
{name = "Bounce", icon = "animBounce"},
{name = "Pulse", icon = "animPulse"},
{name = "Rotation", icon = "animRotation"},
{name = "Shake", icon = "animShake"},
{name = "Switch", icon = "animSwitch"},
{name = "Filter", icon = "animFilter"},
{name = "Path", icon = "animPath"},
},
id = "animation"
},
...
editor/index.lua loads it. The id is used to load a module for editing a component like
animation = require("editor.animation.index")
For audio, group, timer and variables, the icons and the modules are loaded with their table.lua for instance, groupTable.lua
local name = ...
local parent = name:match("(.-)[^%.]+$")
local Props = {
name = "group",
anchorName = "selectGroup",
icons = {"Groups", "Trash"},
id = "group"
}
local M = require(parent.."baseTable").new(Props)
return M
action’ icon/module ared loaded with action/index.lua. It is called by editor/index.lua
local actionIcon = muiIcon:create {
icon = {"actions_over", "actions_over","actions_over"},
text = "",
name = "action-icon",
x = display.contentCenterX/2 + 42,
y = -2,
-- y = (display.actualContentHeight - display.contentHeight)/2 -2,
width = 22,
height = 22,
fontSize =16,
listener = function()
self.isVisible = not self.isVisible
if self.isVisible then
self:show()
else
self:hide()
end
end,
fillColor = {1.0}
}
UI.editor.actionIcon = actionIcon
editor/index.lua
Add {name = “selectXXX”, btree= “load xxx”}
M.commands = {{name="selectApp", btree=nil},
{name="selectBook", btree="load book"},
{name="selectPage", btree="load page"},
{name="selectLayer", btree="load layer"},
-- {name="selectAction", btree=""},
{name="selectTool", btree="editor component"},
-- {name="selectActionCommand", btree=""}
{name="selectAudio", btree="load audio"},
{name="selectGroup", btree="load group"},
-- {name="selectTimer", btree="load timer"},
-- {name="selectVariable", btree="load variable"},
-- {name="selectVideo", btree="load video"},
}
new xxxTable out of baseTable.lua
write like this
local name = ...
local parent = name:match("(.-)[^%.]+$")
local Props = {
name = "group",
anchorName = "selectGroup",
icons = {"Groups", "Trash"},
id = "group"
}
local M = require(parent.."baseTable").new(Props)
return M
this baseTable fires “load xxx” with a selected entry in xxxTable when user clicks it
baseTable.lua
tree.backboard = {
show = true,
class = target.class
}
tree.backboard[self.name] = target[self.name],
tree:setConditionStatus("select component", bt.SUCCESS, true)
tree:setActionStatus("load "..self.name, bt.RUNNING, true)
tree:setConditionStatus("select "..self.name, bt.SUCCESS)
then next, this tree action (load xxx) is called back. It is linked to selectXXX command by editor/index.lua. commands/selectors/selectXXX to load a json of a selected entry
selectors.lua
this is a view model. You see each command to be displayed as a button. It is a anchorName to xxxTable
Add set store name “xxxTable” and btree = “select component”
M:create(UI)
self.componentSelector =
selectorBase.new(
UI,
33,
-2,
{
{label = "Layer", command = "selectLayer", store = "layerTable", filter = true, btree = "select layer"},
{label = "Audio", command = "selectAudio", store = "audioTable", btree = "select component"},
{label = "Group", command = "selectGroup", store = "groupTable", btree = "select component"},
{label = "Timer", command = "selectTimer"},
{label = "Var", command = "selectVariable"},
{label = "Action", command = "selectAction", store = "actionTable"}
},
"toolLayer",
selectLayerFilter,
propsTable,
propsButtons
)
M:didShow()
Add xxxStore:set
UI.editor.layerStore:set({})
UI.editor.audioStore:set({})
UI.editor.actionStore:set({})
UI.editor.groupStore:set({})
UI.editor.timerStore:set({})
UI.editor.variableStore:set({})
--
if storeTable then
-- should we show the last secection?
if storeTable == "layerTable" then
UI.editor.layerStore:set(UI.scene.model.components.layers)
elseif storeTable == "audioTable" then
print(storeTable)
UI.editor.audioStore:set(UI.scene.model.components.audios)
elseif storeTable == "groupTable" then
print(storeTable)
UI.editor.groupStore:set(UI.scene.model.components.groups)
elseif storeTable == "actionTable" then
UI.editor.actionStore:set(UI.scene.model.commands)
end
...
commands/selector/selectXXX
write code to read json and set it to propsTable
local command = function (params)
local UI = params.UI
local xxx = params.xxx or ""
local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/audios/"..params.class.."/"..xxx..".json", system.ResourceDirectory)
local decoded, pos, msg = json.decodeFile( path )
if not decoded then
print( "Decode failed at "..tostring(pos)..": "..tostring(msg), path )
else
print( "props is decoded!" )
UI.editor.propsStore:set(decoded)
propsButtons:show()
end
for controllers for components are registered either in index.lua or selectors.lua or buttons.lua
for instance, action/selectors.lua
M.commands = {"selectAction", "selectActionCommand"}
---
function M:init(UI, toggleHandler)
local app = App.get()
for i = 1, #self.commands do
app.context:mapCommand(
"editor.action." .. self.commands[i],
"editor.action.controller." .. self.commands[i]
)
end
self.togglePanel = toggleHandler
end
for animation/buttons.lua
M.commands = {"create", "delete", "save", "cancel", "copy", "paste"}
---
function M:init(UI, toggleHandler)
local app = App.get()
for i = 1, #self.commands do
app.context:mapCommand(
"editor.anim." .. self.commands[i],
"editor.animation.controller." .. self.commands[i]
)
end
self.togglePanel = toggleHandler
end
editor.animation.controller
each contoller of components is based on editor.controller.index
local M = require("editor.controller.index").new("animation")
...
...
return M
As of the base class, editor.controller.index has render(), save(), renderIndex(), saveIndex(), read() … and component’controllers can override them for their own.
for a component which uses a media file in assets, don’t forget to update assets.json too
editor.model
assetTool = {
name = "Assets",
id = "asset"
}
editor.parts.selectors
function M:create(UI)
...
...
self.assetsSelector =
selectorBase.new(
UI,
56,
-2,
{
{label = "Audio", command = "selectAudioAsset", store = "audios", btree = "select asset"},
{label = "Particles", command = "selectPaticlesAsset", store = "paticles", btree = "select asset"},
{label = "Spritesheet", command = "selectSpriteAsset", store = "sprites", btree = "select asset"},
{label = "SyncText", command = "selectSyncTextAsset", store = "aduios.sync", btree = "select asset"},
{label = "Video", command = "selectVideoAsset", store = "videos", btree = "select asset"},
},
"toolAssets",
nil
)
...
...
end
function self.assetsSelector:onClick(isVisible, assetName)
...
...
UI.editor.assetStore:set({class = assetName, decoded = UI.editor.assets[assetName]})
...
...
end
select asset in controller.BTree (Obsolete)
this behavior’s nodes are not used now. It could be implemented “select asset” condition true in self.assetsSelector:onClick(isVisible, assetName) to invoke “load asset” node
| | ->
| | | (select asset)
| | | [load asset]
| | | ?
| | | | ->
| | | | | (select asset props)
| | | | | [editor asset props]
| | | | ->
| | | | | !(press esc)
| | | | | ?
| | | | | | ->
| | | | | | | (add asset)
| | | | | | | [editor asset]
| | | | | | ->
| | | | | | | (modify asset)
| | | | | | | [editor asset]
| | | | | | ->
| | | | | | | (delete asset)
| | | | | | | [editor asset]
| | | | ->
| | | | | (press esc)
| | | | | [editor asset]
asset.assetTable
local handlerMap = {
audios = {class = "audio",
modify = require("editor.audio.audioTable").commandHandler,
icons = {"addAudio", "trash"},
tool =" selectAudio"},
videos = {class = "video",
modify = require("editor.parts.layerTableCommands").commandHandlerClass,
icons = {"repVideo", "trash"},
tool ="selectTool"},
}
editor.assets.index
assetTool consists of assetTable, conrolPros, buttons and controller.
editor.index.holds the editor asset instance
local mod = require("editor.assets.index.lua")
self.editorTools["asset"] = mod
self.views[#self.views + 1] = mod
the asset icon on editor menu is created by edior.parts.selectors. see model paragraph above
editor.asset.assetTable
editor.parts.selectors.assetsSelector:onClick() sets assetStore with value
then assetStore:listener sets commandHandler dynamically. User selects a new component (ex. video icon) and then clicks to fire an event which triggers handlerMap’s modify function. (ex editor.parts.layerTableCommands").commandHandlerClass)
UI.editor.assetStore:listen(
function(foo, fooValue)
...
...
self.commandHandler = getHandler(fooValue.class)
...
...
end
)
getHandler returns one of handlerMap’s modify function. It is one of commandHandler/commandHandlerClass of layerTableCommands (components)
editor.parts.layerTableCommands
it calls tree:setConditionStatus with backboard for a component tool
function M.commandHandlerClass(self, eventObj, event)
...
...
-- target.isSelected = true
UI.classInstance = target.name
-- for load layer
tree.backboard = {
layer = target.layer,
class = target.class,
path = path
}
tree:setConditionStatus("select layer", bt.SUCCESS, true)
tree:setActionStatus("load layer", bt.RUNNING) -- need tick to process load layer with tree.backboard
tree:setConditionStatus("select props", bt.FAILED, true)
-- For editor compoent. this fires selectTool event with backboard params
tree.backboard = {
class = target.class,
isNew = false,
layer = target.layer,
path = path
}
tree:setConditionStatus("modify component", bt.SUCCESS)
tree:setActionStatus("editor component", bt.RUNNING, true)
...
...
end
assetTable lists media files and use can select one and then click then the icon on the left top corner for creating a component.
When clicking the icon, editor pass the media file proprs to a layer component tool
A media file is always linked with one of components. So assets.json is edited by the controller of a component that handles one of media type
asset.controller.save is called by a component’s controller
editor.audio.audioTable receives the string array of audio files via nanostore
editor/parts/controller/selector/selectAudio receives the onClick event when user selects an audio entry in audioTable
local instance = require("editor.audio.index").controller:command()
return instance
the controller:command() from editor.controller.index decodes the json file of a selected audio and setValue to the UI table which has been initiated in editor.audio.index
conrolProps onCompletebox buttons are loaded in editor.audio.index and they are initiated with the controller
editor.audio.index
local selectbox = require(parent .. "audioTable")
local controlProps = require(parent.."controlProps")
local onCompletebox = require(root..".parts.onCompletebox")
-- this set editor.audio.save, cacnel
local buttons = require(parent.."buttons")
--
local controller = require("editor.controller.index").new("audio")
--
local M = require(root.."baseClassEditor").new(model, controller)
function M:init(UI)
self.UI = UI
self.group = display.newGroup()
UI.editor.classEditorGroup = self.group
--
selectbox:init(UI)
controlProps:init(UI, self.x + self.width*1.5, self.y, self.width, self.height)
controlProps.model = model.props
controlProps.UI = UI
--
onCompletebox:init(UI)
buttons:init(UI)
-- --
controller:init{
selectbox = selectbox,
controlProps = controlProps,
onCompletebox = onCompletebox,
buttons = buttons
}
controller.view = self
--
UI.useClassEditorProps = function() return controller:useClassEditorProps() end
--
end
editor.controller.index
this is the base class of the controller of editor.audio. and this command() is called in editor/parts/controller/selector/selectAudio mentioned above.
function M:command()
local instance = require("commands.kwik.baseCommand").new(
function (params)
local UI = params.UI
local name = params[params.class] or ""
local decoded = util.decode(params) -- this reads models/xx.json
--
print("From selectors")
self.controlProps:didHide(UI)
self.controlProps:destroy(UI)
self.controlProps:init(UI)
self.controlProps:setValue(decoded)
self.controlProps.isNew = params.isNew
--
self.controlProps:create(UI)
self.controlProps:didShow(UI)
--
-- self:show()
self.controlProps:show()
self.onCompletebox:show()
self.buttons:show()
--
UI.editor.editPropsLabel = name
--
UI.editor.sceneGroup:dispatchEvent{name="labelStore",
currentBook= UI.editor.currentBook,
currentPage= UI.page,
currentLayer = name}
end)
return instance
end
editor.audio.index
local selectbox = require(parent .. "audioTable")
local controlProps = require(parent.."controlProps")
local onCompletebox = require(root..".parts.onCompletebox")
local buttons = require(parent.."buttons")
overwrites contoroller’s render and save functions
function controller:render(book, page, type, name, model)
local dst = "App/"..book.."/"..page .."/components/audios/"..type.."/"..name ..".lua"
local tmplt = "editor/template/components/pageX/audios/audio.lua"
util.mkdir("App", book, page, "components", "audios", type)
util.saveLua(tmplt, dst, model)
return dst
end
function controller:save(book, page, type, name, model)
local dst = "App/"..book.."/models/"..page .."/audios/"..type.."/"..name..".json"
util.mkdir("App", book, "models", page, "audios", type)
util.saveJson(dst, model)
return dst
end
editor.audio.controller.save
local name = ...
local parent,root = parent_root(name)
local util = require("editor.util")
local json = require("json")
local instance = require("commands.kwik.baseCommand").new(
function (params)
local UI = params.UI
local selectbox = require(root.."audioTable")
local controller = require(root.."index").controller
local controlProps = controller.controlProps
local onCompletebox = controller.onCompletebox
local selected = selectbox.selection or {}
local page = params.page or UI.page,
local props = {
name = selected.audio, -- UI.editor.currentLayer,
class= "audio",
subclass = selected.subclass or "short",
controls = {},
actionName = nil,
-- the following vales come from read()
--class = self.class,
index = selected.index,
}
--
local controls = controlProps:getValue()
for i=1, #controls do
-- props.subclass
--
local name = controls[i].name
if name =="_type" then
local t = controls[i].value or "short"
if t:len() == 0 then
t = "short"
elseif t=="audioshort"then
t = "short"
elseif t=="audiolong" then
t= "long"
elseif t=="audiosync" then
t= "sync"
end
props.subclass = t
name = "type"
elseif name== "_file" then
name = "filename"
end
props.controls[name] = controls[i].value
end
--
-- props.name
--
if props.name == nil then -- NEW
print("#NEW",props.controls.name)
-- local t = util.split(props.controls.file or "", '.')
-- props.name = t[1]
props.name = props.controls.name
end
-- props.actionName
props.actionName = onCompletebox.selectedTextLabel
print("porps")
for k, v in pairs(props) do print("", k, v) end
--
local updatedModel = util.createIndexModel(UI.scene.model)
-- print(json.encode(updatedModel))
local files = {}
if params.isNew or selected.index == nil then
local dst = updatedModel.components.audios[props.subclass] or {}
dst[#dst + 1] = props.name
else
local dst = updatedModel.components.audios[props.subclass]
dst[props.index] = props.name -- TBI for audio's name
end
--
-- TODO check if name is not duplicated or not
--
-- index
files[#files+1] = util.renderIndex(UI.editor.currentBook, page,updatedModel)
files[#files+1] = util.saveIndex(UI.editor.currentBook, page, props.layer,props.class, updatedModel)
-- save lua
files[#files+1] = controller:render(UI.editor.currentBook, page, props.subclass, props.name, props.controls)
-- save json
files[#files+1] = controller:save(UI.editor.currentBook, page, props.subclass, props.name, props.controls)
-- publish
util.executePubish(files)
end
)
--
return instance
This is experimental. A Behavior tree implemetation enables a conditional call for an action node. If conditions are not satisfied for a button, the associated action to the button will not be executed when user clicks it
editor/controller/BTree/selectors.tree
BTree calls BThandler when actionNode is activated. The parameters are stored in backboard.
editor.index
M.commands = {
{name="selectApp", btree=nil},
{name="selectBook", btree="load book"},
{name="selectPage", btree="load page"},
{name="selectLayer", btree="load layer"},
{name="selectPageProps", btree=nil},
-- {name="selectAction", btree=""},
{name="selectTool", btree="editor component"},
-- {name="selectActionCommand", btree=""}
{name="selectAudio", btree="load audio"},
{name="selectGroup", btree="load group"},
{name="selectTimer", btree="load timer"},
{name="selectVariable", btree="load variable"},
-- {name="selectVideo", btree="load video"},
}
-- connects with BTree ----
local BTMap = {}
for i=1, #M.commands do
if M.commands[i].btree then
BTMap[M.commands[i].btree] ={eventName = "editor.selector."..M.commands[i].name, name = M.commands[i].name}
end
end
-- BTree calls this when activating actionNode
M.BThandler = function(name, status)
-- print("#BTHandler: dispathEvent")
-- print("", name, bt.getFriendlyStatus( nil,status ))
local target = BTMap[name]
-- print("", target)
if target then
-- print("", target.eventName)
--local obj = M.UI.editor.sceneGroup[target.name]
local params = {
name = target.eventName,
UI = M.UI, -- beaware UI is belonged to a page
-- show = not obj.isVisible,
}
if tree.backboard then
for k, v in pairs(tree.backboard) do
params[k] = v
end
end
M.UI.scene.app:dispatchEvent(params)
end
return bt.SUCCESS
end
TBI for saving a component with a media file in assets.
classDiagram
editorIndex
editorIndex *-- menu : 1. User clicks Layer
commands o-- selectBook
commands o-- selectPage
commands o-- selectLayer
commands o-- selectTool
class selectors{
- List: App, Book,Page
- List: Layer, Audio, Group, Timer ..
+ projectPageSelector
+ componentSelector
- componentHandler()
}
class selectorBase{
+ selectorIcon
+ entries
+ onClick()
}
selectors <|..selectorBase: 1.1 onClick calls componentHandler
menu *-- selectors
selectors ..> store :1.2 commandHandler sets Layer entries
class commands {
+ page
+ book
+ layer
* class
store.set()
}
class selectTool {
controller:command()
}
class editorAnimation {
+ layerTable as selectbox
+ controlbox
+ buttons
+ controller
}
class layerTable {
+ name
+ class entries: animation, interaction, replacement
create() displays name and class entries
oncllick() name for propsTable, animation for editorAnimation
}
class layerTableCommands{
+ commandHandler
+ commandHanderClass
}
class selectLayer {
propsTable:setValue(decoded)
propsTable:show()
propsButtons:show()
}
class propsTable {
can be editable
}
propsTable *-- buttons
selectLayer ..>propsTable
layerTable *--layerTableCommands
class controller {
selectbox.classEditorHandler()
-> reset()
-> setValue()
-> redraw()
command()
-> util.decode(params)
-> selectbox:setValue(decoded)
-> controlbox:didHide(UI)
-> controlbox:destroy(UI)
-> controlbox:init(UI)
-> controlbox:setValue(decoded)
-> controlbox:create(UI)
-> controlbox:didShow(UI)
-> controlbox:show()
-> onCompletebox:show()
-> buttons:show()
}
editorAnimation *-- layerTable
editorAnimation <|.. controller
layerTable <|.. baseTable : create() is overrided in layerTable
editorAnimation *-- conrolbox
conrolbox <|.. baseProps
class baseTable {
+ entries
commandHandler()
store.listener()
render()
}
class BTree{
setCondition()
setActionStatus()
}
layerTableCommands ..> BTree : 2. User clicks a animation entry <br> 2.1 BTree select animation TRUE
BTree ..> commands : 2.2. load animation (Activated) <br>2.3.1 selectTool for animation class
commands ..> selectTool: 2.5. select animation
selectTool ..> controller: 2.5 calls command() to display animationEditor
store ..> baseTable : 1.3. calls listenr in baseTable
class selectbox{
+ setTemplate()
+ commandHandler()
}
editorAnimation *-- selectbox : User clicks an animation entry
selectbox --> controller : commandHandler calls classEdtiorHandler
menu.controller.selector.selectGroup is linked to group.controller.selectGroup
local instance = require("components.editor.group.controller.selectGroup")
classDiagram
commands o-- selectLayer
commands o-- selectGroup
selectors ..> store
selectGroup --> _selectGroup
class _selectGroup{
<<Controller>>
decodede = util.read
layersbox store.set(decodede)
layersTable store.set(decoded)
return instance.new()
}
class add{
<<Controller>>
layersbox:set(decodede)
layersTable:set(decoded)
}
class remove{
<<Controller>>
layersbox:set(decodede)
layersTable:set(decoded)
}
class editorGroup {
+ groupTable as selectbox
+ controlbox
+ buttons
+ layersbox
+ groupTable
}
class groupTable {
+ groups
oncllick()
}
class layersbox {
+ layers
setValue() override
}
layersbox <|.. targetbox
layersbox <|.. targetboxMulti
targetboxMulti --> util
class targetboxMulti{
commandHandler() override
}
class util {
+ setSelection()
}
class layersTable {
+ layers
scrollView = widget.newDragItemsScrollView()
}
class dragitemscrollview{
<<ext>>
}
layersTable --> dragitemscrollview
layersTable --> util
groupTable ..> BTree
editorGroup *-- groupTable
editorGroup *.. layersbox
editorGroup *.. layersTable
editorGroup o.. add
editorGroup o.. remove
editorGroup o.. _selectGroup
groupTable <|.. baseTable
editorGroup *-- conrolbox
conrolbox <|.. baseProps
BTree ..> commands
commands ..> selectGroup
_selectGroup ..> layersbox
_selectGroup ..> layersTable
store ..> baseTable
class selectGroup{
require(group.controller.selectGroup)
}
classDiagram
editorIndex
editorIndex *-- menu : 1. User clicks Timer
commands o-- selectBook
commands o-- selectPage
commands o-- selectLayer
commands o-- selectTimer
class selectors{
- List: App, Book,Page
- List: Layer, Audio, Group, Timer ..
+ projectPageSelector
+ componentSelector
- componentHandler()
}
class selectorBase{
+ selectorIcon
+ entries
+ onClick()
}
selectors <|..selectorBase: 1.1 onClick calls componentHandler
menu *-- selectors
selectors ..> store :1.2 commandHandler sets Timer entries
class commands {
+ page
+ book
+ layer
* class
store.set()
}
class selectTimer {
controller:command()
}
class editorTimer {
+ timerTable as selectbox
+ controlbox
+ buttons
+ controller
}
class timerTable {
oncllick()
}
class controller {
command()
- util.decode(params)
- controlbox:didHide(UI)
- controlbox:destroy(UI)
- controlbox:init(UI)
- controlbox:setValue(decoded)
- controlbox:create(UI)
- controlbox:didShow(UI)
- controlbox:show()
- onCompletebox:show()
- buttons:show()
}
timerTable ..> BTree: 2. User clicks a timer entry <br> 2.1 BTree select timer TRUE
editorTimer *-- timerTable
editorTimer <|.. controller
timerTable <|.. baseTable
editorTimer *-- conrolbox
conrolbox <|.. baseProps
class baseTable {
+ entries
commandHandler()
store.listener()
render()
}
class BTree{
setCondition()
setActionStatus()
}
BTree ..> commands : 2.2. load timer (Activated) <br> 2.3 menu/controller/selettor/selectTimer
commands ..> selectTimer: 2.5. select timer
selectTimer ..> controller: 2.5 calls command() to display timerEditor
store ..> baseTable : 1.3. calls listenr in baseTable
User can create mutliple sprites as seqeunce data from a spresheet(imageSheet) , and for Sync Audio & Text, one line of text consists of words.
spitesheet model
sequenceData = {
{
name = "default",
count = 2,
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
start = 1,
time = 1000,
},
{
name = "test",
frames = {1,2},
loopCount = 0,
loopDirection = "forward", -- reverse after last frame
pause = false,
time = 1000,
}
sync audio model for “A B C”
M.line = {
{ start = 0, out = 1000, dur = 0, name = "A", file = "a.mp3", action = "onComplete"},
{ start = 1000, out = 2000, dur = 0, name = "B", file = "b.mp3", action = "onComplete"},
{ start = 2000, out = 3000, dur = 0, name = "C", file = "c.mp3", action = "onComplete"},
}
These arrays of data are displayed by listbox.lua and add/save/delete/select are handled in replacement.controller.add, replacement.controller.save, replecment.controller.delete, replacment.controller.select respectively.
M.commands = {"delete", "save", "cancel", "select", "add"}
---
function M:init(UI)
local app = App.get()
for i = 1, #self.commands do
app.context:mapCommand(
"editor.replacement.list." .. self.commands[i],
"editor.replacement.controller." .. self.commands[i]
)
end
end
--
// TBI a tool for imagesheet
ref: https://kwiksher.com/doc/kwik_tutorial/animations/working_with_spritesheet/#b-if-you-are-using-an-optimized-file-from-texture-packer
//TBI the sequence data is generated by frames' data in sheetInfo lua?
//if asset.json's sheetInfo is text string, it is from texture packer
```
{
filename = "slots.png",
sheetInfo = "slots.lua",
}
```
Kwik needs these entry in sequence data to create each sprite
```
Name:bird
Start Frame 1
Frame Count 1
Length 1 sec
Name:cat
Start Frame 2
Frame Count 1
Length 1 sec
```
the table of frames is exported from Texturepacker
```lua
frames = {
{
-- bird
x=2,
y=2,
width=191,
height=222,
sourceX = 1,
sourceY = 1,
sourceWidth = 192,
sourceHeight = 223
},
'''
//TBI editor.template.components.pageX.replacement.defaults.spritesheet
// the sheetInfo for a Same size frames sheet should be a table
Now
```
local M = {
name = "spritesheet",
class = "spritesheet",
type = "uniform-sized", -- TexturePacker, Animate
controls = {
filename = "imagesheet.png",
sheetInfo = "spritesheet",
```
Update it as same as sheetInfo in assets.json
```
sheetInfo = {
width = 188,
height = 188,
sheetWidth = 376,
sheetHeight = 188,
},
```
with the listbox value, save for a replacement component is as normal as the other components. editor.controller.save is processed and retrives useClassEditorProps() in replacment.controller.index
// TBI? listbox getValue in userClassEditorProps // suite_page1_replacements.lua does not have unit tests for saving spreadsheet/video. It has “editor.replacement.list.save” though.
server/tests/*.http
App/skelton/page
this book/page is an empty but it loads the server in editor.index
function M:didShow(UI)
...
elseif httpServerOn then
--------- pegasus init with --------
require("server.index").run{
selectors = selectors,
UI = UI,
bookTable = bookTable,
pageTable = pageTable,
layerTable = layerTable
}
end
...
App/Transition2/page
this is a page without a kwik index model for testing composer.gotoScene
App/bookFree/page1
this is a kwip page. It has the index model. the page is loaded with app.showView()
kwik scenes are attached with model from App/bookX/pageX/index.lua and
local sceneName = ...
--
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={
} },
{ gotoBtn={ --class={"animation"}
} },
{ title={ class={"linear"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})
--
return scene
the index.lua is loaded with app:showView()
function app:showView(viewName, _options)
self.currentViewName = viewName
local scene = self.context.Router[viewName]
if scene == ni then
print("ERROR showView ", viewName )
return
end
local options = _options or {}
options.params = options.params or {}
scene.UI.page = scene.model.name
options.params.sceneProps = {app =scene.app, classType = scene.classType, UI = scene.UI, model=scene.model, getCommands = scene.getCommands}
composer.gotoScene("App."..self.props.appName.."."..viewName, options)
end
doGet/doPost are handled with server/harness.lua, that can access UI table
function M:init(path, params)
projRoot = path
self.selectors = params.selectors
self.UI = params.UI
self.bookTable = params.bookTable
self.pageTable = params.pageTable
self.layerTable = params.layerTable
end
function M:dispatchEevnt(eventName, params)
self.UI.scene.app:dispatchEvent {
name = eventName, -- "editor.selector.selectApp",
UI = self.UI,
params = params
}
-- selectApp
-- returns books
--
end
App/Transition2 is a normal page of Solar2D composer. It does not have a model of index.lua. We coulld load Transition2.page with composer.gotoScene but this way does not work with kwik model with compoennts/commands.
So let’s define I/F.
httpYac
### for composer.gotoScene
GET /Transition2/page
### selectLayer
GET /Transition2/page/character
###
POST /Transition2/page/character/transition2.to
If index.lua is not found, it is a noraml composer page, and the following I/F are used for doGet and doPost
page.lua
function scene:getLayer(layerName)
function scene:getFunc(funcName)
See server/test/book01/
PUT /newBook
template is located in editor/template
destination for a new book is App/newBook
only the directories exclude pageX are copied for newBook
components/index.lua and pageX are generated when you specify a new page name to be added
local scenes = {
{{#pages}}
{{name}},
{{/pages}}
}
return scenes
image/pageX
commands/pageX
components/pageX
models/pageX
PUT /newBook/page1
PUT /newBook/page1/imageOne
TODO: layer_image.lua to display text(layer name) if imageOne.png is not found in assets/pageX
PUT /newBook/page1/imageOne/?class=linear
PUT /newBook/commands/page1/eventOne
create page2 from page1.
PUT /newBook/page2?copyFrom=newBook/page1
editor.pegasus
pegasus is a open-souce lua http-server
httpYac in vscode to send a REST request to editor.pegasus
selectPage.http
this calls these commnands in editor
selectApp
selectBook
selectPage
ref edtior.tests
pegasus harness receives the request in REST format (json)
dispatch events to commands
selectXXX reads App/bookX/pageX/index.lua or models/pageX/.json files, and the data are stored in nanstores for components such bookStore, pageStore, layerStore, audioStore
Response of http Rest in pegasus are retreived by nanostores:get(xxxStore)
selectLayer.http - Read
select a layer and shows the properties
selectTool.http - Write
attach a tool (animation) to a selected layer
graph LR
subgraph vscode
httpYac
end
subgraph solar2D
pegasus
subgraph commands
selectApp
selectBook
selectPage
selectLayer
selectTool[selectTool <br> new or update class props]
end
subgraph controllers_save
updateIndex[index model <br> layer model]
save[save .json]
render[render lua]
updateIndex -.-> render
updateIndex -.-> save
end
subgraph nanosotres
bookStore
pageStore
layerStore
end
subgraph UI
bookTable
pageTable
layerTable
subgraph animation
selectBox
contropProps
pointABBox
onCompletebox
end
subgraph audio
selectBox_[selectBox]
contropProps_[controlProps]
onCompletebox_[onCompletebox]
end
end
subgraph models
subgraph runtime
lua[pageX.index <br> <br> scene.components <br> - layers <br> - audios <br><br> layerX.lua <br> layerX_anim.lua]
end
subgraph persistent
json[models/pageX/.json]
layerX.json
layerX_anim.json
end
end
end
httpYac -.- pegasus
pegasus -. 1.onGet.->selectLayer
selectLayer -. read .- lua
lua -. 1.onGet set .-> layerStore
selectLayer -. show .- layerTable
layerStore -. 1.onGet subscribed data.-> layerTable
selectTool -. nanostores get .- layerStore
selectTool -.read class.- lua
save -. write .- json
save -. write .- layerX_anim.json
save -. write .- layerX.json
render -.write .- lua
pegasus -. 2-1.onPost class.->selectTool
pegasus -. 2-2.onPost save.->updateIndex
layerX_anim.json -.setValue .-> contropProps
lua -. read .- layerX.json
lua -. 2-1 onPost read .- layerX_anim.json
editor.parts.selectors
user selects one of entries of layers, audios, actions, groups, timers, variables
the tables of layers, audios, actions, groups, timers, variables are made with nanostore:set when user clicks one of the menu
componentHandler is attached to the onClick event of menu and then each XXXStore:listener creates each table
local function componentHandler(UI, storeTable)
...
-- should we show the last secection?
if storeTable == "layerTable" then
UI.editor.layerStore:set(UI.scene.model.components.layers)
elseif storeTable == "audioTable" then
UI.editor.audioStore:set(UI.scene.model.components.audios)
elseif storeTable == "groupTable" then
UI.editor.groupStore:set(UI.scene.model.components.groups or {})
elseif storeTable == "timerTable" then
print(storeTable, #UI.scene.model.components.timers)
UI.editor.timerStore:set(UI.scene.model.components.timers)
elseif storeTable == "variableTable" then
UI.editor.variableStore:set(UI.scene.model.components.variables)
elseif storeTable == "actionTable" then
UI.editor.actionStore:set(UI.scene.model.commands)
end
...
UI.scene.model.components.layers is rendered in editor.parts.layerTable
local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
layers = {
{ bg={
} },
{ gotoBtn={ --class={"animation"}
} },
{ title={ class={"linear"} } },
},
audios = {},
groups = {},
timers = {},
variables = {},
page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})
editor.parts.layerTable
the entries of componets.layers are recursively parsed
local function parse(model)
...
return name, class, children
end
local function render(models, xIndex, yIndex, parentObj)
...
for i = 1, #models do
local model = models[i]
local entry = {}
...
entry.name, entry.class, entry.children = parse(model)
-- children
if entry.children and #entry.children > 0 then
...
local childEntries, c = render(entry.children, xIndex + 1, count + yIndex, obj )
for k, v in pairs(childEntries) do
objs[#objs + 1] = v
end
obj.childEntries = childEntries
end
end
...
return objs, count
end
UI.editor.layerStore:listen(
function(foo, fooValue)
M:destroy()
M.selection = nil
M.selections = {}
M.objs = render(fooValue, 0, 0)
end
)
note: util.read() function parses “App/”..book.."/models/"..page .."/index.json"
...
ret.layers = parser(decoded)
...
setFiles(ret.audios, "/audios/short")
setFiles(ret.audios, "/audios/long")
setFiles(ret.groups, "/groups")
setFiles(ret.commands, "/commands")
return ret
{
layers = {
{name = "layerOne", parent="", children = {
{name="childOne}, parent="layerOne", children = {}}
}
},
{name = "layerTwo", parent="", children = {}},
},
audios = {}
}
Open
kwik4 | kwik5 |
---|---|
the psd files are under Kwik/{Project Name} folder. | Please choose a folder that has the psf files to be published |
Solar2D Project
this tells where to publish images of a PSD to a solar2D project Kwik4 was under build4 folder and no choice was available
New
creates a book folder under App folder. A book has scenes(pages). Each scene is genereted from each psd file.
Select
you can select a book where a psd is published as a scene(page).
Book > scene (page) > layer
names of psd files are used to create a scene lua file for Solar2D composer instead of the fixed sequence page01, page02 .. in kwik4
kwik4 | kwik5 |
---|---|
pageOne.psd is refered to “page01” - assets/images/p1 - components/page01 - commands/page01 - views/page01Scene.lua. | pageOne.psd is “pageOne” - assets/images/pageOne - components/pageOne - commands/pageOne - components/pageOne/index.lua |
dist folder
kwik4 | kwik5 |
---|---|
build4 - assets/images/p1 - components/page01 - commands/page01 - views/page01Scene.lua. | App/{bookName} - assets/images/pageOne - components/pageOne - commands/pageOne - components/pageOne/index.lua commands/common components/common components/bookstore |
bookshelf is renamed to bookstore that is an enhancement of the kwik4’s bookshefl imlementation (it was provided with kwikshelf.plugin)
main.lua
you can load a book project with the following code.
require("controller.index").bootstrap({name="bookFree", sceneIndex = 1 })
sceneIndex is defined in App/{bookName}/index.lua
local scenes = {
"page1",
"page2",
}
return scenes
App/{bookName}/components/{pageName}/layers/{layerName}.lua
a component or layer lua file is retrived from composer library of Solar2D.
kwik4 | kwik5 |
---|---|
UI.layer local bg = UI.layer.bg | UI.scene.view local bg = UI.scene.view.bg |
kwik4’s UI.layer is obsolete.
Kwik5 holds all layers in UI.layers as an array, and also UI.scene.view contains all the layers with it.
function M:create(UI)
local sceneGroup = UI.scene.view
-- layers is a array
local layers = UI.layers
for i=1, #layers do
print(layers[i].name)
end
-- sceneGroup is a hash table
local bg = sceneGroup["bg"]
print(bg.name)
end
Validate Names: layer names in Japanese Kana
Layer Groups: publish images of a nested layer group.
animation, button, layer repleacements(spritesheet,video, particles) are editied with Kwik Editor in Solar2D.
It helpes to change values of components properties easily, or add/attach a component class to a layer visually for designers to do a no-code editing
Kwik5 is aimed to be a low code tool faster to work with .lua code easily and directly. See the next Lua section
kwik4 | kwik5 |
---|---|
1. kwik’s component panel saves props to .kwk(XML) 2. Publish .lua from .kwk | 1. UXP Publish code(.lua) as base 2. Create/modify .lua for animation, button .. with Kwik editor in Solar2D simulator For developers, use a text editor to edit .lua directly |
Kwik4 had .kwk xml to hold a properties/values of a Kwik project. Kwik5 uses .lua mainly
App/{bookName}/index.lua
you can add your own(custom) page to the index.lua
local scenes = {
"page1",
"page2",
"yourPageName"
}
return scenes
App/{bookName}/components/{pageName}/index.lua
layers form photoshop(images) and other resouces are separated.
you can add your own(custom) layers to the layers table
nested layres are supported
layers = {
...
{ title = {}},
{
customLayeTop = {
{panelOne ={ }},
{panelTwo = { {buttonOne={} }, {buttonTwo={} }} }
}
}
you can add your own(custom) components to the components table
page sceneName = ...
--
local scene = require('controller.scene'page(sceneName, {
name = "page1",
components = {
layers = {
{ bg={} },
{ gotoBtn={} },
{ title={} },
{ customLayerName={} },
},
audios = { },
groups = { },
timers = { },
variables = { },
pages = { "yourComponentName" }
},
commnands = { },
onInit = function(scene) print("onInit") end
})
--
return scene
layer has a class
Kwik4’s animation, button, layer replacements(spritesheet,video, particles,,) equal to a class in kwik5
you may add your own custom class to a layer
layers = {
...
{ title = {}, class={"animation", "button", "yourClass"}},
}
components
Kwik4’s audio, group, timer, varaible are treated as components in a scene in Kwik5
components = {
layers = {
{ bg={} },
{ gotoBtn={} },
{ title={} },
},
audios = {
long = {"longOne", "longTwo" },
short ={"shortOne","shortTwo"} ,
sync = {"syncOne", "syncTtwo", en={"helloen"}, jp={"hellojp"}}
}
groups = {"groupOne","groupTwo"},
timers = {"timerOne", "timerTwo"},
variables = {"varOne", "varTwo"},
},
common component examples
App/{bookName}/components/{pageName}/index.lua has commnands for events.
For example, panelOne.OK event that comes from a button in panelOne. It fires OK event, and actions code in panelOne.OK.lua in App/{bookName}/commnads/{pageName} will be exectued.
page sceneName = ...
--
local scene = require('controller.scene'page(sceneName, {
name = "page1",
layers = {
{ bg={} },
...
},
components = {
...
},
commnands = {
"panelOne.OK",
"panelOne.Cancel",
"timerOne",
"actionOne",
},
onInit = function(scene) print("onInit") end
})
--
return scene
each book status: isFree, isPurchased, isDownloadable, isDownloaded
See the section of bookstore/components/#Photoshop files table.psd
restore & download all
$5 a month 0 sponsors Get a Sponsor badge on your profile. Let’s start a project with Kwik
$10 a month 0 sponsors Support Kwik development, documentation and bug fixing
$25 a month 0 sponsors Access to new features before in advance of their becoming open source.
$50 a month 0 sponsors Pesonal support. About 3-4 hours in a week
$75 a month 0 sponsors Previous two tiers combined. Let’s discuss about new featuers of Kwik.
$100 a month 0 sponsors I will make significant effort to update or may be create a sample tutorial of your choosing.
$200 a month 0 sponsors Your issues and request hit the top of my list. You may ask for beta testers in our facebook group and issues are handled in a private repository for your beta testing.
$500 a month 0 sponsors This includes everything above, plus your app will be featured in our blog or video if you wish
New
$10 a month
Get a Sponsor badge on your profile.
I can offer you some advice or guidance on how to get started a Kwik project.
$100 a month
You will receive help and support.
I will work for fixing or updating a feature or sample tutorial for you.
$500
Your issues and request hit the top of my list.
I’ll join your private chat for help and support if you like
your issues can be handled in a private repository of kwik shared for your beta testing.
You may ask for beta testers in our facebook group or twitter
your app will be featured in our blog or video if you wish
ubuntu 22.10 kinetic
https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/releases
ubuntu 22.08 (Kinetic): Based on Debian 12 (Bookworm) GNOME 43
/Users/ymmtny/Documents/work/Solar2DTux/
platform/linux/Solar2DBuilder/test
hello
../linux/bin/Solar2DBuilder build --lua "args_arm.lua"
local params =
{
platform='linux',
arch = 'arm', -- armhf, arm64, x86_64, amd64
appName='hello',
appVersion='1.0',
dstPath='.',
projectPath='./HelloWorld',
linuxtemplate='../linux/bin/Resources/template_arm.tgz',
}
return params
build.sh
docker run --rm --name builder -it -v $(pwd):/solar2d -v ¥
$(pwd)/platform/linux/buildx/entrypoint.sh:/entrypoint.sh ¥
--entrypoint /entrypoint.sh ¥
--platform linux/arm64 gcc:12-bookworm
entorypoint.sh
cd /solar2d/platform/linux
sh ./setup_dev.sh
sh ./setup_build.sh
setup_dev.sh
apt-get install -y cmake ...
aput-get install -y temurin-8-jdk temurin-8-jre
...
git clone https://git.launchpad.net/ubuntu/+source/readline
git checkout -b debian/stretch origin/debian/stretch
...
wget wxurl=https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.4/wxWidgets-3.1.4.tar.bz2
...
setup_build.sh
objdump -p /usr/local/bin/Solar2D/Solar2DSimulator | grep NEEDED
NEEDED libGL.so.1
NEEDED libz.so.1
NEEDED libopenal.so.1
NEEDED libfreetype.so.6
NEEDED libpng16.so.16
NEEDED libjpeg.so.62
NEEDED libcrypto.so.3
NEEDED libcurl.so.4
NEEDED libSDL2-2.0.so.0
NEEDED libstdc++.so.6
NEEDED libm.so.6
NEEDED libgcc_s.so.1
NEEDED libc.so.6
snap
cp -R platform/linux/snapcraft/snap_arm64 /solar2d/snap
bookworm uses wayland. raspi-config > Advanced to swich X11
sudo raspi-config nonint do_wayland W1
https://qiita.com/ktamido/items/82ed2f5bd324d4721096 バージョン0.7.0以降の WayVNC は RealVNC と互換性のある RSA-AES 認証が追加されたらしく、適切な証明書を生成すると、RealVNCから接続できる
https://docs.cse.lehigh.edu/xforwarding/xforwarding-mac/ https://stackoverflow.com/questions/65468655/vs-code-remote-x11-cant-get-display-while-connecting-to-remote-server
sudo apt update
sudo apt install x11-apps
java 8
AppImage
https://github.com/ANSH3LL/solar2d-appimage/tree/main
./Solar2D-x86_64.AppImage builder [options]
./Solar2D-x86_64.AppImage simulator
./Solar2D-x86_64.AppImage execute /path/to/main.lua
./Solar2D-x86_64.AppImage
https://github.com/DanS2D/Solar2DTux-AppImageWIPStuff/tree/master
snap
error
pi@kwiksher-solar2d:~/projects/deploy-solar2d $ snap run --strace solar2d
/snap/strace-static/current/bin/strace: Cannot find user 'pi'
https://snapcraft.io/docs/environment-variables
snap run --shell <snap>.<command>
snap connections --all
snap info --verbose vlc
sudo apt update
sudo apt install snapd
sudo reboot
sudo snap install core
snap install hello-world
sudo snap install snapcraft --classic
sudo snap install lxd --channel=latest/stable
sudo snap remove –purge lxd
https://stackoverflow.com/questions/57525289/not-able-to-execute-lxd-lxc-commands-as-sudo
newgrp lxd
sudo usermod -aG pi lxd
lxd init --minimal
snapcraft --use-lxd
sudo usermod -aG ymmtny lxd
sudo gpasswd lxd
snap install opy_latest_amd64.snap --dangerous
unsquashfs <file>.snap
sudo apt update
ln -s ../sysroot sysroot
snap remove --purge lxd
lxd init --minimal
snapcraft --use-lxd
snapcraft prime --shell-after --use-lxd
snapcraft clean
lxc ls
lxc container
lxc delete {name}
snap install solar2d_2100.9999_arm64.snap --dangerous --devmode
snap connections --all
https://snapcraft.io/docs/debug-snaps
sudo snap install strace-static
snap run --strace <snap-name>
sudo snap install snappy-debug
sudo snappy-debug <snap-name>
sudo journalctl --output=short --follow --all | sudo snappy-debug
https://snapcraft.io/docs/c-c-applications
$SNAPCRAFT_ARCH_TRIPLET
environment:
"LD_LIBRARY_PATH": "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio"
"DISABLE_WAYLAND": "1"
https://snapcraft.io/docs/snapcraft-extensions
snapcraft extension gnome-3-28
apps:
firefox:
command: firefox
command-chain: [tmpdir]
desktop: distribution/firefox.desktop
extensions: [gnome-3-34]
layout:
/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/webkit2gtk-4.0:
bind: $SNAP/gnome-platform/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/webkit2gtk-4.0
gnome-3-38
https://snapcraft.io/docs/build-and-staging-dependencies
environment:
LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/lib
The shorthand format will also produce the same result:
```
architectures:
- amd64
```
https://snapcraft.io/docs/defining-a-command
apps:
part-os-release:
command: bin/os-release.sh
parts:
part-os-release:
plugin: dump
source: .
organize:
os-release.sh: bin/
https://snapcraft.io/docs/iterating-over-a-build
snapcraft prime --shell
snapcraft prime --shell-after
snapcraft clean
snapcraft build --shell
https://mir-server.io/docs/packaging-an-x11-application-as-an-iot-gui ⭐️
sudo ifconfig usb0 down for windows with raspi4 type-c
Solar2D/platform/linux/gh_action.sh
sudo snap install snapcraft --classic
sudo snap install lxd --channel=latest/stable
>sudo snap remove --purge lxd
sudo lxd init --minimal
...
...
sudo snapcraft --use-lxd
https://snapcraft.io/docs/installing-snap-on-raspbian
adb
/Solar2D/platform/linux/snapcraft/snap/snapcraft.yaml
base: core20
architectures:
- amd64
layout:
/usr/bin/keytool:
symlink: $SNAP/usr/lib/jvm/java-8-openjdk-amd64/bin/keytool
/usr/bin/jarsigner:
symlink: $SNAP/usr/lib/jvm/java-8-openjdk-amd64/bin/jarsigner
/usr/bin/java:
symlink: $SNAP/usr/lib/jvm/java-8-openjdk-amd64/bin/java
parts:
readline:
source: http://mirrors.kernel.org/ubuntu/pool/main/r/readline/libreadline7_7.0-3_amd64.deb
plugin: dump
source-type: deb
https://forum.snapcraft.io/t/how-to-execute-a-binary-file-inside-a-snap/34342/13
everything (binary and libraries) is inside release/,
apps:
examplebin:
command: mybinary
environment:
LD_LIBRARY_PATH: $SNAP/release:$SNAP/lib/x86_64-linux-gnu
parts:
examplebin:
plugin: dump
source: release
sudo snap install opy_latest_amd64.snap --dangerous
Can I use snapcraft with LXD on macOS? - answered by bard
No, directly using Snapcraft with LXD on macOS isn't possible. Here's the breakdown:
LXD limitations: macOS itself doesn't have built-in LXD support. LXD relies on functionalities offered by the Linux kernel, which macOS lacks.
Snapcraft on macOS: While Snapcraft itself can run on macOS, it's limited to store-related operations. It cannot build snaps directly due to the missing Linux environment.
There are alternative approaches to consider:
Remote LXD instance: You can point Snapcraft to a remote LXD instance running on a Linux machine. This allows leveraging LXD for building while using your macOS machine for development. Tools like "cleanbuild" can facilitate this setup.
Multipass: Snapcraft defaults to using Multipass (a lightweight Ubuntu virtualization solution) on macOS for building snaps. This provides a Linux environment for the build process within macOS.
If you're comfortable with virtualization, using Multipass might be a simpler solution for building snaps on your macOS machine.
Here are some alternative approaches:
Build with LXD: If you're on Raspberry Pi itself, consider using LXD for building snaps. This ensures the snap targets the Raspberry Pi's architecture from the get-go.
Multipass workaround (advanced): There are complex workarounds involving creating a custom cloud image for Multipass that matches Raspberry Pi's architecture. This approach is not recommended for beginners due to its involved nature. You can search for "Multipass Raspberry Pi cloud image" for more information if you're interested.
For most scenarios, building with LXD directly on your Raspberry Pi is the recommended approach for ensuring compatibility with the target platform.
flatpak
flatpak run --command=sh --devel <application-id>
https://davejansen.com/give-full-filesystem-access-to-flatpak-installed-applications/
sudo flatpak override fr.handbrake.ghb --filesystem=home
https://docs.flatpak.org/en/latest/basic-concepts.html
https://docs.flatpak.org/en/latest/sandbox-permissions.html
–share=network
–socket=fallback-x11
–share=ipc
–device=dri
OpenGL rendering
–socket=pulseaudio
–filesystem=home
Flatpak app inside Docker fails with: Could not connect: No such file or directory
dbus-send --system /org/freedesktop/DBus org.freedesktop.DBus || dbus-daemon --system --fork
gh_action_flatpak
sudo apt install flatpak -y
# Add the Flathub repository
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install flathub org.flatpak.Builder -y
sudo flatpak install flathub org.freedesktop.Platform//21.08 -y
sudo flatpak install org.freedesktop.Sdk//21.08 -y
# build flat app and put it in a repo
flatpak run org.flatpak.Builder --disable-rofiles-fuse --force-clean --repo repo build-dir "${YML}"
# Create a single-file bundle from a local repository
flatpak build-bundle ./repo solar2d.flatpak com.solar2d.simulator
flatpak --user install app.flatpak
flatpak run com.solar2d.simulator
https://flatpak.org/setup/Raspberry%20Pi%20OS
runtime-version: '21.08'
Document how to cross-compile
flatpak-builder --arch=aarch64
https://www.reddit.com/r/Fedora/comments/tpsret/how_can_i_force_flatpak_apps_to_run_on_wayland/
–socket=fallback-x11
flatpak run –socket=wayland
flatseal
https://www.reddit.com/r/linux/comments/ymth3a/flatseal_is_incredible_its_a_gui_that_let_you/
wsl
https://www.reddit.com/r/flatpak/comments/15for23/can_flatkpak_be_run_on_wsl2/
sudo service dbus start
export DISPLAY=localhost:0.0
mesa
https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/wikis/Mesa-git
Raspberry Pi OSでGNOME Desktopを使う https://qiita.com/kniwase/items/bb881771cecf2ae50265
raspi-configから増量する Performance Options -> P2 GPU Memory 4GBモデルなら256MB, 8GBモデルなら512MBあたりが無難
deb
Solar2D/platform/resources/linuxPackageApp.lua
clang
aj@raspberrypi:~ $ clang++-13 --version
Debian clang version 13.0.1-6~deb11u1
Target: aarch64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
aj@raspberrypi:~ $ lld-link-13 --version
Debian LLD 13.0.1
aj@raspberrypi:~ $ ldd --version
ldd (Debian GLIBC 2.31-13+rpt2+rpi1+deb11u5) 2.31
aj@andys-mbp ~/Code/cross-clang-rpi4 $ clang++ --version
Apple clang version 14.0.3 (clang-1403.0.22.14.1)
Target: arm64-apple-darwin22.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
aj@andys-mbp ~/Code/cross-clang-rpi4 $ ld --version
Homebrew LLD 14.0.6 (compatible with GNU linkers)
aj@andys-mbp ~/Code/cross-clang-rpi4 $ cmake --version
cmake version 3.26.3
docker buildx
https://www.docker.com/blog/how-to-rapidly-build-multi-architecture-images-with-buildx/
⭐️ https://thecurve.io/building-for-arm-with-docker-packaging-libcamera-apps-for-alpine-on-armv7/
Distroless
https://blog.inductor.me/entry/alpine-not-recommended
https://zenn.dev/yoshii0110/articles/21ddb58c6f6bfa
RaspberryPi OSのDockerイメージを使ってCI環境構築 - github action
techlife/raspios_lite_armhf:2022-09-06_bullseye
debian ubuntu
https://pc.watch.impress.co.jp/docs/column/ubuntu/1512815.html
https://pc.watch.impress.co.jp/docs/column/ubuntu/1531425.html
jetson nano
Hardware codec encoder is not available in orin nano. This makes the new one much worse than the older one when streaming from a camera.
carrier board is not available in the orin series.
price tag is way more than the original $100.
gcc
The Bullseye default compiler is gcc 10, with Bookworm it’s gcc 12. clang-13 the latest clang release on Bullseye, clang-15 the latest on Bookworm.
binutil 2.39 for gcc-12.2.0
http://blog.kmckk.com/archives/5774305.html
GCC の場合、ホストの C++ コンパイラのみ(通常は GCC)で、Binutils と GCC のソースコードから完全な C/C++ クロスコンパイラをビルドすることが可能 リンカのみ Binutils の GNU ld compiler-rt などをビルドする際に -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY を指定
C++11 thread など使う場合は現状 pthread 必須ですから(Posix 環境の場合), だいたい現在のナウでヤングな C++11 or later コードでは -pthread 必須 優秀な aarch64 + C/C++ 開発若人さまが, clang + cmake で aarch64 クロスコンパイル環境を整えることで, 人類史上最速で究極の aarch64 + C/C++ 開発若人さまへと昇華なされるスキームを確立
https://packages.ubuntu.com/search?keywords=clang
jammy (22.04LTS) 14.0
https://gnutoolchains.com/raspberry/
22.04, the default GNU C compiler version is gcc-11
update to Ubuntu 22.04, and then apt install g++-12.
kwiksher-solar2d.loca
/Users/ymmtny/Documents/GitHub/making-iot/Koi/doc/setup_memo_raspi_yolo.md
/Users/ymmtny/Documents/GitHub/AI-RC/backup
otg_mode=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
modules-load=dwc2,g_ether
first_run.sh, Before the rm … command, add this:
```
cat << EOF > /etc/network/interfaces.d/usb0
auto usb0
allow-hotplug usb0
iface usb0 inet static
address 192.168.2.2
netmask 255.255.255.0
gateway 192.168.2.1
EOF
```