├── .babelrc ├── app ├── favicon.ico ├── res │ └── generator-thumbs │ │ ├── icons-generic.svg │ │ ├── nine-patches.svg │ │ ├── icon-animator.svg │ │ ├── icons-launcher.svg │ │ ├── icons-notification.svg │ │ ├── icons-actionbar.svg │ │ └── icons-app-shortcut.svg ├── pages │ ├── generic-icon-generator.html │ ├── action-bar-icon-generator.html │ ├── app-shortcut-icon-generator.html │ ├── notification-icon-generator.html │ ├── launcher-icon-generator.html │ ├── index.js │ ├── ninepatch │ │ ├── summer.js │ │ ├── nine-patch-generator.html │ │ ├── nine-patch-loader.js │ │ ├── nine-patch-preview.js │ │ ├── nine-patch-generator.scss │ │ ├── nine-patch-generator.js │ │ └── nine-patch-stage.js │ ├── home │ │ ├── home.html │ │ └── home.scss │ ├── notification-icon-generator.js │ ├── app-shortcut-icon-generator.js │ ├── generic-icon-generator.js │ ├── action-bar-icon-generator.js │ └── launcher-icon-generator.js ├── sw.js ├── sw-prod.js ├── studio │ ├── imagelib │ │ ├── index.js │ │ ├── drawing.js │ │ ├── analysis.js │ │ └── effects.js │ ├── index.js │ ├── forms │ │ ├── index.js │ │ ├── boolean-field.js │ │ ├── text-field.js │ │ ├── range-field.js │ │ ├── form.js │ │ ├── field.js │ │ ├── enum-field.js │ │ └── color-field.jsx │ ├── zip.js │ ├── hash.js │ └── util.js ├── misc.scss ├── typography.scss ├── app.entry.js ├── app.entry.scss ├── core.scss ├── _base.html ├── variables.scss ├── _generator-base.html ├── lib │ ├── material-icons.scss │ ├── material-shadows.scss │ └── material-colors.scss ├── components │ ├── page-header.scss │ ├── output-panel.scss │ ├── components.scss │ └── inputs-panel.scss └── base-generator.js ├── .gitignore ├── README.md ├── .github └── workflows │ └── build-and-deploy.yml ├── webpack.config.js ├── package.json ├── .scss-lint.yml ├── gulpfile.babel.js └── LICENSE /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romannurik/AndroidAssetStudio/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _sandbox 2 | .sass-cache 3 | .tmp 4 | node_modules 5 | !app/node_modules 6 | bower_components 7 | dist 8 | .DS_Store 9 | .publish 10 | *.sublime-workspace 11 | *~ 12 | older-version 13 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icons-generic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/pages/generic-icon-generator.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Generic icon generator 3 | bodyClasses: page-generic-icon-generator 4 | destination: /icons-generic.html 5 | --- 6 | 7 | {% extends '_generator-base.html' %} 8 | 9 | {% block body %} 10 | {{ super() }} 11 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/nine-patches.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/sw.js: -------------------------------------------------------------------------------- 1 | // A simple, no-op service worker that takes immediate control. 2 | 3 | self.addEventListener('install', () => { 4 | // Skip over the "waiting" lifecycle state, to ensure that our 5 | // new service worker is activated immediately, even if there's 6 | // another tab open controlled by our older service worker code. 7 | self.skipWaiting(); 8 | }); 9 | -------------------------------------------------------------------------------- /app/pages/action-bar-icon-generator.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Action bar/tab icon generator 3 | bodyClasses: page-actionbar-icon-generator 4 | destination: /icons-actionbar.html 5 | --- 6 | 7 | {% extends '_generator-base.html' %} 8 | 9 | {% block body %} 10 | {{ super() }} 11 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/pages/app-shortcut-icon-generator.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: App shortcut icon generator 3 | bodyClasses: page-app-shortcut-icon-generator 4 | destination: /icons-app-shortcut.html 5 | --- 6 | 7 | {% extends '_generator-base.html' %} 8 | 9 | {% block body %} 10 | {{ super() }} 11 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/pages/notification-icon-generator.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Notification icon generator 3 | bodyClasses: page-notification-icon-generator 4 | destination: /icons-notification.html 5 | --- 6 | 7 | {% extends '_generator-base.html' %} 8 | 9 | {% block body %} 10 | {{ super() }} 11 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/sw-prod.js: -------------------------------------------------------------------------------- 1 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.3.1/workbox-sw.js"); 2 | 3 | workbox.precaching.precacheAndRoute([]); 4 | 5 | workbox.routing.registerRoute( 6 | new RegExp('https://(?:fonts|www).(?:googleapis|gstatic).com/(.*)'), 7 | workbox.strategies.cacheFirst({ 8 | cacheName: 'google-fonts', 9 | plugins: [ 10 | new workbox.expiration.Plugin({ 11 | maxEntries: 20, 12 | purgeOnQuotaError: true, 13 | }), 14 | new workbox.cacheableResponse.Plugin({ 15 | statuses: [0, 200] 16 | }), 17 | ], 18 | }), 19 | ); 20 | 21 | workbox.googleAnalytics.initialize(); 22 | -------------------------------------------------------------------------------- /app/pages/launcher-icon-generator.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Launcher icon generator 3 | bodyClasses: page-launcher-icon-generator 4 | destination: /icons-launcher.html 5 | --- 6 | 7 | {% extends '_generator-base.html' %} 8 | 9 | {% block banner %} 10 |
11 | error 12 | 13 | This page is no longer maintained. Switch to IconKitchen, the successor to the 14 | Android Asset Studio. 15 | 16 |
17 | {% endblock %} 18 | 19 | {% block body %} 20 | {{ super() }} 21 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icon-animator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/studio/imagelib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {Effects} from './effects'; 18 | export {Drawing} from './drawing'; 19 | export {Analysis} from './analysis'; 20 | -------------------------------------------------------------------------------- /app/studio/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {Hash} from './hash'; 18 | export {Util} from './util'; 19 | export {Zip} from './zip'; 20 | export * from './forms'; 21 | export * from './imagelib'; 22 | -------------------------------------------------------------------------------- /app/misc.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .external-link::after { 18 | @include material-icons; 19 | content: 'open_in_new'; 20 | font-size: 16px; 21 | vertical-align: middle; 22 | margin-left: 2px; 23 | position: relative; 24 | top: -1px; 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | # The Android Asset Studio is no longer maintained. Please use [icon.kitchen](https://icon.kitchen) or the asset creation tools built into Android Studio 4 | 5 | Legacy link: **[Open the Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/)** 6 | 7 | A web-based set of tools for generating graphics and other assets that would eventually be in an Android application's res/ directory. 8 | 9 | Currently available asset generators are for: 10 | 11 | - Launcher icons 12 | - Action bar icons 13 | - Notification icons 14 | - Generic square icons 15 | - Simple nine-patches 16 | 17 | ## Building the tool 18 | 19 | To build, ensure you have `node` and `npm` installed, and run: 20 | 21 | $ npm install 22 | 23 | Once dependencies are installed, run it: 24 | 25 | $ npm start 26 | -------------------------------------------------------------------------------- /app/typography.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | body { 18 | font-family: Roboto, sans-serif; 19 | font-size: 14px; 20 | line-height: 20px; 21 | font-weight: 400; 22 | } 23 | 24 | strong { 25 | font-weight: 500; 26 | } 27 | 28 | a { 29 | color: $colorPrimary; 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Node Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [12.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v1 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: npm install and build 26 | run: | 27 | npm ci 28 | npm run build --if-present 29 | env: 30 | CI: true 31 | 32 | - name: Deploy to gh-pages 33 | uses: JamesIves/github-pages-deploy-action@4.1.5 34 | with: 35 | branch: gh-pages # The branch the action should deploy to. 36 | folder: dist # The folder the action should deploy. 37 | -------------------------------------------------------------------------------- /app/app.entry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | require('babel-polyfill'); 18 | 19 | import * as pages from './pages'; 20 | window.pages = pages; 21 | 22 | window.addEventListener('load', () => { 23 | if ('serviceWorker' in navigator) { 24 | navigator.serviceWorker.register('sw.js'); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /app/app.entry.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @import 'lib/material-colors'; 18 | @import 'lib/material-shadows'; 19 | @import 'lib/material-icons'; 20 | 21 | @import 'variables'; 22 | 23 | @import 'core'; 24 | @import 'typography'; 25 | @import 'misc'; 26 | 27 | @import 'components/**/*'; 28 | 29 | @import 'pages/**/*'; 30 | -------------------------------------------------------------------------------- /app/studio/forms/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {BooleanField} from './boolean-field'; 18 | export {ColorField} from './color-field'; 19 | export {EnumField} from './enum-field'; 20 | export {Field} from './field'; 21 | export {Form} from './form'; 22 | export {ImageField} from './image-field'; 23 | export {RangeField} from './range-field'; 24 | export {TextField} from './text-field'; 25 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icons-launcher.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/pages/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {LauncherIconGenerator} from './launcher-icon-generator'; 18 | export {AppShortcutIconGenerator} from './app-shortcut-icon-generator'; 19 | export {ActionBarIconGenerator} from './action-bar-icon-generator'; 20 | export {NotificationIconGenerator} from './notification-icon-generator'; 21 | export {GenericIconGenerator} from './generic-icon-generator'; 22 | export {NinePatchGenerator} from './ninepatch/nine-patch-generator'; 23 | -------------------------------------------------------------------------------- /app/core.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | html { 18 | min-height: 100%; 19 | height: 100%; 20 | -webkit-font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | margin: 0; 25 | min-height: 100%; 26 | height: 100%; 27 | overflow: hidden; 28 | display: flex; 29 | flex-direction: column; 30 | align-items: stretch; 31 | } 32 | 33 | .main-container { 34 | flex: 1; 35 | display: flex; 36 | flex-direction: row; 37 | align-items: stretch; 38 | min-height: 0; // firefox bug 39 | } 40 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icons-notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/pages/ninepatch/summer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // http://en.wikipedia.org/wiki/Adler32 18 | const MOD_ADLER = 65521; 19 | 20 | class Adler32 { 21 | constructor() { 22 | this.reset(); 23 | } 24 | 25 | reset() { 26 | this._a = 1; 27 | this._b = 0; 28 | this._index = 0; 29 | } 30 | 31 | addNext(value) { 32 | this._a = (this._a + value) % MOD_ADLER; 33 | this._b = (this._b + this._a) % MOD_ADLER; 34 | } 35 | 36 | compute() { 37 | return (this._b << 16) | this._a; 38 | } 39 | } 40 | 41 | export const Summer = Adler32; 42 | -------------------------------------------------------------------------------- /app/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Android Asset Studio{% if title %} - {{ title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% block head %} 16 | {% endblock %} 17 | 18 | 19 | {% block body %} 20 | {% endblock %} 21 | 22 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icons-actionbar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | $huePrimary: 'blue-grey'; 18 | $colorPrimary: material-color($huePrimary, '500'); 19 | $colorPrimary100: material-color($huePrimary, '100'); 20 | $colorPrimary600: material-color($huePrimary, '600'); 21 | $colorPrimary700: material-color($huePrimary, '700'); 22 | 23 | $hueAccent: 'blue'; 24 | $colorAccent: material-color($hueAccent, 'a400'); 25 | 26 | $colorWhitePrimary: #fff; 27 | $colorWhiteSecondary: rgba(#fff, .7); 28 | $colorWhiteTertiary: rgba(#fff, .5); 29 | 30 | $colorBlackPrimary: rgba(#000, .87); 31 | $colorBlackSecondary: rgba(#000, .54); 32 | $colorBlackTertiary: rgba(#000, .38); 33 | 34 | $opacityBlackHighlight: .1; 35 | $opacityWhiteHighlight: .2; 36 | 37 | $thinBorderColor: rgba(#000, .12); 38 | -------------------------------------------------------------------------------- /app/_generator-base.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block body %} 4 | {% block banner %}{% endblock %} 5 | 15 |
16 | {% block generator_body %} 17 |
18 |
19 |
20 |
21 | 25 | 29 |
30 |
31 |
See all
32 |
33 |
34 |
35 | {% endblock %} 36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /app/studio/forms/boolean-field.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {EnumField} from './enum-field'; 18 | 19 | export class BooleanField extends EnumField { 20 | constructor(id, params) { 21 | super(id, params); 22 | params.options = [ 23 | { id: '1', title: params.onText || 'Yes' }, 24 | { id: '0', title: params.offText || 'No' } 25 | ]; 26 | params.defaultValue = params.defaultValue ? '1' : '0'; 27 | params.buttons = true; 28 | } 29 | 30 | getValue() { 31 | return super.getValue() == '1'; 32 | } 33 | 34 | setValue(val, pauseUi) { 35 | super.setValue(val ? '1' : '0', pauseUi); 36 | } 37 | 38 | serializeValue() { 39 | return this.getValue() ? '1' : '0'; 40 | } 41 | 42 | deserializeValue(s) { 43 | this.setValue(s == '1'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import path from 'path'; 3 | import webpack from 'webpack'; 4 | 5 | module.exports = { 6 | entry: { 7 | app: './app/app.entry.js', 8 | }, 9 | output: { 10 | filename: '[name].js', 11 | path: path.join(process.cwd(), './dist'), 12 | }, 13 | optimization: { 14 | splitChunks: { 15 | cacheGroups: { 16 | vendor: { 17 | test: /node_modules/, 18 | chunks: 'initial', 19 | name: 'vendor', 20 | enforce: true, 21 | }, 22 | } 23 | }, 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env': { 28 | NODE_ENV: process.env.NODE_ENV 29 | } 30 | }) 31 | ], 32 | module: { 33 | rules: [ 34 | { 35 | enforce: 'pre', 36 | test: /\.jsx?/, 37 | use: 'import-glob', 38 | }, 39 | { 40 | test: /\.jsx?$/, 41 | exclude: /node_modules/, 42 | use: [ 43 | { 44 | loader: 'babel-loader', 45 | options: { 46 | presets: [ 47 | '@babel/preset-env', 48 | '@babel/preset-react' 49 | ], 50 | plugins: [ 51 | '@babel/plugin-proposal-object-rest-spread' 52 | ], 53 | } 54 | }, 55 | ] 56 | } 57 | ] 58 | }, 59 | resolve: { 60 | extensions: ['.js', '.jsx', '.yaml'] 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /app/res/generator-thumbs/icons-app-shortcut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@babel/core": "^7.11.6", 4 | "@babel/plugin-proposal-object-rest-spread": "^7.11.0", 5 | "@babel/preset-env": "^7.11.5", 6 | "@babel/preset-react": "^7.10.4", 7 | "@babel/register": "^7.11.5", 8 | "babel-loader": "^8.1.0", 9 | "browser-sync": "^2.26.12", 10 | "del": "^6.0.0", 11 | "gulp": "^4.0.2", 12 | "gulp-autoprefixer": "^7.0.1", 13 | "gulp-cache": "^1.1.3", 14 | "gulp-changed": "^4.0.2", 15 | "gulp-csso": "^4.0.1", 16 | "gulp-gh-pages": "^0.5.4", 17 | "gulp-if": "^3.0.0", 18 | "gulp-imagemin": "^7.1.0", 19 | "gulp-load-plugins": "^2.0.4", 20 | "gulp-minify-html": "^1.0.6", 21 | "gulp-nucleus": "0.1.1", 22 | "gulp-replace": "^1.0.0", 23 | "gulp-sass": "^5.1.0", 24 | "gulp-sass-glob": "^1.1.0", 25 | "gulp-tap": "^2.0.0", 26 | "gulp-util": "^3.0.8", 27 | "import-glob": "^1.5.0", 28 | "merge-stream": "^2.0.0", 29 | "pretty-bytes": "^5.4.1", 30 | "run-sequence": "^2.2.1", 31 | "sass": "^1.56.1", 32 | "webpack": "^4.44.2", 33 | "workbox-build": "^3.4.1" 34 | }, 35 | "engines": { 36 | "node": ">=12.0.0" 37 | }, 38 | "scripts": { 39 | "build": "gulp", 40 | "start": "gulp serve" 41 | }, 42 | "private": true, 43 | "dependencies": { 44 | "babel-polyfill": "^6.26.0", 45 | "jquery": "^3.5.1", 46 | "jszip": "^3.5.0", 47 | "react": "^16.13.1", 48 | "react-color": "^2.18.1", 49 | "react-dom": "^16.13.1", 50 | "tinycolor2": "^1.4.2", 51 | "webfontloader": "^1.6.28" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/lib/material-icons.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @mixin use-icon-font { 18 | font-weight: normal; 19 | font-style: normal; 20 | font-size: 24px; 21 | // Preferred icon size 22 | display: inline-block; 23 | width: 1em; 24 | height: 1em; 25 | line-height: 1; 26 | text-transform: none; 27 | letter-spacing: normal; 28 | word-wrap: normal; 29 | // Support for all WebKit browsers. 30 | -webkit-font-smoothing: antialiased; 31 | // Support for Safari and Chrome. 32 | text-rendering: optimizeLegibility; 33 | // Support for Firefox. 34 | -moz-osx-font-smoothing: grayscale; 35 | // Support for IE. 36 | -webkit-font-feature-settings: 'liga'; 37 | -moz-font-feature-settings: 'liga'; 38 | font-feature-settings: 'liga'; 39 | // Custom added for GMP 40 | user-select: none; 41 | } 42 | 43 | @mixin material-icons { 44 | @include use-icon-font; 45 | font-family: 'Material Icons'; 46 | } 47 | 48 | .material-icons { 49 | @include material-icons; 50 | } 51 | -------------------------------------------------------------------------------- /.scss-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | Comment: 3 | # 1. Copyright notices should be preserved in production CSS. 4 | # 2. Some files have their name in a top comment, so as to distinguish the 5 | # rules in live CSS, so allow "blah.scss */" comments to be preserved. 6 | # 3. Preserve CSSJanus "@noflip" annotations. 7 | allowed: '(Copyright)|(\.scss[ */]*$)|(@noflip)' 8 | DeclarationOrder: 9 | enabled: false 10 | DisableLinterReason: 11 | enabled: true 12 | IdSelector: 13 | enabled: false 14 | ImportantRule: 15 | enabled: false 16 | MergeableSelector: 17 | # Only lint on mergeable selectors that are exactly the same. 18 | force_nesting: false 19 | NameFormat: 20 | enabled: false 21 | NestingDepth: 22 | # The default is 3. We're allowing 6 to keep some noise down. 23 | max_depth: 6 24 | PropertySortOrder: 25 | enabled: false 26 | QualifyingElement: 27 | enabled: false 28 | SelectorDepth: 29 | enabled: false 30 | # The default is 3. We're allowing 6 to keep some noise down. 31 | max_depth: 6 32 | SelectorFormat: 33 | # We already have different conventions, like under_scores or camelCase, 34 | # and converting your app from these to hyphened-words could involve 35 | # refactoring your HTML and your JavaScript as well; these are lints 36 | # that cannot be fixed _within_ the file. 37 | enabled: false 38 | Shorthand: 39 | # 3 sides are less readable than 4. 40 | allowed_shorthands: [1,2,4] 41 | SpaceAfterPropertyColon: 42 | enabled: false 43 | # Since we have line length limits in place, sometimes (like with image 44 | # URLs) we need to newline after a property colon. 45 | style: one_space_or_newline 46 | TrailingSemicolon: 47 | enabled: false 48 | TransitionAll: 49 | enabled: true 50 | UnnecessaryMantissa: 51 | enabled: false 52 | UrlFormat: 53 | enabled: false 54 | VendorPrefix: 55 | enabled: false -------------------------------------------------------------------------------- /app/studio/forms/text-field.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import $ from 'jquery'; 18 | 19 | import {Field} from './field'; 20 | 21 | export class TextField extends Field { 22 | createUi(container) { 23 | var fieldContainer = $('.form-field-container', super.createUi(container)); 24 | 25 | this.el_ = $('') 26 | .attr('type', 'text') 27 | .attr('placeholder', this.params_.placeholder) 28 | .addClass('form-field-text') 29 | .val(this.getValue()) 30 | .on('input', ev => { 31 | var oldVal = this.getValue(); 32 | var newVal = $(ev.currentTarget).val(); 33 | if (oldVal != newVal) { 34 | this.setValue(newVal, true); 35 | } 36 | }) 37 | .appendTo(fieldContainer); 38 | } 39 | 40 | getValue() { 41 | var value = this.value_; 42 | if (typeof value != 'string') { 43 | value = this.params_.defaultValue || ''; 44 | } 45 | return value; 46 | } 47 | 48 | setValue(val, pauseUi) { 49 | let oldValue = this.value_; 50 | this.value_ = val; 51 | if (!pauseUi) { 52 | this.el_.val(val); 53 | } 54 | this.notifyChanged_(val, oldValue); 55 | } 56 | 57 | serializeValue() { 58 | return this.getValue(); 59 | } 60 | 61 | deserializeValue(s) { 62 | this.setValue(s); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/components/page-header.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @mixin pageHeaderLink { 18 | color: $colorWhiteSecondary; 19 | 20 | &:hover, 21 | &:focus { 22 | color: $colorWhitePrimary; 23 | } 24 | } 25 | 26 | .page-header { 27 | position: relative; 28 | padding: 12px 24px 12px 64px; 29 | background-color: $colorPrimary; 30 | box-shadow: material-shadow(4); 31 | z-index: 3; 32 | transform: translateZ(0); 33 | } 34 | 35 | .page-header-banner { 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | padding: 16px 0; 40 | box-shadow: material-shadow(4); 41 | background-color: material-color('red', '700'); 42 | color: $colorWhitePrimary; 43 | font-weight: 500; 44 | z-index: 4; 45 | 46 | a { 47 | color: $colorWhitePrimary; 48 | // background-color: rgba($colorBlackPrimary, .2); 49 | // padding: 4px 8px; 50 | // border-radius: 4px; 51 | } 52 | 53 | .material-icons { 54 | margin-right: 8px; 55 | } 56 | } 57 | 58 | .page-header-back-button { 59 | position: absolute; 60 | font-size: 24px; 61 | left: 12px; 62 | bottom: 4px; 63 | padding: 12px; 64 | } 65 | 66 | .page-header-crumb { 67 | display: inline-block; 68 | text-decoration: none; 69 | font-size: 14px; 70 | line-height: 20px; 71 | @include pageHeaderLink; 72 | } 73 | 74 | .page-header-title { 75 | display: block; 76 | color: $colorWhitePrimary; 77 | font-size: 20px; 78 | font-weight: 500; 79 | line-height: 32px; 80 | margin: 0; 81 | } 82 | 83 | .page-header-links { 84 | position: absolute; 85 | right: 24px; 86 | top: 50%; 87 | transform: translateY(-50%); 88 | font-size: 14px; 89 | line-height: 20px; 90 | 91 | a { 92 | @include pageHeaderLink; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/studio/zip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import JSZip from 'jszip/lib'; 18 | 19 | import {Util} from './util'; 20 | 21 | 22 | export const Zip = { 23 | createDownloadifyZipButton(element, options) { 24 | return new DownloadZipButton(element, options); 25 | } 26 | }; 27 | 28 | 29 | class DownloadZipButton { 30 | constructor(element, options) { 31 | this.fileSpecs_ = []; 32 | this.el_ = element; 33 | this.el_.click(() => this.generateAndDownloadZipFile_()); 34 | this.updateUI_(); 35 | } 36 | 37 | setZipFilename(zipFilename) { 38 | this.zipFilename_ = zipFilename; 39 | } 40 | 41 | clear() { 42 | this.fileSpecs_ = []; 43 | this.updateUI_(); 44 | } 45 | 46 | add(spec) { 47 | this.fileSpecs_.push(spec); 48 | this.updateUI_(); 49 | } 50 | 51 | updateUI_() { 52 | if (this.fileSpecs_.length && !this.isGenerating_) { 53 | this.el_.removeAttr('disabled'); 54 | } else { 55 | this.el_.attr('disabled', 'disabled'); 56 | } 57 | } 58 | 59 | generateAndDownloadZipFile_() { 60 | let filename = this.zipFilename_ || 'output.zip'; 61 | if (!this.fileSpecs_.length) { 62 | return; 63 | } 64 | 65 | this.isGenerating_ = true; 66 | this.updateUI_(); 67 | 68 | let zip = new JSZip(); 69 | 70 | this.fileSpecs_.forEach(fileSpec => { 71 | if (fileSpec.canvas) { 72 | zip.file(fileSpec.name, fileSpec.canvas.toDataURL().replace(/.*?;base64,/, ''), {base64: true}); 73 | } else { 74 | zip.file(fileSpec.name, fileSpec.textData); 75 | } 76 | }); 77 | 78 | zip.generateAsync({type: 'blob'}).then(blob => { 79 | Util.downloadFile(blob, filename); 80 | this.isGenerating_ = false; 81 | this.updateUI_(); 82 | }).catch(error => { 83 | console.error(error); 84 | this.isGenerating_ = false; 85 | this.updateUI_(); 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/studio/forms/range-field.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import $ from 'jquery'; 18 | 19 | import {Field} from './field'; 20 | 21 | export class RangeField extends Field { 22 | createUi(container) { 23 | var fieldContainer = $('.form-field-container', super.createUi(container)); 24 | var me = this; 25 | 26 | this.el_ = $('
') 27 | .addClass('form-field-range') 28 | .attr('id', this.getHtmlId()) 29 | .appendTo(fieldContainer); 30 | 31 | this.rangeEl_ = $('') 32 | .attr('type', 'range') 33 | .attr('min', this.params_.min || 0) 34 | .attr('max', this.params_.max || 100) 35 | .attr('step', this.params_.step || 1) 36 | .on('input', () => this.setValue(Number(this.rangeEl_.val()) || 0, true)) 37 | .val(this.getValue()) 38 | .appendTo(this.el_); 39 | 40 | if (this.params_.textFn || this.params_.showText) { 41 | this.params_.textFn = this.params_.textFn || (d => d); 42 | this.textEl_ = $('
') 43 | .addClass('form-field-range-text') 44 | .text(this.params_.textFn(this.getValue())) 45 | .appendTo(this.el_); 46 | } 47 | } 48 | 49 | getValue() { 50 | var value = this.value_; 51 | if (typeof value != 'number') { 52 | value = this.params_.defaultValue; 53 | if (typeof value != 'number') 54 | value = 0; 55 | } 56 | return value; 57 | } 58 | 59 | setValue(val, pauseUi) { 60 | let oldValue = this.value_; 61 | this.value_ = val; 62 | if (!pauseUi) { 63 | this.rangeEl_.val(val); 64 | } 65 | if (this.textEl_) { 66 | this.textEl_.text(this.params_.textFn(val)); 67 | } 68 | this.notifyChanged_(val, oldValue); 69 | } 70 | 71 | serializeValue() { 72 | return this.getValue(); 73 | } 74 | 75 | deserializeValue(s) { 76 | this.setValue(Number(s)); // don't use parseInt nor parseFloat 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/studio/forms/form.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Class defining a data entry form for use in the Asset Studio. 19 | */ 20 | export class Form { 21 | /** 22 | * Creates a new form with the given parameters. 23 | * @constructor 24 | * @param {Array} [params.inputs] A list of inputs 25 | */ 26 | constructor(params) { 27 | this.id_ = params.id; 28 | this.params_ = params; 29 | this.fields_ = params.fields; 30 | this.fields_.forEach(field => field.setForm_(this)); 31 | this.fields_.forEach(field => field.createUi(params.container)); 32 | } 33 | 34 | /** 35 | * Adds an onchange listener. 36 | */ 37 | onChange(listener) { 38 | this.changeListeners_ = (this.changeListeners_ || []).concat([listener]); 39 | } 40 | 41 | /** 42 | * Notifies that the form contents have changed; 43 | * @private 44 | */ 45 | notifyChanged_(field, newValue, oldValue) { 46 | if (this.pauseNotify_) { 47 | return; 48 | } 49 | (this.changeListeners_ || []).forEach(listener => listener(field, newValue, oldValue)); 50 | } 51 | 52 | /** 53 | * Returns the current values of the form fields, as an object. 54 | * @type Object 55 | */ 56 | getValues() { 57 | let values = {}; 58 | this.fields_.forEach(field => values[field.id_] = field.getValue()); 59 | return values; 60 | } 61 | 62 | /** 63 | * Returns all available serialized values of the form fields, as an object. 64 | * All values in the returned object are either strings or objects. 65 | * @type Object 66 | */ 67 | getValuesSerialized() { 68 | let values = {}; 69 | this.fields_.forEach(field => { 70 | let value = field.serializeValue ? field.serializeValue() : undefined; 71 | if (value !== undefined) { 72 | values[field.id_] = field.serializeValue(); 73 | } 74 | }); 75 | 76 | return values; 77 | } 78 | 79 | /** 80 | * Sets the form field values for the key/value pairs in the given object. 81 | * Values must be serialized forms of the form values. The form must be 82 | * initialized before calling this method. 83 | */ 84 | setValuesSerialized(serializedValues) { 85 | this.pauseNotify_ = true; 86 | this.fields_ 87 | .filter(field => field.id_ in serializedValues && field.deserializeValue) 88 | .forEach(field => field.deserializeValue(serializedValues[field.id_])); 89 | this.pauseNotify_ = false; 90 | this.notifyChanged_(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/pages/home/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | bodyClasses: page-home 4 | destination: /index.html 5 | generatorGroups: 6 | - items: 7 | - url: icons-launcher.html 8 | image: res/generator-thumbs/icons-launcher.svg 9 | title: Launcher icon generator 10 | description: Generate launcher and store listing icons for your app. 11 | 12 | - url: icons-app-shortcut.html 13 | image: res/generator-thumbs/icons-app-shortcut.svg 14 | title: App shortcut icon generator 15 | description: Generate app launcher shortcut icons (Android 7.1+). 16 | 17 | - url: https://shapeshifter.design/ 18 | image: res/generator-thumbs/icon-animator.svg 19 | extraClasses: is-external 20 | title: Shape Shifter 21 | description: Create vector drawables and vector icon animations. 22 | 23 | - url: icons-notification.html 24 | image: res/generator-thumbs/icons-notification.svg 25 | title: Notification icon generator 26 | description: 27 | Generate notification icons for your app. These show up 28 | in the system status bar and notification shade. 29 | 30 | - url: nine-patches.html 31 | image: res/generator-thumbs/nine-patches.svg 32 | title: Simple nine-patch generator 33 | description: 34 | Generate nine-patch (.9.png) assets for your app, normally 35 | used for custom UI widgets. 36 | 37 | - url: icons-generic.html 38 | image: res/generator-thumbs/icons-generic.svg 39 | title: Generic icon generator 40 | description: 41 | Generate square icons for custom use within your app. 42 | 43 | - url: icons-actionbar.html 44 | image: res/generator-thumbs/icons-actionbar.svg 45 | title: Action bar/tab icon generator 46 | description: Generate icons for the app bar or tab bars. 47 | 48 | --- 49 | {% extends '_base.html' %} 50 | 51 | {% block header %} 52 | {% endblock %} 53 | 54 | {% block body %} 55 |
56 |
57 |

Android Asset Studio

58 |

59 | A collection of tools to easily generate assets such as launcher icons 60 | for your Android app. 61 |

62 |
63 |
64 | {% for group in generatorGroups %} 65 |
66 |
67 | {% if group.title %}

{{ group.title }}

{% endif %} 68 | {% for generator in group.items %} 69 | 71 |
72 | {% include generator.image %} 73 |
74 |
{{ generator.title }}
75 |
{{ generator.description }}
76 |
77 | {% endfor %} 78 |
79 |
80 | {% endfor %} 81 | 87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /app/pages/home/home.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | $homeCardWidth: 296px; 4 | $homeCardGutter: 24px; 5 | $homeHeaderOverlap: 48px; 6 | 7 | @function home-card-span($numCards) { 8 | @return $homeCardWidth * $numCards + $homeCardGutter * ($numCards - 1); 9 | } 10 | 11 | @function home-card-span-outer($numCards) { 12 | @return home-card-span($numCards) + $homeCardGutter * 2; 13 | } 14 | 15 | body.page-home { 16 | font-size: 14px; 17 | line-height: 20px; 18 | background-color: material-color('grey', '200'); 19 | min-height: 100%; 20 | height: auto; 21 | overflow: auto; 22 | 23 | .home-header, 24 | .home-generator-list, 25 | .home-footer { 26 | flex: 0 0 auto; 27 | display: flex; 28 | flex-direction: column; 29 | align-items: center; 30 | 31 | .inner { 32 | box-sizing: border-box; 33 | flex: 0 0 auto; 34 | padding: 0 24px; 35 | 36 | @for $i from 1 through 3 { 37 | @media only screen and (min-width: home-card-span-outer($i)) { 38 | width: home-card-span-outer($i); 39 | } 40 | } 41 | } 42 | } 43 | 44 | .home-header { 45 | background-color: $colorPrimary; 46 | color: $colorWhiteSecondary; 47 | font-size: 16px; 48 | line-height: 24px; 49 | margin-bottom: -$homeHeaderOverlap; 50 | 51 | .inner { 52 | margin-top: 48px; 53 | margin-bottom: $homeHeaderOverlap; 54 | } 55 | 56 | h1 { 57 | color: $colorWhitePrimary; 58 | font-size: 28px; 59 | line-height: 36px; 60 | font-weight: 400; 61 | margin: 0 0 12px 0; 62 | } 63 | 64 | .subtitle { 65 | margin: 0 0 48px 0; 66 | max-width: 360px; 67 | } 68 | } 69 | 70 | .home-footer { 71 | .inner { 72 | color: $colorBlackTertiary; 73 | margin: 24px 0 64px 0; 74 | } 75 | } 76 | 77 | .home-generator-list { 78 | .inner { 79 | display: flex; 80 | flex-flow: row wrap; 81 | justify-content: flex-start; 82 | align-items: stretch; 83 | padding-right: 0; 84 | } 85 | } 86 | 87 | .home-generator-card { 88 | width: $homeCardWidth; 89 | margin: 0 24px 24px 0; 90 | display: flex; 91 | flex-direction: column; 92 | background-color: #fff; 93 | border-radius: 2px; 94 | box-shadow: material-shadow(2); 95 | text-decoration: none; 96 | overflow: hidden; 97 | transition: box-shadow .1s ease; 98 | outline: 0; 99 | 100 | &:hover, 101 | &:focus { 102 | box-shadow: material-shadow(5); 103 | } 104 | 105 | &:active { 106 | box-shadow: material-shadow(8); 107 | } 108 | 109 | .generator-image { 110 | width: $homeCardWidth; 111 | height: math.div($homeCardWidth, 1.48); 112 | color: $colorAccent; 113 | } 114 | 115 | .generator-title { 116 | margin: 16px 16px 0 16px; 117 | font-size: 16px; 118 | line-height: 24px; 119 | font-weight: 500; 120 | color: $colorBlackPrimary; 121 | } 122 | 123 | &.is-external .generator-title::after { 124 | @include material-icons; 125 | content: 'open_in_new'; 126 | font-size: 16px; 127 | vertical-align: middle; 128 | margin-left: 4px; 129 | position: relative; 130 | top: -1px; 131 | } 132 | 133 | .generator-description { 134 | margin: 4px 16px 16px 16px; 135 | font-size: 14px; 136 | line-height: 20px; 137 | color: $colorBlackSecondary; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/pages/notification-icon-generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import $ from 'jquery'; 18 | 19 | import * as studio from '../studio'; 20 | 21 | import {BaseGenerator} from '../base-generator'; 22 | 23 | const ICON_SIZE = {w: 24, h: 24}; 24 | const TARGET_RECT = {x: 1, y: 1, w: 22, h: 22}; 25 | 26 | export class NotificationIconGenerator extends BaseGenerator { 27 | setupForm() { 28 | super.setupForm(); 29 | $('.outputs-panel').attr('data-theme', 'dark'); 30 | 31 | let defaultNameForSourceValue_ = v => { 32 | let name = studio.Util.sanitizeResourceName(v.name || 'example'); 33 | return `ic_stat_${name}`; 34 | }; 35 | 36 | let nameField; 37 | this.form = new studio.Form({ 38 | id: 'iconform', 39 | container: '#inputs-form', 40 | fields: [ 41 | new studio.ImageField('source', { 42 | title: 'Source', 43 | helpText: 'Must be transparent', 44 | maxFinalSize: { w: 128, h: 128 }, 45 | defaultValueClipart: 'ac_unit', 46 | dropTarget: document.body, 47 | onChange: (newValue, oldValue) => { 48 | if (nameField.getValue() == defaultNameForSourceValue_(oldValue)) { 49 | nameField.setValue(defaultNameForSourceValue_(newValue)); 50 | } 51 | } 52 | }), 53 | (nameField = new studio.TextField('name', { 54 | newGroup: true, 55 | title: 'Name', 56 | helpText: 'Used when generating ZIP files.', 57 | defaultValue: defaultNameForSourceValue_({}) 58 | })) 59 | ] 60 | }); 61 | this.form.onChange(field => this.regenerateDebounced_()); 62 | } 63 | 64 | regenerate() { 65 | let values = this.form.getValues(); 66 | values.name = values.name || 'ic_stat'; 67 | 68 | this.zipper.clear(); 69 | this.zipper.setZipFilename(`${values.name}.zip`); 70 | 71 | this.densities.forEach(density => { 72 | let mult = studio.Util.getMultBaseMdpi(density); 73 | let iconSize = studio.Util.multRound(ICON_SIZE, mult); 74 | 75 | let outCtx = studio.Drawing.context(iconSize); 76 | let tmpCtx = studio.Drawing.context(iconSize); 77 | 78 | if (values.source.ctx) { 79 | let srcCtx = values.source.ctx; 80 | studio.Drawing.drawCenterInside( 81 | tmpCtx, 82 | srcCtx, 83 | studio.Util.mult(TARGET_RECT, mult), 84 | {x: 0, y: 0, w: srcCtx.canvas.width, h: srcCtx.canvas.height}); 85 | } 86 | 87 | studio.Effects.fx([ 88 | {effect: 'fill-color', color: '#fff'} 89 | ], outCtx, tmpCtx, iconSize); 90 | 91 | this.zipper.add({ 92 | name: `res/drawable-${density}/${values.name}.png`, 93 | canvas: outCtx.canvas 94 | }); 95 | 96 | this.setImageForSlot_(density, outCtx.canvas.toDataURL()); 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/studio/hash.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import $ from 'jquery'; 18 | 19 | import {Util} from './util'; 20 | 21 | export const Hash = { 22 | bindFormToDocumentHash(form) { 23 | if (this.boundForm_) { 24 | console.error('already bound to a form'); 25 | return; 26 | } 27 | 28 | this.boundForm_ = form; 29 | 30 | form.onChange(Util.debounce(100, () => { 31 | this.currentHash_ = paramsToHash_(form.getValuesSerialized()); 32 | window.history.replaceState({}, '', '#' + this.currentHash_); 33 | })); 34 | 35 | let maybeUpdateHash_ = () => { 36 | // Don't use document.location.hash because it automatically 37 | // resolves URI-escaped entities. 38 | let newHash = paramsToHash_(hashToParams_( 39 | (document.location.href.match(/#.*/) || [''])[0])); 40 | if (newHash != this.currentHash_) { 41 | form.setValuesSerialized(hashToParams_(newHash)); 42 | this.currentHash_ = newHash; 43 | } 44 | }; 45 | 46 | $(window).on('hashchange', maybeUpdateHash_); 47 | 48 | maybeUpdateHash_(); 49 | } 50 | }; 51 | 52 | function hashToParams_(hash) { 53 | const params = {}; 54 | hash = hash.replace(/^[?#]/, ''); 55 | 56 | hash.split('&').forEach(entry => { 57 | let [path, val] = entry.split('=', 2); 58 | path = decodeURIComponent(path || ''); 59 | val = decodeURIComponent(val || ''); 60 | 61 | // Most of the time path == key, but for objects like a.b=1, we need to 62 | // descend into the hierachy. 63 | let pathArr = path.split('.'); 64 | let obj = params; 65 | pathArr.slice(0, -1).forEach(pathPart => { 66 | obj[pathPart] = obj[pathPart] || {}; 67 | obj = obj[pathPart]; 68 | }); 69 | let key = pathArr[pathArr.length - 1]; 70 | if (key in obj) { 71 | // Handle array values. 72 | if (Array.isArray(obj[key])) { 73 | obj[key].push(val); 74 | } else { 75 | obj[key] = [obj[key], val]; 76 | } 77 | } else { 78 | obj[key] = val; 79 | } 80 | }); 81 | 82 | return params; 83 | } 84 | 85 | function paramsToHash_(params, prefix) { 86 | const hashArr = []; 87 | const keyPath_ = k => encodeURIComponent((prefix ? prefix + '.' : '') + k); 88 | const pushKeyValue_ = (k, v) => { 89 | if (v === false) v = 0; 90 | if (v === true) v = 1; 91 | hashArr.push(keyPath_(k) + '=' + encodeURIComponent(v.toString())); 92 | }; 93 | 94 | for (let key in params) { 95 | let val = params[key]; 96 | if (val === undefined || val === null) { 97 | continue; 98 | } 99 | 100 | if (Array.isArray(val)) { 101 | val.forEach(v => pushKeyValue_(key, v)); 102 | } else if (typeof val == 'object') { 103 | hashArr.push(paramsToHash_(val, keyPath_(key))); 104 | } else { 105 | pushKeyValue_(key, val); 106 | } 107 | } 108 | 109 | return hashArr.join('&'); 110 | } -------------------------------------------------------------------------------- /app/studio/forms/field.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import $ from 'jquery'; 18 | 19 | /** 20 | * Represents a form field and its associated UI elements. This should be 21 | * broken out into a more MVC-like architecture in the future. 22 | */ 23 | export class Field { 24 | /** 25 | * Instantiates a new field with the given ID and parameters. 26 | * @constructor 27 | */ 28 | constructor(id, params) { 29 | this.id_ = id; 30 | this.params_ = params; 31 | if (this.params_.onChange) { 32 | this.onChange(this.params_.onChange); 33 | } 34 | this.enabled_ = true; 35 | } 36 | 37 | /** 38 | * Sets the form owner of the field. Internally called by 39 | * {@link studio.Form}. 40 | * @private 41 | * @param {studio.Form} form The owner form. 42 | */ 43 | setForm_(form) { 44 | this.form_ = form; 45 | this.onChange((newValue, oldValue) => { 46 | this.form_.notifyChanged_(this, newValue, oldValue); 47 | }); 48 | } 49 | 50 | /** 51 | * Returns a complete ID. 52 | * @type String 53 | */ 54 | getLongId() { 55 | return this.form_.id_ + '-' + this.id_; 56 | } 57 | 58 | /** 59 | * Returns the ID for the form's UI element (or container). 60 | * @type String 61 | */ 62 | getHtmlId() { 63 | return '_frm-' + this.getLongId(); 64 | } 65 | 66 | /** 67 | * Generates the UI elements for a form field container. Not very portable 68 | * outside the Asset Studio UI. Intended to be overriden by descendents. 69 | * @private 70 | * @param {HTMLElement} container The destination element to contain the 71 | * field. 72 | */ 73 | createUi(container) { 74 | container = $(container); 75 | this.baseEl_ = $('
') 76 | .addClass('form-field-outer') 77 | .addClass(this.params_.newGroup ? 'is-new-group' : '') 78 | .append( 79 | $('