mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
cmd/gio: support app icons
If there is an appicon.png file in the main package the gio tool will use it for Android and iOS apps in buildmode exe. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+16
-2
@@ -242,6 +242,20 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
icon := filepath.Join(bi.dir, "appicon.png")
|
||||
iconSnip := ""
|
||||
if _, err := os.Stat(icon); err == nil {
|
||||
err := buildIcons(resDir, icon, []iconVariant{
|
||||
{filepath.Join("mipmap-hdpi", "ic_launcher.png"), 72},
|
||||
{filepath.Join("mipmap-xhdpi", "ic_launcher.png"), 96},
|
||||
{filepath.Join("mipmap-xxhdpi", "ic_launcher.png"), 144},
|
||||
{filepath.Join("mipmap-xxxhdpi", "ic_launcher.png"), 192},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iconSnip = `android:icon="@mipmap/ic_launcher"`
|
||||
}
|
||||
themes := `<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.GioApp" parent="android:style/Theme.NoTitleBar">
|
||||
@@ -284,7 +298,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:glEsVersion="0x00030000"/>
|
||||
<application android:label="Gio">
|
||||
<application %s android:label="%s">
|
||||
<activity android:name="org.gioui.GioActivity"
|
||||
android:label="%s"
|
||||
android:theme="@style/Theme.GioApp"
|
||||
@@ -296,7 +310,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>`, *appID, bi.version, bi.version, appName)
|
||||
</manifest>`, *appID, bi.version, bi.version, iconSnip, appName, appName)
|
||||
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
|
||||
if err := ioutil.WriteFile(manifest, []byte(manifestSrc), 0660); err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,12 +6,17 @@ import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -32,6 +37,7 @@ type buildInfo struct {
|
||||
target string
|
||||
appID string
|
||||
version int
|
||||
dir string
|
||||
archs []string
|
||||
}
|
||||
|
||||
@@ -68,11 +74,16 @@ func main() {
|
||||
errorf("gio: %v", err)
|
||||
}
|
||||
name = filepath.Base(name)
|
||||
dir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkg))
|
||||
if err != nil {
|
||||
errorf("gio: %v", err)
|
||||
}
|
||||
bi := &buildInfo{
|
||||
name: name,
|
||||
pkg: pkg,
|
||||
target: *target,
|
||||
appID: *appID,
|
||||
dir: dir,
|
||||
version: *version,
|
||||
}
|
||||
switch *target {
|
||||
@@ -189,3 +200,43 @@ var allArchs = map[string]arch{
|
||||
clang: "x86_64-linux-android21-clang",
|
||||
},
|
||||
}
|
||||
|
||||
type iconVariant struct {
|
||||
path string
|
||||
size int
|
||||
}
|
||||
|
||||
func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
||||
f, err := os.Open(icon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
img, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resizes errgroup.Group
|
||||
for _, v := range variants {
|
||||
v := v
|
||||
resizes.Go(func() (err error) {
|
||||
scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
|
||||
draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), draw.Src, nil)
|
||||
path := filepath.Join(baseDir, v.path)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if cerr := f.Close(); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
return png.Encode(f, scaled)
|
||||
})
|
||||
}
|
||||
return resizes.Wait()
|
||||
}
|
||||
|
||||
+88
-10
@@ -18,6 +18,8 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const minIOSVersion = "9.0"
|
||||
|
||||
func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
||||
appName := bi.name
|
||||
switch *buildMode {
|
||||
@@ -199,21 +201,86 @@ int main(int argc, char * argv[]) {
|
||||
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
||||
return err
|
||||
}
|
||||
icon := filepath.Join(bi.dir, "appicon.png")
|
||||
if _, err := os.Stat(icon); err == nil {
|
||||
assetPlist, err := iosIcons(bi, tmpDir, app, icon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Merge assets plist with Info.plist
|
||||
cmd := exec.Command(
|
||||
"/usr/libexec/PlistBuddy",
|
||||
"-c", "Merge "+assetPlist,
|
||||
plistFile,
|
||||
)
|
||||
if _, err := runCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", plistFile)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// iosIcons builds an asset catalog and compile it with the Xcode command actool.
|
||||
// iosIcons returns the asset plist file to be merged into Info.plist.
|
||||
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
||||
assets := filepath.Join(tmpDir, "Assets.xcassets")
|
||||
if err := os.Mkdir(assets, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
appIcon := filepath.Join(assets, "AppIcon.appiconset")
|
||||
err := buildIcons(appIcon, icon, []iconVariant{
|
||||
{"ios_2x.png", 120},
|
||||
{"ios_3x.png", 180},
|
||||
{"ios_store.png", 1024},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
contentJson := `{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "ios_2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "ios_3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "ios_store.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
]
|
||||
}`
|
||||
contentFile := filepath.Join(appIcon, "Contents.json")
|
||||
if err := ioutil.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
assetPlist := filepath.Join(tmpDir, "assets.plist")
|
||||
compile := exec.Command(
|
||||
"actool",
|
||||
"--compile", appDir,
|
||||
"--platform", iosPlatformFor(bi.target),
|
||||
"--minimum-deployment-target", minIOSVersion,
|
||||
"--app-icon", "AppIcon",
|
||||
"--output-partial-info-plist", assetPlist,
|
||||
assets)
|
||||
_, err = runCmd(compile)
|
||||
return assetPlist, err
|
||||
}
|
||||
|
||||
func buildInfoPlist(bi *buildInfo) string {
|
||||
appName := strings.Title(bi.name)
|
||||
var platform string
|
||||
switch bi.target {
|
||||
case "ios":
|
||||
platform = "iphoneos"
|
||||
case "tvos":
|
||||
platform = "appletvos"
|
||||
}
|
||||
platform := iosPlatformFor(bi.target)
|
||||
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
@@ -243,7 +310,7 @@ func buildInfoPlist(bi *buildInfo) string {
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>12.4</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
<string>%s</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
@@ -255,7 +322,18 @@ func buildInfoPlist(bi *buildInfo) string {
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>`, bi.appID, appName, bi.version, bi.version, platform)
|
||||
</plist>`, bi.appID, appName, bi.version, bi.version, platform, minIOSVersion)
|
||||
}
|
||||
|
||||
func iosPlatformFor(target string) string {
|
||||
switch target {
|
||||
case "ios":
|
||||
return "iphoneos"
|
||||
case "tvos":
|
||||
return "appletvos"
|
||||
default:
|
||||
panic("invalid platform " + target)
|
||||
}
|
||||
}
|
||||
|
||||
func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
||||
@@ -379,7 +457,7 @@ func iosCompilerFor(target, arch string) (string, []string, error) {
|
||||
"-Werror",
|
||||
"-arch", allArchs[arch].iosArch,
|
||||
"-isysroot", sdkPath,
|
||||
"-m" + platformOS + "-version-min=9.0",
|
||||
"-m" + platformOS + "-version-min=" + minIOSVersion,
|
||||
}
|
||||
return clang, cflags, nil
|
||||
}
|
||||
|
||||
+4
-1
@@ -2,4 +2,7 @@ module gioui.org/cmd
|
||||
|
||||
go 1.12
|
||||
|
||||
require golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||
require (
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||
)
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
Reference in New Issue
Block a user