diff --git a/devsite/.env.sample b/devsite/.env.sample new file mode 100644 index 00000000..20b9a508 --- /dev/null +++ b/devsite/.env.sample @@ -0,0 +1,12 @@ +URL=http://developer.pebble.com +HTTPS_URL=https://developer.pebble.com +EXTERNAL_SERVER=https://developer-api.getpebble.com +DOCS_URL= +ALGOLIA_APP_ID= +ALGOLIA_API_KEY= +ALGOLIA_SEARCH_KEY= +ALGOLIA_PREFIX=devsite-dev- +GOOGLE_ANALYTICS= +ROLLBAR_CLIENT_TOKEN= +RACK_ENV=development +SKIP_DOCS=false diff --git a/devsite/.gitignore b/devsite/.gitignore new file mode 100644 index 00000000..2e26e435 --- /dev/null +++ b/devsite/.gitignore @@ -0,0 +1,12 @@ +__public__ +.sass-cache/ +.env +tmp/ +source/stylesheets/ +log/ +.bundle/ +coverage/ +node_modules/ +vendor/ +.ruby-version +.jekyll-metadata diff --git a/devsite/.rspec b/devsite/.rspec new file mode 100644 index 00000000..e69de29b diff --git a/devsite/.scss-lint.yml b/devsite/.scss-lint.yml new file mode 100644 index 00000000..38435dd3 --- /dev/null +++ b/devsite/.scss-lint.yml @@ -0,0 +1,155 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is the scss-lint configuration file for the Pebble Developer Site. + +linters: + BorderZero: + enabled: true + + CapitalizationInSelector: + enabled: true + + ColorKeyword: + enabled: true + + Comment: + enabled: true + + DebugStatement: + enabled: true + + DeclarationOrder: + enabled: true + + DuplicateProperty: + enabled: true + + ElsePlacement: + enabled: true + style: new_line + + EmptyLineBetweenBlocks: + enabled: true + ignore_single_line_blocks: true + + EmptyRule: + enabled: true + + FinalNewline: + enabled: true + present: true + + HexLength: + enabled: true + style: short + + HexNotation: + enabled: true + style: lowercase + + HexValidation: + enabled: true + + IdWithExtraneousSelector: + enabled: true + + Indentation: + enabled: true + character: space + width: 2 + + LeadingZero: + enabled: true + style: include_zero + + MergeableSelector: + enabled: true + force_nesting: true + + NameFormat: + enabled: true + convention: BEM + + PlaceholderInExtend: + enabled: true + + PropertySortOrder: + enabled: true + ignore_unspecified: false + + PropertySpelling: + enabled: true + extra_properties: [] + + SelectorDepth: + enabled: true + max_depth: 3 + + Shorthand: + enabled: true + + SingleLinePerProperty: + enabled: true + allow_single_line_rule_sets: true + + SingleLinePerSelector: + enabled: true + + SpaceAfterComma: + enabled: true + + SpaceAfterPropertyColon: + enabled: true + style: one_space + + SpaceAfterPropertyName: + enabled: true + + SpaceBeforeBrace: + enabled: true + allow_single_line_padding: false + + SpaceBetweenParens: + enabled: true + spaces: 0 + + StringQuotes: + enabled: true + style: single_quotes + + TrailingSemicolon: + enabled: true + + UnnecessaryMantissa: + enabled: true + + UnnecessaryParentReference: + enabled: true + + UrlFormat: + enabled: true + + UrlQuotes: + enabled: true + + ZeroUnit: + enabled: true + + Compass::*: + enabled: false + + SelectorFormat: + enabled: true + convention: hyphenated_BEM diff --git a/devsite/Gemfile b/devsite/Gemfile new file mode 100644 index 00000000..1177cd52 --- /dev/null +++ b/devsite/Gemfile @@ -0,0 +1,44 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source 'https://rubygems.org' +ruby '2.2.4' + +gem 'slugize', '>= 0.0.3' +gem 'jekyll', '>= 3.0.3' +gem 'jekyll-paginate' +gem 'bundler', '>= 1.7.9' +gem 'rack', '< 1.6.0' +gem 'rack-contrib', '>= 1.2.0' +gem 'nokogiri', '>= 1.6.3.1' +gem 'algoliasearch', '>= 1.6.1' +gem 'htmlentities', '>= 4.3.2' +gem 'rubyzip', '>=1.1.6' +gem 'dotenv', '>= 0.11.1' +gem 'newrelic_rpm', '>= 3.9.8.273' +gem 'rack-wwwhisper', '>= 1.0' +gem 'uglifier', '>= 2.7.0' +gem 'googlestaticmap', '>= 1.2.2' +gem 'rack-rewrite', '>= 1.5.0' +gem 'rack-ssl-enforcer', '>= 0.2.9' +gem 'rack-xframe-options', '>= 0.1.2' +gem 'rack-host-redirect' +gem 'pygments.rb' +gem 'redcarpet' + +group :development, :test do + gem 'rspec', '>= 3.1.0' + gem 'simplecov', '>= 0.9.1' + gem 'rubocop', '>= 0.28.0' +end diff --git a/devsite/Gemfile.lock b/devsite/Gemfile.lock new file mode 100644 index 00000000..806bb642 --- /dev/null +++ b/devsite/Gemfile.lock @@ -0,0 +1,138 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.3.8) + algoliasearch (1.7.0) + httpclient (~> 2.4) + json (>= 1.5.1) + ast (2.2.0) + colorator (0.1) + diff-lcs (1.2.5) + docile (1.1.5) + dotenv (2.1.0) + execjs (2.6.0) + ffi (1.9.10) + git-version-bump (0.15.1) + googlestaticmap (1.2.2) + htmlentities (4.3.4) + httpclient (2.7.1) + jekyll (3.1.2) + colorator (~> 0.1) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.3) + liquid (~> 3.0) + mercenary (~> 0.3.3) + rouge (~> 1.7) + safe_yaml (~> 1.0) + jekyll-paginate (1.1.0) + jekyll-sass-converter (1.4.0) + sass (~> 3.4) + jekyll-watch (1.3.1) + listen (~> 3.0) + json (1.8.3) + kramdown (1.9.0) + liquid (3.0.6) + listen (3.0.6) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9.7) + mercenary (0.3.5) + mini_portile2 (2.0.0) + net-http-persistent (2.9.4) + newrelic_rpm (3.14.2.312) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) + parser (2.3.0.2) + ast (~> 2.2) + posix-spawn (0.3.11) + powerpack (0.1.1) + pygments.rb (0.6.3) + posix-spawn (~> 0.3.6) + yajl-ruby (~> 1.2.0) + rack (1.5.5) + rack-contrib (1.4.0) + git-version-bump (~> 0.15) + rack (~> 1.4) + rack-host-redirect (1.2.1) + rack + rack-rewrite (1.5.1) + rack-ssl-enforcer (0.2.9) + rack-wwwhisper (1.1.9) + addressable (~> 2.0) + net-http-persistent + rack (~> 1.0) + rack-xframe-options (0.1.2) + rack (>= 0.9.1) + rainbow (2.1.0) + rb-fsevent (0.9.7) + rb-inotify (0.9.7) + ffi (>= 0.5.0) + redcarpet (3.3.4) + rouge (1.10.1) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.2) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) + rubocop (0.36.0) + parser (>= 2.3.0.0, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + ruby-progressbar (1.7.5) + rubyzip (1.1.7) + safe_yaml (1.0.4) + sass (3.4.21) + simplecov (0.11.1) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + slugize (0.0.3) + uglifier (2.7.2) + execjs (>= 0.3.0) + json (>= 1.8.0) + yajl-ruby (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + algoliasearch (>= 1.6.1) + bundler (>= 1.7.9) + dotenv (>= 0.11.1) + googlestaticmap (>= 1.2.2) + htmlentities (>= 4.3.2) + jekyll (>= 3.0.3) + jekyll-paginate + newrelic_rpm (>= 3.9.8.273) + nokogiri (>= 1.6.3.1) + pygments.rb + rack (< 1.6.0) + rack-contrib (>= 1.2.0) + rack-host-redirect + rack-rewrite (>= 1.5.0) + rack-ssl-enforcer (>= 0.2.9) + rack-wwwhisper (>= 1.0) + rack-xframe-options (>= 0.1.2) + redcarpet + rspec (>= 3.1.0) + rubocop (>= 0.28.0) + rubyzip (>= 1.1.6) + simplecov (>= 0.9.1) + slugize (>= 0.0.3) + uglifier (>= 2.7.0) + +RUBY VERSION + ruby 2.2.4p230 + +BUNDLED WITH + 1.13.6 diff --git a/devsite/README.md b/devsite/README.md new file mode 100644 index 00000000..eb7d088a --- /dev/null +++ b/devsite/README.md @@ -0,0 +1,184 @@ +# [developer.pebble.com][site] + +[![Build Status](https://magnum.travis-ci.com/pebble/developer.getpebble.com.svg?token=HUQ9CCUxB447Nq1exrnd)][travis] + +This is the repository for the [Pebble Developer website][site]. + +The website is built using [Jekyll](http://jekyllrb.com) with some plugins that +provide custom functionality. + +For anyone who wants to contribute to the content of the site, you should find +the information in one of the sections below. + +* [Blog Posts](#blog-posts) +* [Markdown](#markdown) +* [Landing Page Features](#landing-page-features) +* [Colors](#colors) + +## Getting Started + +Once you have cloned the project you will need to run `bundle install` to +install the Ruby dependencies. If you do not have [bundler](http://bundler.io/) +installed you will need to run `[sudo] gem install bundler` first. + +You should also do `cp .env.sample .env` and edit the newly created `.env` file +with the appropriate values. Take a look at the +[Environment Variables documentation](/docs/environment.md) for more details. + +To start the Jekyll web server, run `bundle exec jekyll serve`. + +## JS Documentation + +The PebbleKit JS and Rocky documentation is generated with the +[documentation.js](documentation.js.org) framework. The documentation tool can +create a JSON file from the JSDocs contained in the [js-docs](/js-docs) +folder. + +To install documentation.js, run `npm install -g documentation` + +To regenerate the `/source/_data/rocky-js.json` file, run `./scripts/generate-rocky-docs.sh` + +> **NOTE**: This is intended to be a temporary hack. Ideally the rocky-js.json +> file is generated as part of the release generator (and built using the actual +> Rocky.js source, or stubs in the Tintin repository. + +## Blog Posts + +### Setting up a new author +Add your name to the `source/_data/authors.yml` so the blog knows who you are! + +``` +blogUsername: + name: First Last + photo: https://example.com/you.png +``` + +### Creating a new blog post +Add a Markdown file in `source/_posts/` with a filename in following the +format: `YYYY-MM-DD-Title-of-the-blog-most.md`. + +Start the file with a block of YAML metadata: + +``` +--- +title: Parlez-vous Pebble? Sprechen sie Pebble? ¿Hablas Pebble? +author: blogUsername +tags: +- Freshly Baked +--- +``` + +You should pick one tag from this list: + +* Freshly Baked - Posts announcing or talking about new features +* Beautiful Code - Posts about writing better code +* "#makeawesomehappen" - Hackathons/events/etc +* At the Pub - Guest Blog Posts (presumably written at a pub) +* Down the Rabbit Hole - How Pebble works 'on the inside' +* CloudPebble - Posts about CloudPebble +* Timeline - Posts about Timeline + +### Setting the post's preview text + +The blog's homepage will automatically generate a 'preview' of your blog post. It does this by finding the first set of 3 consecutive blank lines, and using everything before those lines as the preview. + +You should aim to have your preview be 1-2 paragraphs, and end with a hook that causes the reader to want to click the 'Read More' link. + +## Markdown + +There is a [Markdown styleguide and additional syntax cheatsheat][markdown] +you should use if you are writing any Markdown for the site. That includes all +blog posts and guides. + +## Landing Page Features + +The landing page of the website contains a slideshow (powered by [slick][slick]). +The contents of the slideshow, or 'features' as we call them, are generated +from the features data file found at `source/_data/features.yaml`. + +There are two main types of features, images and videos. + +### Image Feature + +```yaml +- title: Want to make your apps more internationally friendly? + url: /guides/publishing-tools/i18n-guide/ + background_image: /images/landing-page/i18n-guide.png + button_text: Read our brand new guide to find out how + button_fg: black + button_bg: yellow + duration: 5000 +``` + +It should be relatively clear what each of the fields is for. For the +`button_fg` and `button_bg` options, check out the [colors](#colors) section +for the available choices. + +The `background_image` can either be a local asset file or an image on an +external web server. + +**Please Remember:** The landing page will see a lot of traffic so you +should strive to keep image sizes small, while still maintaing relatively large +dimensions. Run the images through minifying tools, such as +[TinyPNG][tinypng] or [TinyJPG][tinyjpg], before commiting them to the site. + +### Video Feature + +```yaml +- title: Send a Smile with Android Actionable Notifications + url: /blog/2014/12/19/Leverage-Android-Actionable-Notifications/ + background_image: /images/landing-page/actionable-notifications.png + video: + url: https://s3.amazonaws.com/developer.getpebble.com/videos/actionable-notifications.mp4 + button_text: Learn how to supercharge Your Android Apps + button_fg: white + button_bg: green + duration: 5000 +``` + +To prevent massively bloating the size of this repository, we are hosting all +videos externally on S3. If you do not have permission to upload videos to our +S3 bucket, you will need to ask someone who does! + +In order to enable to videos to play across all of the browsers + platforms, +you will need to provided the video in MP4, OGV and WEBM formats. +There is a script provided in the scripts folder to do the automatic conversion +from MP4, and to export the first frame of the video as a PNG used as a +placeholder while the video loads. + +```sh +./scripts/video-encode.sh PATH_TO_MP4 +``` + +If you run the script as above, it will create an OGV, WEBM and PNG file in the same folder as the MP4. The PNG file should go in the `/assets/images/landing-page/` folder, and the three video files should be uploaded to S3. + +## Colors + +Buttons and Alerts come are available in several different color options, with +both foreground and background modifier classes to give you maximum control. + +The available colors: + +* white +* green +* blue +* red +* purple +* yellow +* orange +* lightblue +* dark-red + +To set the background, use `--bg-` modifier. To set the foreground (i.e) +the text color, use `--fg-`. + +## Troubleshooting + +Trouble building the developer site? Read the [Troubleshooting](/docs/troubleshooting.md) page for some possible solutions. + +[site]: https://developer.pebble.com +[markdown]: ./docs/markdown.md +[slick]: http://kenwheeler.github.io/slick/ +[tinypng]: https://tinypng.com/ +[tinyjpg]: https://tinyjpg.com/ +[travis]: https://magnum.travis-ci.com/pebble/developer.getpebble.com diff --git a/devsite/Rakefile b/devsite/Rakefile new file mode 100644 index 00000000..a373c3e3 --- /dev/null +++ b/devsite/Rakefile @@ -0,0 +1,28 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +task default: "assets:precompile" + +namespace :assets do + desc "Precompile assets" + task :precompile do + Rake::Task["clean"].invoke + sh "bundle exec jekyll build --trace" + end +end + +desc "Remove compiled files" +task :clean do + sh "rm -rf #{File.dirname(__FILE__)}/__public__/*" +end diff --git a/devsite/_config.yml b/devsite/_config.yml new file mode 100644 index 00000000..0c6d145d --- /dev/null +++ b/devsite/_config.yml @@ -0,0 +1,111 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +url: https://developer.pebble.com +https_url: https://developer.pebble.com +baseurl: +asset_path: /assets +external_server: https://developer-api.getpebble.com + +title: Pebble Developers +description: The official developer website for the Pebble smartwatch. + +source: source/ +destination: __public__/ +plugins_dir: plugins/ + +debug: true + +gems: [jekyll-paginate] + +# Blog Options +permalink: none +paginate: 8 +paginate_path: "blog/:num" +excerpt_separator: "\n\n\n" +future: true + +disqus: + short_name: pebbletechblog + show_comment_count: true + +# Markdown Options +markdown: PebbleMarkdownParser +markdown_ext: md + +# SASS options. +sass: + sass_dir: _sass + style: :compressed + +# Helpful and easily changeable external links. +links: + pebble: https://www.pebble.com + jobs: https://www.pebble.com/jobs/ + twitter: https://twitter.com/pebbledev/ + cloudpebble: https://cloudpebble.net/ + cloudpebble_beta: https://beta.cloudpebble.net/ + devportal: https://dev-portal.getpebble.com/ + site_repo: https://github.com/pebble/developer.getpebble.com/ + community_resources_repo: https://github.com/pebble/community-resources/ + github: https://github.com/pebble/ + forums: https://forums.pebble.com + forums_developer: https://forums.pebble.com/c/development + pebblekit_android: https://github.com/pebble/pebble-android-sdk/ + pebblekit_ios: https://github.com/pebble/pebble-ios-sdk/ + examples_org: https://github.com/pebble-examples + pebblekit_android_jar: https://oss.sonatype.org/service/local/repositories/releases/content/com/getpebble/pebblekit/3.0.0/pebblekit-3.0.0-eclipse.jar + legal: + privacy: https://www.pebble.com/legal/privacy/ + cookies: https://www.pebble.com/legal/cookies/ + s3_assets: https://developer-assets.getpebble.com + pebble_tool_root: https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/ + libpebble: https://github.com/pebble/libpebble2 + kickstarter3: https://www.kickstarter.com/projects/597507018/pebble-2-time-2-and-core-an-entirely-new-3g-ultra + discord_invite: http://discord.gg/aRUAYFN + +# Jekyll collections. +collections: + guides: + output: true + permalink: /guides/:path/ + changelogs: + output: true + permalink: /sdk/changelogs/:path/ + +# Default options based for various scopes +defaults: + - scope: + path: "" + type: "posts" + values: + layout: "blog/post" + generate_toc: true + permalink: /blog/:year/:month/:day/:title/ + - scope: + path: "" + type: "guides" + values: + layout: guides/default + menu: true + generate_toc: true + guide_group: + guide_subgroup: + menu_section: guides + - scope: + path: "" + type: changelogs + values: + layout: sdk/changelog + generate_toc: true diff --git a/devsite/app.json b/devsite/app.json new file mode 100644 index 00000000..b0c16ae2 --- /dev/null +++ b/devsite/app.json @@ -0,0 +1,28 @@ +{ + "name": "Pebble Developer Website", + "description": "The official Pebble Developer website.", + "website": "https://developer.pebble.com", + "repository": "https://github.com/pebble/developer.getpebble.com", + "env": { + "EXTERNAL_SERVER": { + "required": true + }, + "DOCS_URL": { + "required": true + }, + "ALGOLIA_APP_ID": { + "required": true + }, + "ALGOLIA_API_KEY": { + "required": true + }, + "ALGOLIA_SEARCH_KEY": { + "required": true + }, + "ALGOLIA_PREFIX": "devsite-staging-", + "RACK_ENV": "staging", + "HEROKU_APP_NAME": { + "required": true + } + } +} diff --git a/devsite/config.ru b/devsite/config.ru new file mode 100644 index 00000000..023d1b8c --- /dev/null +++ b/devsite/config.ru @@ -0,0 +1,53 @@ +require 'newrelic_rpm' +require 'rack/contrib/try_static' +require 'rack/rewrite' +require 'dotenv' +require 'rack/ssl-enforcer' +require 'rack/xframe-options' +require 'rack-host-redirect' + +Dotenv.load +require 'dotenv' + +def load_404 + not_found_page = File.expand_path('../__public__/404.html', __FILE__) + File.read(not_found_page) +end + +if ENV['RACK_ENV'] == 'development' + def not_found_html + load_404 + end +else + use Rack::SslEnforcer, + :hsts => { :expires => 500, :subdomains => false }, + :strict => true + + not_found_html = load_404 +end + +use Rack::XFrameOptions, 'DENY' + +# Determine if the C preview docs are enabled +docs_config = YAML.load_file('source/_data/docs.yaml') +preview_docs = docs_config.key?('c_preview') + +use Rack::Rewrite do + # Redirect all old PebbleKit docs to their new location + r301 %r{/docs/js/(.*)}, '/docs/pebblekit-js/$1' + # Redirect C preview docs to main C docs if there are no preview docs + r302 %r{/docs/c/preview/?(.*)}, '/docs/c/$1' unless preview_docs +end + +use Rack::HostRedirect, { + 'developer.getpebble.com' => 'developer.pebble.com' +} + +use Rack::TryStatic, + root: '__public__', + urls: %w(/), + try: %w(.html index.html /index.html) + +run lambda{ |_env| + [404, { 'Content-Type' => 'text/html' }, [not_found_html]] +} diff --git a/devsite/docs.json b/devsite/docs.json new file mode 100644 index 00000000..9c7361f7 --- /dev/null +++ b/devsite/docs.json @@ -0,0 +1,4971 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n images and other objects onto the canvas element. It provides the 2D rendering\n context for the drawing on the device's display.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 50, + "offset": 206 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 50, + "offset": 206 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 50, + "offset": 206 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": null + }, + { + "title": "description", + "description": "The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n images and other objects onto the canvas element. It provides the 2D rendering\n context for the drawing on the device's display.", + "lineNumber": 2 + } + ], + "loc": { + "start": { + "line": 19, + "column": 0 + }, + "end": { + "line": 25, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 26, + "column": 0 + }, + "end": { + "line": 151, + "column": 2 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "kind": "namespace", + "name": "CanvasRendingContext2D", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sets all pixels in the rectangle at (x,y) with size (width, height) to\n black, erasing any previously drawn content.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 116 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Sets all pixels in the rectangle at (x,y) with size (width, height) to\n black, erasing any previously drawn content.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 27, + "column": 2 + }, + "end": { + "line": 36, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 37, + "column": 2 + }, + "end": { + "line": 37, + "column": 46 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "x", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "clearRect", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "clearRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.clearRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Draws a filled rectangle at (x,y) with size (width, height).", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Draws a filled rectangle at (x,y) with size (width, height).", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 39, + "column": 2 + }, + "end": { + "line": 47, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 48, + "column": 2 + }, + "end": { + "line": 48, + "column": 44 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "x", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "fillRect", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "fillRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.fillRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Paints a rectangle at (x, y) with size (width, height), using the current\n stroke style.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 15, + "offset": 88 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 15, + "offset": 88 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 15, + "offset": 88 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Paints a rectangle at (x, y) with size (width, height), using the current\n stroke style.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 50, + "column": 2 + }, + "end": { + "line": 59, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 60, + "column": 2 + }, + "end": { + "line": 60, + "column": 47 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "x", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "strokeRect", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "strokeRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.strokeRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Draws (fills) text at the given (x,y) position.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Draws (fills) text at the given (x,y) position.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The text to draw", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "text" + }, + { + "title": "param", + "description": "The x-coordinate of the text's starting point", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-coordinate of the text's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The maximum width to draw. If specified, and\n the string is wider than the width, the font is adjusted to use a smaller\n font.", + "lineNumber": 7, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Number" + } + }, + "name": "maxWidth" + } + ], + "loc": { + "start": { + "line": 64, + "column": 2 + }, + "end": { + "line": 74, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 75, + "column": 2 + }, + "end": { + "line": 75, + "column": 46 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "text", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The text to draw", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "x", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "maxWidth", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The maximum width to draw. If specified, and\n the string is wider than the width, the font is adjusted to use a smaller\n font.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 8, + "offset": 128 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 8, + "offset": 128 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 8, + "offset": 128 + } + } + }, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Number" + } + } + } + ], + "name": "fillText", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "fillText", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.fillText" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Draws (fills) text at the given (x,y) position using the current stroke style.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 79, + "offset": 78 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 79, + "offset": 78 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 79, + "offset": 78 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Draws (fills) text at the given (x,y) position using the current stroke style.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The text to draw", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "text" + }, + { + "title": "param", + "description": "The x-coordinate of the text's starting point", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-coordinate of the text's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The maximum width to draw. If specified, and\n the string is wider than the width, the font is adjusted to use a smaller\n font.", + "lineNumber": 7, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Number" + } + }, + "name": "maxWidth" + } + ], + "loc": { + "start": { + "line": 77, + "column": 2 + }, + "end": { + "line": 88, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 89, + "column": 2 + }, + "end": { + "line": 89, + "column": 48 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "text", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The text to draw", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "x", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 46, + "offset": 45 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "maxWidth", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The maximum width to draw. If specified, and\n the string is wider than the width, the font is adjusted to use a smaller\n font.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 7, + "offset": 126 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 7, + "offset": 126 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 7, + "offset": 126 + } + } + }, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Number" + } + } + } + ], + "name": "strokeText", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "strokeText", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.strokeText" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns an object containing information about the text.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Returns an object containing information about the text.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "Information about the measured text", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "TextMetrics" + } + }, + { + "title": "param", + "description": "The text to measure", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "text" + } + ], + "loc": { + "start": { + "line": 91, + "column": 2 + }, + "end": { + "line": 98, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 99, + "column": 2 + }, + "end": { + "line": 99, + "column": 33 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Information about the measured text", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TextMetrics" + } + } + ], + "params": [ + { + "name": "text", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The text to measure", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "measureText", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "measureText", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.measureText" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The width of lines drawn (to the nearest integer) with the context (1.0\n by default)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 84 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 84 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 84 + } + } + }, + "tags": [ + { + "title": "description", + "description": "The width of lines drawn (to the nearest integer) with the context (1.0\n by default)", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 101, + "column": 2 + }, + "end": { + "line": 105, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 106, + "column": 2 + }, + "end": { + "line": 106, + "column": 11 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "name": "lineWidth", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "lineWidth", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.lineWidth" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Determines how the end points of every line are drawn ('butt' by default).\n There are 3 possible values for this property: 'butt', 'round', 'square'.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 149 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 149 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 149 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Determines how the end points of every line are drawn ('butt' by default).\n There are 3 possible values for this property: 'butt', 'round', 'square'.", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 108, + "column": 2 + }, + "end": { + "line": 112, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 113, + "column": 1 + }, + "end": { + "line": 113, + "column": 8 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "name": "lineCap", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "lineCap", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.lineCap" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Determins how two connecting segments with non-zero lengths in a shape\nare joined together ('miter' by default). There are three possible values\nfor this property: 'miter', 'round', 'bevel'", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 189 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 189 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 189 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 115, + "column": 2 + }, + "end": { + "line": 119, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 120, + "column": 2 + }, + "end": { + "line": 120, + "column": 10 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "name": "lineJoin", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "lineJoin", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.lineJoin" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sets the miter limit ratio in pixels (10 by default)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Sets the miter limit ratio in pixels (10 by default)", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 122, + "column": 1 + }, + "end": { + "line": 125, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 126, + "column": 2 + }, + "end": { + "line": 126, + "column": 12 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "name": "miterLimit", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "miterLimit", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.miterLimit" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Gets the current line dash pattern", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Gets the current line dash pattern", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A list of numbers that specifies the distances to\n alternately draw a line and a gap.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Array" + } + } + ], + "loc": { + "start": { + "line": 128, + "column": 2 + }, + "end": { + "line": 134, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 135, + "column": 2 + }, + "end": { + "line": 135, + "column": 29 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A list of numbers that specifies the distances to\n alternately draw a line and a gap.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 36, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 36, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 36, + "offset": 85 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Array" + } + } + ], + "name": "getLineDash", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "getLineDash", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.getLineDash" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sets the current line dash pattern.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Sets the current line dash pattern.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "A list of numbers that specifies the distances to\nalternatly draw a line and a gap.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Array" + }, + "name": "segments" + } + ], + "loc": { + "start": { + "line": 137, + "column": 2 + }, + "end": { + "line": 143, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 144, + "column": 2 + }, + "end": { + "line": 144, + "column": 37 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "params": [ + { + "name": "segments", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A list of numbers that specifies the distances to\nalternatly draw a line and a gap.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 34, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 34, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 34, + "offset": 83 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Array" + } + } + ], + "name": "setLineDash", + "kind": "function", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "setLineDash", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.setLineDash" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sets the line dash pattern offset or \"phase\" (0 by default).", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Sets the line dash pattern offset or \"phase\" (0 by default).", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 146, + "column": 2 + }, + "end": { + "line": 149, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 150, + "column": 2 + }, + "end": { + "line": 150, + "column": 16 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "name": "lineDashOffset", + "memberof": "CanvasRendingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + }, + { + "name": "lineDashOffset", + "scope": "static" + } + ], + "namespace": "CanvasRendingContext2D.lineDashOffset" + } + ] + }, + "path": [ + { + "name": "CanvasRendingContext2D", + "kind": "namespace" + } + ], + "namespace": "CanvasRendingContext2D" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 5, + "offset": 4 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "TextMetrics", + "position": { + "start": { + "line": 1, + "column": 5, + "offset": 4 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " interface represents the dimensions of a text in the\ncanvas (display), as created by ", + "position": { + "start": { + "line": 1, + "column": 18, + "offset": 17 + }, + "end": { + "line": 2, + "column": 33, + "offset": 103 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "measureText", + "position": { + "start": { + "line": 2, + "column": 33, + "offset": 103 + }, + "end": { + "line": 2, + "column": 48, + "offset": 118 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 48, + "offset": 118 + }, + "end": { + "line": 2, + "column": 49, + "offset": 119 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 49, + "offset": 119 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 49, + "offset": 119 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "TextMetrics" + }, + { + "title": "property", + "description": "Calculated width of text in pixels.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "property", + "description": "Calculated height of text in pixels.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + }, + { + "title": "property", + "description": "A number giving the distance\n parallel to the baseline from the alignment point given by the\n ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n bounding rectangle of the given text, in pixels.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "actualBoundingBoxLeft" + }, + { + "title": "property", + "description": "A number giving the distance\n parallel to the baseline from the alignment point given by the\n ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n bounding rectangle of the given text, in CSS pixels.", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "actualBoundingBoxRight" + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 26, + "column": 0 + }, + "end": { + "line": 151, + "column": 2 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/CanvasRenderingContext2D.js", + "code": "/**\n * The `TextMetrics` interface represents the dimensions of a text in the\n * canvas (display), as created by ``measureText``.\n * @typedef {Object} TextMetrics\n * @property {Number} width - Calculated width of text in pixels.\n * @property {Number} height - Calculated height of text in pixels.\n * @property {Number} actualBoundingBoxLeft - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the left side of the\n * bounding rectangle of the given text, in pixels.\n * @property {Number} actualBoundingBoxRight - A number giving the distance\n * parallel to the baseline from the alignment point given by the\n * ``CanvasRenderingContext2D.textAlign`` property to the right side of the\n * bounding rectangle of the given text, in CSS pixels.\n */\n\n\n\n/**\n * @namespace\n * @description\n * The CanvasRenderingContext2D interface is used for drawing rectangles, text,\n * images and other objects onto the canvas element. It provides the 2D rendering\n * context for the drawing on the device's display.\n */\nvar CanvasRendingContext2D = {\n /**\n * @description\n * Sets all pixels in the rectangle at (x,y) with size (width, height) to\n * black, erasing any previously drawn content.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n clearRect: function(x, y, width, height) { },\n\n /**\n * @description\n * Draws a filled rectangle at (x,y) with size (width, height). \n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n fillRect: function(x,y, width, height) { },\n\n /**\n * @description\n * Paints a rectangle at (x, y) with size (width, height), using the current\n * stroke style.\n *\n * @param {Number} x - The x-axis coordinate of the rectangle's starting point\n * @param {Number} y - The y-axis coordinate of the rectangle's starting point\n * @param {Number} width - The rectangle's width\n * @param {Number} height - The rectangle's height\n */\n strokeRect: function(x, y, width, height) { },\n\n\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n */\n fillText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Draws (fills) text at the given (x,y) position using the current stroke style.\n *\n * @param {String} text - The text to draw\n * @param {Number} x - The x-coordinate of the text's starting point\n * @param {Number} y - The y-coordinate of the text's starting point\n * @param {Number} [maxWidth] - The maximum width to draw. If specified, and\n * the string is wider than the width, the font is adjusted to use a smaller\n * font.\n *\n */\n strokeText: function(text, x, y, maxWidth) { },\n\n /**\n * @description\n * Returns an object containing information about the text.\n *\n * @returns {TextMetrics} Information about the measured text\n *\n * @param {String} text - The text to measure\n */\n measureText: function(text) { },\n\n /**\n * @description\n * The width of lines drawn (to the nearest integer) with the context (1.0\n * by default)\n */\n lineWidth,\n\n /**\n * @description\n * Determines how the end points of every line are drawn ('butt' by default).\n * There are 3 possible values for this property: 'butt', 'round', 'square'.\n */\n lineCap,\n\n /**\n * Determins how two connecting segments with non-zero lengths in a shape\n * are joined together ('miter' by default). There are three possible values\n * for this property: 'miter', 'round', 'bevel'\n */\n lineJoin,\n\n /**\n * @description\n * Sets the miter limit ratio in pixels (10 by default)\n */\n miterLimit,\n\n /**\n * @description\n * Gets the current line dash pattern\n *\n * @returns {Array} - A list of numbers that specifies the distances to\n * alternately draw a line and a gap.\n */\n getLineDash: function() { },\n\n /**\n * @description\n * Sets the current line dash pattern.\n *\n * @param {Array} segments - A list of numbers that specifies the distances to\n alternatly draw a line and a gap.\n */\n setLineDash: function(segments) { },\n\n /**\n * @description\n * Sets the line dash pattern offset or \"phase\" (0 by default).\n */\n lineDashOffset\n};\n" + }, + "kind": "typedef", + "name": "TextMetrics", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "properties": [ + { + "name": "width", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calculated width of text in pixels.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calculated height of text in pixels.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "actualBoundingBoxLeft", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A number giving the distance\n parallel to the baseline from the alignment point given by the\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 3, + "offset": 96 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "CanvasRenderingContext2D.textAlign", + "position": { + "start": { + "line": 3, + "column": 3, + "offset": 96 + }, + "end": { + "line": 3, + "column": 41, + "offset": 134 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " property to the left side of the\n bounding rectangle of the given text, in pixels.", + "position": { + "start": { + "line": 3, + "column": 41, + "offset": 134 + }, + "end": { + "line": 4, + "column": 51, + "offset": 218 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 51, + "offset": 218 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 51, + "offset": 218 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "actualBoundingBoxRight", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A number giving the distance\n parallel to the baseline from the alignment point given by the\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 3, + "offset": 96 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "CanvasRenderingContext2D.textAlign", + "position": { + "start": { + "line": 3, + "column": 3, + "offset": 96 + }, + "end": { + "line": 3, + "column": 41, + "offset": 134 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " property to the right side of the\n bounding rectangle of the given text, in CSS pixels.", + "position": { + "start": { + "line": 3, + "column": 41, + "offset": 134 + }, + "end": { + "line": 4, + "column": 55, + "offset": 223 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 55, + "offset": 223 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 55, + "offset": 223 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "TextMetrics", + "kind": "typedef" + } + ], + "namespace": "TextMetrics" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides an interface for the Console.log method", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 49, + "offset": 48 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 49, + "offset": 48 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 49, + "offset": 48 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "Console" + }, + { + "title": "description", + "description": "Provides an interface for the Console.log method", + "lineNumber": 2 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 27 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/Console.js", + "code": "/**\n * @namespace Console\n * @description\n * Provides an interface for the Console.log method\n */\nvar Console = new Object();\n\n/**\n * @description\n * Logs a message\n * @param {String} message - the message to log\n */\nConsole.log = function(message) { };\n" + }, + "kind": "namespace", + "name": "Console", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Logs a message", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Logs a message", + "lineNumber": 1 + }, + { + "title": "param", + "description": "the message to log", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "message" + } + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 13, + "column": 36 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/Console.js", + "code": "/**\n * @namespace Console\n * @description\n * Provides an interface for the Console.log method\n */\nvar Console = new Object();\n\n/**\n * @description\n * Logs a message\n * @param {String} message - the message to log\n */\nConsole.log = function(message) { };\n" + }, + "params": [ + { + "name": "message", + "lineNumber": 3, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "the message to log", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "log", + "kind": "function", + "memberof": "Console", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Console", + "kind": "namespace" + }, + { + "name": "log", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Console.log" + } + ] + }, + "path": [ + { + "name": "Console", + "kind": "namespace" + } + ], + "namespace": "Console" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calls a function after a specified delay", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Calls a function after a specified delay", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "timeoutId - The ID of the timeout", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "The function to execute", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "fct" + }, + { + "title": "param", + "description": "The delay (in ms)", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "delay" + } + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 38 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/rocky.js", + "code": "/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Calls a function after a specified delay\n * @returns {Number} timeoutId - The ID of the timeout\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetTimeout = function(fct, delay) { };\n\n/**\n * @description\n * Clears the delay set by ``setTimeout``\n * @param {Number} timeoutId - The ID of the timeout you wish to clear.\n */\nclearTimeout = function(timeoutId) { };\n\n/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Repeatedly calls a function, with a fixed time delay between each call\n * @returns {Number} intervalId - The ID of the interval\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetInterval = function(fct, delay) { };\n\n/**\n * @description\n * Clears the interval set by ``setInterval``\n * @params {Number} intervalId - The ID of the interval you wish to clear.\n */\nclearInterval = function(intervalId) { };\n\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "timeoutId - The ID of the timeout", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "fct", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The function to execute", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "delay", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The delay (in ms)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "setTimeout", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "setTimeout", + "kind": "function" + } + ], + "namespace": "setTimeout" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Clears the delay set by ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 25, + "offset": 24 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "setTimeout", + "position": { + "start": { + "line": 1, + "column": 25, + "offset": 24 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Clears the delay set by ``setTimeout``", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The ID of the timeout you wish to clear.", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "timeoutId" + } + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 17, + "column": 0 + }, + "end": { + "line": 17, + "column": 39 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/rocky.js", + "code": "/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Calls a function after a specified delay\n * @returns {Number} timeoutId - The ID of the timeout\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetTimeout = function(fct, delay) { };\n\n/**\n * @description\n * Clears the delay set by ``setTimeout``\n * @param {Number} timeoutId - The ID of the timeout you wish to clear.\n */\nclearTimeout = function(timeoutId) { };\n\n/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Repeatedly calls a function, with a fixed time delay between each call\n * @returns {Number} intervalId - The ID of the interval\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetInterval = function(fct, delay) { };\n\n/**\n * @description\n * Clears the interval set by ``setInterval``\n * @params {Number} intervalId - The ID of the interval you wish to clear.\n */\nclearInterval = function(intervalId) { };\n\n" + }, + "params": [ + { + "name": "timeoutId", + "lineNumber": 3, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The ID of the timeout you wish to clear.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "clearTimeout", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "clearTimeout", + "kind": "function" + } + ], + "namespace": "clearTimeout" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Repeatedly calls a function, with a fixed time delay between each call", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Repeatedly calls a function, with a fixed time delay between each call", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "intervalId - The ID of the interval", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "The function to execute", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "fct" + }, + { + "title": "param", + "description": "The delay (in ms)", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "delay" + } + ], + "loc": { + "start": { + "line": 21, + "column": 0 + }, + "end": { + "line": 27, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 28, + "column": 0 + }, + "end": { + "line": 28, + "column": 39 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/rocky.js", + "code": "/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Calls a function after a specified delay\n * @returns {Number} timeoutId - The ID of the timeout\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetTimeout = function(fct, delay) { };\n\n/**\n * @description\n * Clears the delay set by ``setTimeout``\n * @param {Number} timeoutId - The ID of the timeout you wish to clear.\n */\nclearTimeout = function(timeoutId) { };\n\n/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Repeatedly calls a function, with a fixed time delay between each call\n * @returns {Number} intervalId - The ID of the interval\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetInterval = function(fct, delay) { };\n\n/**\n * @description\n * Clears the interval set by ``setInterval``\n * @params {Number} intervalId - The ID of the interval you wish to clear.\n */\nclearInterval = function(intervalId) { };\n\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "intervalId - The ID of the interval", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "fct", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The function to execute", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "delay", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The delay (in ms)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "setInterval", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "setInterval", + "kind": "function" + } + ], + "namespace": "setInterval" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Clears the interval set by ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 28, + "offset": 27 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "setInterval", + "position": { + "start": { + "line": 1, + "column": 28, + "offset": 27 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + } + } + }, + "tags": [ + { + "title": "description", + "description": "Clears the interval set by ``setInterval``", + "lineNumber": 1 + }, + { + "title": "params", + "description": "{Number} intervalId - The ID of the interval you wish to clear.", + "lineNumber": 3 + } + ], + "loc": { + "start": { + "line": 30, + "column": 0 + }, + "end": { + "line": 34, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 35, + "column": 0 + }, + "end": { + "line": 35, + "column": 41 + } + }, + "file": "/Users/devsite/src/pebble/developer.getpebble.com/rocky-stubs/rocky.js", + "code": "/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Calls a function after a specified delay\n * @returns {Number} timeoutId - The ID of the timeout\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetTimeout = function(fct, delay) { };\n\n/**\n * @description\n * Clears the delay set by ``setTimeout``\n * @param {Number} timeoutId - The ID of the timeout you wish to clear.\n */\nclearTimeout = function(timeoutId) { };\n\n/* Shim for Rocky.js docs. Contains top level APIs + the Rocky object */\n\n/**\n * @description\n * Repeatedly calls a function, with a fixed time delay between each call\n * @returns {Number} intervalId - The ID of the interval\n * @param {Function} fct - The function to execute\n * @param {Number} delay - The delay (in ms)\n */\nsetInterval = function(fct, delay) { };\n\n/**\n * @description\n * Clears the interval set by ``setInterval``\n * @params {Number} intervalId - The ID of the interval you wish to clear.\n */\nclearInterval = function(intervalId) { };\n\n" + }, + "name": "clearInterval", + "kind": "function", + "params": [ + { + "title": "param", + "name": "intervalId", + "lineNumber": 35 + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "clearInterval", + "kind": "function" + } + ], + "namespace": "clearInterval" + } +] \ No newline at end of file diff --git a/devsite/docs/development.md b/devsite/docs/development.md new file mode 100644 index 00000000..a8a8f147 --- /dev/null +++ b/devsite/docs/development.md @@ -0,0 +1,12 @@ +# Pebble Developer Site · Development Guide + +## Handlebars Templates + +In order to reduce the size of the JS of the site, we are now pre-compiling +the Handlebars templates and just using the runtime Handlebars library. + +If you change, add or remove from the templates, you just recompile them +into the file at `/source/assets/js/templates.js`. + +There is a bash script at `/scripts/update-templates.sh` that you can use to +generate this file. diff --git a/devsite/docs/environment.md b/devsite/docs/environment.md new file mode 100644 index 00000000..cc43b5f7 --- /dev/null +++ b/devsite/docs/environment.md @@ -0,0 +1,84 @@ +# Environment Variables + +The following environment variables are used in the generation of the site. + +## URL + +This overrides the `url` configuration parameter of Jeyll. Set this to the root +of where the site will be hosted. + +``` +URL=http://developer.pebble.com +``` + +## HTTPS_URL + +This overrides the `https_url` configuration parameter of Jeyll. Set this to +the secure root of where the site will be hosted. + +``` +HTTPS_URL=https://developer.pebble.com +``` + +## PORT + +The port on which the Jekyll server will listen. If you don't set this it will +default to port `4000`. + +``` +PORT=8000 +``` + +## ASSET_PATH + +This sets the `asset_path` configuration variable, which tells the site where +the assets are to be found. + +During development and testing, this can be set to the relative URL of the +assets folder inside the main site. + +For production, this should be set to the CDN where the assets will be uploaded. + +*Note:* As of 8th January 2014, the production version of the site still used +local assets and not a CDN. + +``` +ASSET_PATH=assets/ +``` + +## EXTERNAL_SERVER + +This sets the `external_server` configuration variable, which tells the site the +location of the external server used for events, community blog and contact. + +``` +EXTERNAL_SERVER=https://developer-api.getpebble.com +``` + +## DOCS_URL + +The URL of the server on which the documentation sources are being built. + +The production and staging values are private, so if you do not work for Pebble +you will have to omit it from the environment (or `.env` file). Sorry + +## ALGOLIA_* + +The site search is powered by [Algolia](https://algolia.com). There are four +environment variables that are required to turn on indexing at build time and +also correctly setup the client JS for searching. The production and staging +values can be found on our Algolia account. + +If you do not work for Pebble, or don't care about testing the indexing, then +omit these values from the environment (or `.env` file) to disable Algolia. + +The `ALGOLIA_PREFIX` value is extremely important. Make sure you set it if you +are enabling Algolia support on the site, and check that it matches the scoped +search key. + +``` +ALGOLIA_APP_ID= +ALGOLIA_API_KEY= +ALGOLIA_SEARCH_KEY= +ALGOLIA_PREFIX= +``` diff --git a/devsite/docs/markdown.md b/devsite/docs/markdown.md new file mode 100644 index 00000000..f7a8260b --- /dev/null +++ b/devsite/docs/markdown.md @@ -0,0 +1,305 @@ +# Writing Markdown + +If you are writing anything in Markdown for the Pebble Developer site, you +should read this guide to learn about some of the rules and enhancements that +the site has, beyond those of "standard Markdown". + +## Styleguide + +### 80 character lines + +To keep your Markdown files readable and easy to review, please break all lines +at 80 characters. + +*NOTE:* The line breaking does not affect the HTML output, this is purely to +keep the source files readable and reviewable. + +### Headers + +Use the `#`, `##` etc syntax for headers, and include a space after the hashes +and before the header text. + +``` +## Write Headers Like This + +##Don't Write Them Like This + +And Definitely Don't Do This +======= +``` + +You should also generally avoid using the top level header (`#`) because the +page that is displaying the content will be using the document title in a \ +tag automatically. + +#### Table of Contents + +If enabled, the table of contents for the document will include all headers on +the page. + +You can enable/disable table of contents generation for a specific page in the +YAML metadata: + +``` +generate_toc: true +``` + +#### Anchors + +All headers automatically have anchors attached to them, so you can easily link +to sections of the page. The ID for the header will be the slugized header text. + +For example, `## Install Your App` will become `#install-your-app`. + +### Blockcode + +Use triple backticks for block code, and +[specify the language](http://pygments.org/languages/) to ensure the syntax is +highlighted correctly. + + ```js + var foo = 'bar'; + ``` + + +#### Click to Copy + +By default, all code blocks will include the Click to Copy button in the +top right corner. If you want to disable it, prepend the language with `nc^`. + + ```nc^text + This is not for copying! + ``` + +### Images + +In blog posts and guides, images will be block elements that are centered on +the page. *Plan accordingly.* + +#### Size + +You can control the width (and optionally height) of images using the following +syntax: + +``` +![Image with width](/images/huge_image.png =300) +![Image with width and height](/images/huge_image.png =300x400) +``` + +### HTML + +Do not use HTML unless you **absolutely** have to. It is almost always better to +use Markdown so that we can more easily maintain a consistent style across the +site. + +## Additional Syntax + +### Buttons + +To convert any link into a button simply append a `>` onto the end of the text. + +``` +[Button Text >](http://google.com/) +``` + +You can optionally pass extra button classes after the `>` to modify the style +of the button. + +``` +[Wide Orange Button >{wide,fg-orange}](http://google.com) +``` + +The available classes are: + +* wide +* small +* center +* fg-COLOR +* bg-COLOR + +*Where COLOR is any one of the [available colors](README.md#colors).* + +### Link Data + +To add additional data attributes to links (useful for outbound tracking), +append a `>` to the end of the link title, and format the content as below. + +``` +[Link Text](http://google.com "Link Title >{track-event:click,track-name:google}") +``` + +This will create a link with the attributes `data-track-event="click"` and +`data-track-name="google"`. + +### SDK Documentation Links + +If you wish to link to a section of the SDK documentation, you can do so using +double backticks. This can either be done to enhance existing inline code +or in the text of a link. + +``` +This will link to the ``window_create`` documentation. + +You should check out the page on [Events](``event services``) +``` + +### Pebble Screenshots + +If you want to provide a watch screenshot and have it displayed in a Pebble +wrapper, you should upload the 144x168 image and use the following syntax. + +``` +![ >{pebble-screenshot,pebble-screenshot--time-red}](/images/screenshot.png) +``` + +You can pick from any of the following screenshot wrappers: + +* pebble-screenshot--black +* pebble-screenshot--white +* pebble-screenshot--red +* pebble-screenshot--gray +* pebble-screenshot--orange +* pebble-screenshot--steel-black +* pebble-screenshot--steel-stainless +* pebble-screenshot--snowy-black +* pebble-screenshot--snowy-red +* pebble-screenshot--snowy-white +* pebble-screenshot--time-round-black-20 +* pebble-screenshot--time-round-red-14 + +The following screenshot classes exist, but the accompanying images are not +currently available. They will be aliased to black-20 or red-14 as size +dictates: + +* pebble-screenshot--time-round-rosegold-14 +* pebble-screenshot--time-round-silver-14 +* pebble-screenshot--time-round-silver-20 + +> Please match the wrapper to the screenshot where possible. For example, do not +use an original Pebble wrapper with a color screenshot. + +#### Screenshot Viewer + +If you want to show matching screenshots across multiple platforms, you should use the new +`screenshot_viewer` tag. + +Here is an example of it in use: + +``` +{% screenshot_viewer %} +{ + "image": "/images/guides/pebble-apps/display-animations/submenu.png", + "platforms": [ + { "hw": "basalt", "wrapper": "time-red" }, + { "hw": "chalk", "wrapper": "time-round-red-14" } + ] +} +{% endscreenshot_viewer %} +``` + +The URL to the image gets the hardware platform insert into it, so in order to make +the above example work, you should have two files with the following names: + +``` +/source/assets/images/guides/pebble-apps/display-animations/submenu~basalt.png +/source/assets/images/guides/pebble-apps/display-animations/submenu~chalk.png +``` + +### Alerts + +Some information requires more prominent formatting than standard block notes. +Use the `alert` Liquid tag for this purpose. Both 'notice' (purple) and +'important' (dark red) are supported for appropriate levels of highlighting. +Some examples are below: + +``` +{% alert important %} +PebbleKit JS and PebbleKit Android/iOS may **not** be used in conjunction. +{% endalert %} +``` + +``` +{% alert notice %} +This API is currently in the beta stage, and may be changed before final +release. +{% endalert %} +``` + +### SDK Platform Specific Paragraphs + +On pages that have the SDK Platform choice system, you can tag paragraphs as +being only relevant for CloudPebble or local SDK users. Text, code snippets, +images, and other markdown are all supported. + +First, add `platform_choice: true` to the page YAML metadata. + +Specify platform-specific sections of markdown using the `platform` Liquid tag: + +``` +{% platform local %} +Add the resource to your project in `package.json`. +{% endplatform %} + +{% platform cloudpebble %} +Add the resource to your project by clicking 'Add New' next to 'Resources' in +the project sidebar. +{% endplatform %} +``` + +### Formatting + +The following additional text formatting syntax is supported. + +#### Strikethrough + +``` +This is some ~~terribly bad~~ amazingly good code. +``` + +#### Highlight + +``` +CloudPebble is ==extremely== good. +``` + +#### Tables + +Tables are supported with the +[PHP Markdown syntax](https://michelf.ca/projects/php-markdown/extra/#table). + +``` +| First Header | Second Header | +| ------------- | ------------- | +| Content Cell | Content Cell | +| Content Cell | Content Cell | +``` + +### Emoji + +You can use emoji in your text by using the colon syntax. + +``` +If you're a beginner Pebble developer, you should use :cloud:Pebble +``` + +### Embedded Content + +#### YouTube + +To embed a YouTube video or playlist, use the standard link syntax with EMBED +as the link title. + +``` +You should check out this video on developing Pebble apps: +[EMBED](https://www.youtube.com/watch?v=LU_hPBhgjGQ) +``` + +#### Gist + +To embed a GitHub Gist, use the standard link syntax with EMBED as the link +title. + +``` +Here is the Gist code. +[EMBED](https://gist.github.com/JaviSoto/5405969) +``` diff --git a/devsite/docs/troubleshooting.md b/devsite/docs/troubleshooting.md new file mode 100644 index 00000000..cb44b4ee --- /dev/null +++ b/devsite/docs/troubleshooting.md @@ -0,0 +1,16 @@ +# Troubleshooting + +This page contains fixes to known problems encountered from building the +developer site, and how they were fixed. This may help you if you have +the same problems. + +## Nokogiri + +**Error** + +> An error occurred while installing nokogiri (1.6.7.2), and Bundler cannot continue. +> Make sure that `gem install nokogiri -v '1.6.7.2'` succeeds before bundling. + +**Solution** + +`gem install nokogiri -- --use-system-libraries` diff --git a/devsite/js-docs/pkjs/Pebble.js b/devsite/js-docs/pkjs/Pebble.js new file mode 100644 index 00000000..1e243309 --- /dev/null +++ b/devsite/js-docs/pkjs/Pebble.js @@ -0,0 +1,428 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @namespace Pebble + * + * @desc The Pebble namespace is where all of the Pebble specific methods and + * properties exist. This class contains methods belonging to PebbleKit JS and + * allows bi-directional communication with a C or JavaScript watchapp, as well as managing + * the user's timeline subscriptions, creating AppGlance slices and obtaining + * information about the currently connected watch. + */ +var Pebble = new Object; + + +/** + * @desc Adds a listener for PebbleKit JS events, such as when an ``AppMessage`` is + * received or the configuration view is opened or closed. + * + * #### Event Type Options + * + * Possible values: + * + * * `ready` - The watchapp has been launched and the PebbleKit JS component + * is now ready to receive events. + * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The + * AppMessage ``Dictionary`` is contained in the payload property (i.e: + * `event.payload`). The payload consists of key-value pairs, where the keys + * are strings containing integers (e.g: "0"), or aliases for keys defined + * in package.json (e.g: "KEY_EXAMPLE"). Values should be integers, strings + * or byte arrays (arrays of characters). This event is not available to + * {@link /docs/rockyjs/ Rocky.js} applications, and attempting to register it will throw an exception. + * * `showConfiguration` - The user has requested the app's configuration + * webview to be displayed. This can occur either upon the app's initial + * install or when the user taps 'Settings' in the 'My Pebble' view within + * the phone app. + * * `webviewclosed` - The configuration webview was closed by the user. If + * the webview had a response, it will be contained in the response property + * (i.e: `event.response`). This response can be used to feed back user + * preferences to the watchapp. + * * `message` - Provide a {@link #PostMessageCallback PostMessageCallback} + * as the callback. The message event is emitted every time PebbleKit JS + * receives a {@link #postMessage postMessage} from the {@link /docs/rockyjs/ Rocky.js} + * application. The payload contains a simple JavaScript object. (i.e. `event.data`). + * This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications. + * * `postmessageconnected` - Provide a {@link #PostMessageConnectedCallback PostMessageConnectedCallback} + * as the callback. The event may be emitted immediately upon subscription, + * if the subsystem is already connected. It is also emitted when connectivity is established. + * This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications. + * * `postmessagedisconnected` - Provide a {@link #PostMessageDisconnectedCallback PostMessageDisconnectedCallback} + * as the callback. The event may be emitted immediately upon subscription, + * if the subsystem is already disconnected. It is also emitted when connectivity is lost. + * This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications. + * * `postmessageerror` - Provide a {@link #PostMessageErrorCallback PostMessageErrorCallback} + * as the callback. The event is emitted when a transmission error occurrs. + * Your message has not been delivered. The type of error is not provided. + * This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications. + * + * @param {String} type - The type of the event, from the list described above. + * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback} + * to receive any events of the type specified that occur. +*/ +Pebble.addEventListener = function(type, callback) { }; + +/** + * @desc Attaches an event handler to the specified events. Synonymous with + [Pebble.addEventListener()](#addEventListener). Only applicable to + {@link /docs/rockyjs/ Rocky.js} applications. + * + * `Pebble.on(type, callback);` + * + * @param {String} type - The type of the event, from the list described above. + * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback} + * to receive any events of the type specified that occur. + */ +Pebble.on = function(type, callback) { }; + +/** + * @desc Remove an existing event listener previously registered with + * [Pebble.addEventListener()](#addEventListener) or [Pebble.on()](#on). + * + * @param {String} type - The type of the event listener to be removed. See + * [Pebble.addEventListener()](#addEventListener) for a list of available event types. + * @param {Function} callback - The existing developer-defined function that was + * previously registered. + */ +Pebble.removeEventListener = function(type, callback) { }; + +/** + * @desc Remove an existing event handler from the specified events. Synonymous + * with [Pebble.removeEventListener()](#removeEventListener). Only applicable to + * {@link /docs/rockyjs/ Rocky.js} applications. + * + * `Pebble.off(type, callback);` + * + * @param {String} type - The type of the event listener to be removed. See + * [Pebble.addEventListener()](#addEventListener) for a list of available types. + * @param {Function} callback - The existing developer-defined function that was + * previously registered. + */ +Pebble.off = function(type, callback) { }; + +/** + * @desc Show a simple modal notification on the connected watch. + * + * @param {String} title - The title of the notification + * @param {String} body - The main content of the notification + */ +Pebble.showSimpleNotificationOnPebble = function(title, body) { }; + +/** + * @desc Send an AppMessage to the app running on the watch. Messages should be + * in the form of JSON objects containing key-value pairs. See + * Pebble.sendAppMessage() for valid key and value data types. + * `Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };` + * Please note that `sendAppMessage` is `undefined` in + * {@link /docs/rockyjs/ Rocky.js} applications, see {@link #postMessage postMessage} instead. + * + * @returns {Number} The transaction id for this message + * + * @param {Object} data - A JSON object containing key-value pairs to send to + * the watch. Values in arrays that are greater then 255 will be mod 255 + * before sending. + * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback} + * callback to run if the watch acknowledges (ACK) this message. + * @param {AppMessageOnFailure} onFailure - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback} + * callback to run if the watch does NOT acknowledge (NACK) this message. + */ +Pebble.sendAppMessage = function(data, onSuccess, onFailure) { }; + +/** + * @desc Sends a message to the {@link /docs/rockyjs/ Rocky.js} component. Please be aware + * that messages should be kept concise. Each message is queued, so + * `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued + * messages may still be delivered, or automatically removed from the queue + * after a few seconds of failed connectivity. Any transmission failures, or + * out of memory errors will be raised via the `postmessageerror` event. + * + * `Pebble.postMessage({temperature: 30, conditions: 'Sunny'});` + * + * @param {Object} data - A {@link #PostMessageCallback PostMessageCallback} containing + * the data to deliver to the watch. + * This will be received in the `data` field of the `type` delivered to + * the `on('message', ...)` handler. + */ +Pebble.postMessage = function(data) { }; + +/** + * @desc Get the user's timeline token for this app. This is a string and is + * unique per user per app. Note: In order for timeline tokens to be + * available, the app must be submitted to the Pebble appstore, but does not + * need to be public. Read more in the + * {@link /guides/pebble-timeline/timeline-js/ timeline guides}. + * + * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback} + * callback to handle a successful attempt to get the timeline token. + * @param {Function} onFailure - A developer-defined callback to handle a + * failed attempt to get the timeline token. + */ + Pebble.getTimelineToken = function(onSuccess, onFailure) { }; + +/** + * @desc Subscribe the user to a timeline topic for your app. This can be used + * to filter the different pins a user could receive according to their + * preferences, as well as maintain groups of users. + * + * @param {String} topic - The desired topic to be subscribed to. Users will + * receive all pins pushed to this topic. + * @param {Function} onSuccess - A developer-defined callback to handle a + * successful subscription attempt. + * @param {Function} onFailure - A developer-defined callback to handle a + * failed subscription attempt. + */ +Pebble.timelineSubscribe = function(topic, onSuccess, onFailure) { }; + +/** + * @desc Unsubscribe the user from a timeline topic for your app. Once + * unsubscribed, the user will no longer receive any pins pushed to this + * topic. + * + * @param {String} topic - The desired topic to be unsubscribed from. + * @param {Function} onSuccess - A developer-defined callback to handle a + * successful unsubscription attempt. + * @param {Function} onFailure - A developer-defined callback to handle a + * failed unsubscription attempt. + */ +Pebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { }; + +/** + * @desc Obtain a list of topics that the user is currently subscribed to. The + * length of the list should be checked to determine whether the user is + * subscribed to at least one topic. + * + * `Pebble.timelineSubscriptions(function(topics) { console.log(topics); }, function() { console.log('error'); } );` + * + * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the + * retuned list of topic strings. + * @param {Function} onFailure - The developer-defined function to gracefully + * handle any errors in obtaining the user's subscriptions. + */ +Pebble.timelineSubscriptions = function(onSuccess, onFailure) { }; + + +/** + * @desc Obtain an object containing information on the currently connected + * Pebble smartwatch. + * + * **Note:** This function is only available when using the Pebble Time + * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information} + * for details on how to use this function. + * + * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the + * currently connected Pebble watch. + */ +Pebble.getActiveWatchInfo = function() { }; + +/** + * @desc Returns a unique account token that is associated with the Pebble + * account of the current user. + * + * **Note:** The behavior of this changed slightly in SDK 3.0. Read the + * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the + * details and how to adapt older tokens. + * + * @returns {String} A string that is guaranteed to be identical across devices + * if the user owns several Pebble or several mobile devices. From the + * developer's perspective, the account token of a user is identical across + * platforms and across all the developer's watchapps. If the user is not + * logged in, this function will return an empty string (''). + */ + +Pebble.getAccountToken = function() { }; + +/** + * @desc Returns a a unique token that can be used to identify a Pebble device. + * + * @returns {String} A string that is is guaranteed to be identical for each + * Pebble device for the same app across different mobile devices. The token + * is unique to your app and cannot be used to track Pebble devices across + * applications. + */ +Pebble.getWatchToken = function() { }; + + +/** + * @desc Triggers a reload of the app glance which first clears any existing + * slices and then adds the provided slices. + * + * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} + * JSON objects to add to the app glance. + * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined + * callback which is called if the reload operation succeeds. + * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined + * callback which is called if the reload operation fails. + */ +Pebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { }; + +/** + * @desc When an app is marked as configurable, the PebbleKit JS component must + * implement `Pebble.openURL()` in the `showConfiguration` event handler. The + * Pebble mobile app will launch the supplied URL to allow the user to configure + * the watchapp or watchface. See the + * {@link /guides/user-interfaces/app-configuration-static/ App Configuration guide}. + * + * @param {String} url - The URL of the static configuration page. + */ +Pebble.openURL = function(url) { }; + +/** + * @typedef {Function} AppGlanceReloadSuccessCallback + * @memberof Pebble + * + * @desc Called when AppGlanceReload is successful. + * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object + * containing the app glance slices. + */ + + /** + * @typedef {Function} AppGlanceReloadFailureCallback + * @memberof Pebble + * + * @desc Called when AppGlanceReload has failed. + * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object + * containing the app glance slices. + */ + +/** + * @typedef {Function} AppMessageAckCallback + * @memberof Pebble + * + * @desc Called when an AppMessage is acknowledged by the watch. + * @param {Object} data - An object containing the callback data. This contains + * the `transactionId` which is the transaction ID of the message. + */ + +/** + * @typedef {Function} AppMessageNackCallback + * @memberof Pebble + * + * @desc Called when an AppMessage is not acknowledged by the watch. + * @param {Object} data - An object containing the callback data. This contains + * the `transactionId` which is the transaction ID of the message + * @param {String} error - The error message + */ + +/** + * @typedef {Function} EventCallback + * @memberof Pebble + * + * @desc Called when an event of any type previously registered occurs. The + * parameters are different depending on the type of event, shown in + * brackets for each parameter listed here. + * @param {Object} event - An object containing the event information, including: + * * `type` - The type of event fired, from the list in the description of [Pebble.addEventListener()](#addEventListener). + * * `payload` - The dictionary sent over ``AppMessage`` consisting of + * key-value pairs. *This field only exists for `appmessage` events.* + * * `response` - The contents of the URL navigated to when the + * configuration page was closed, after the anchor. This may be encoded, + * which will require use of decodeURIComponent() before reading as an + * object. *This field only exists for for `webviewclosed` events.* + */ + +/** + * @typedef {Function} TimelineTokenCallback + * @memberof Pebble + * + * @desc Called when the user's timeline token is available. + * @param {String} token - The user's token. + */ + +/** + * @typedef {Function} TimelineTopicsCallback + * @memberof Pebble + * + * @desc Called when the user's list of subscriptions is available for processing by the developer. + * @param {[String]} List of topic strings that the user is subscribed to + */ + +/** + * @typedef {Function} PostMessageCallback + * @memberof Pebble + * + * @desc The callback function signature to be used with the `message` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + * * `data` - The data sent within the message. + */ + +/** + * @typedef {Function} PostMessageErrorCallback + * @memberof Pebble + * + * @desc The callback function signature to be used with the `postmessageerror` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + * * `data` - The data failed to send within the message. + */ + +/** + * @typedef {Function} PostMessageConnectedCallback + * @memberof Pebble + * + * @desc The callback function signature to be used with the `postmessageconnected` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + */ + +/** + * @typedef {Function} PostMessageDisconnectedCallback + * @memberof Pebble + * + * @desc The callback function signature to be used with the `postmessagedisconnected` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + */ + + +/** + * @typedef {Object} WatchInfo + * @memberof Pebble + * + * @desc Provides information about the connected Pebble smartwatch. + * + * @property {String} platform - The hardware platform, such as `basalt` or `emery`. + * @property {String} model - The watch model, such as `pebble_black` + * @property {String} language - The user's currently selected language on + * this watch. + * @property {Object} firmware - An object containing information about the + * watch's firmware version, including: + * * `major` - The major version + * * `minor` - The minor version + * * `patch` - The patch version + * * `suffix` - Any additional version information, such as `beta3` +*/ + +/** + * @typedef {Object} AppGlanceSlice + * @memberof Pebble + * + * @desc The structure of an app glance. + * + * @property {String} expirationTime - Optional ISO date-time string of when + the entry should expire and no longer be shown in the app glance. + * @property {Object} layout - An object containing: + * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK. + * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance. +*/ diff --git a/devsite/js-docs/rocky/CanvasRenderingContext2D.js b/devsite/js-docs/rocky/CanvasRenderingContext2D.js new file mode 100644 index 00000000..268aeb4f --- /dev/null +++ b/devsite/js-docs/rocky/CanvasRenderingContext2D.js @@ -0,0 +1,377 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** +* @namespace CanvasRenderingContext2D +* @desc The CanvasRenderingContext2D interface is used for drawing +* rectangles, text, images and other objects onto the canvas element. It +* provides the 2D rendering context for the drawing on the device's display. +* +* The canvas uses a standard x and y +* {@link https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes coordinate system}. +* +* The `CanvasRenderingContext2D` object is obtained as a parameter in the +* {@link /docs/rockyjs/rocky#on rocky.on('draw', ...)} event. +* +* `rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
});` +* +* The state of pixels is maintained between each draw, so developers are +* responsible for clearing an area, before drawing again. +* +* Please note that this API is still in development and there may be some +* limitations, which are documented below. We will also be adding support +* for more common APIs in future releases. +*/ +var CanvasRenderingContext2D = { + /** + * @typedef {Object} TextMetrics + * @desc The TextMetrics interface represents the dimensions of a text + * in the canvas (display), as created by {@link #measureText measureText}. + * + * @property {Number} width - Calculated width of text in pixels. + * @property {Number} height - Calculated height of text in pixels. + */ + + /** + * @typedef {Object} Canvas + * @desc Provides information about the device's canvas (display). This is not + * actually a DOM element, it is provided for standards compliance only. + * + * `rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
  var h = ctx.canvas.unobstructedHeight;
});` + * + * @property {Number} clientWidth - The full width of the canvas. + * @property {Number} clientHeight - The full height of the canvas. + * @property {Number} unobstructedWidth - The width of the canvas that is not + * obstructed by system overlays (Timeline Quick View). + * @property {Number} unobstructedHeight - The height of the canvas that is + * not obstructed by system overlays (Timeline Quick View). + */ + + /** + * @desc Specifies the color to use inside shapes. The default is + * `#000` (black). + * + * #### Options + * + * Possible values: + * + * * Most (but not all) CSS color names. e.g. `blanchedalmond` + * * Pebble color names. e.g. `shockingpink` + * * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF` + * + * Please note that we currently only support solid colors. You may specifiy + * `transparent` or `clear` for transparency, but we do do not support + * partial transparency or the `#RRGGBBAA` notation yet. + * + * `ctx.fillStyle = 'white';` + * + */ + fillStyle, + + /** + * @desc Specifies the color to use for lines around shapes. The + * default is `#000` (black). + * + * #### Options + * + * Possible values: + * + * * Most (but not all) CSS color names. e.g. `blanchedalmond` + * * Pebble color names. e.g. `shockingpink` + * * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF` + * + * Please note that we currently only support solid colors. You may specifiy + * `transparent` or `clear` for transparency, but we do do not support + * partial transparency or the `#RRGGBBAA` notation yet. + * + * `ctx.strokeStyle = 'red';` + * + */ + strokeStyle, + + /** + * @desc A {@link #Canvas Canvas} object containing information about + * the system's canvas (display). + */ + canvas, + + /** + * @desc The width of lines drawn (to the nearest integer) with the + * context (`1.0` by default). + * + * `ctx.lineWidth = 8;` + * + */ + lineWidth, + + /** + * @desc Specifies the current text style being used when drawing text. + * Although this string uses the same syntax as a CSS font specifier, you + * cannot specifiy arbitrary values and you must only use one of the values below. + * + * The default font is `14px bold Gothic`. + * + * `ctx.font = '28px bold Droid-serif';` + * + * #### Options + * + * Possible values: + * + * * `18px bold Gothic` + * * `14px Gothic` + * * `14px bold Gothic` + * * `18px Gothic` + * * `24px Gothic` + * * `24px bold Gothic` + * * `28px Gothic` + * * `28px bold Gothic` + * * `30px bolder Bitham` + * * `42px bold Bitham` + * * `42px light Bitham` + * * `42px Bitham-numeric` + * * `34px Bitham-numeric` + * * `21px Roboto` + * * `49px Roboto-subset` + * * `28px bold Droid-serif` + * * `20px bold Leco-numbers` + * * `26px bold Leco-numbers-am-pm` + * * `32px bold numbers Leco-numbers` + * * `36px bold numbers Leco-numbers` + * * `38px bold numbers Leco-numbers` + * * `42px bold numbers Leco-numbers` + * * `28px light numbers Leco-numbers` + */ + font, + + /** + * @desc Specifies the current text alignment being used when drawing + * text. Beware that the alignment is based on the x-axis coordinate value of + * the {@link #fillText CanvasRenderingContext2D.fillText} method. + * + * `ctx.textAlign = 'center';` + * + * #### Options + * + * Possible values: + * + * * `left` - The text is left-aligned + * * `right` - The text is right-aligned + * * `center` - The text is center-aligned + * * `start` (default) - The text is aligned left, unless using a + * right-to-left language. Currently only left-to-right is supported. + * * `end` - The text is aligned right, unless using a right-to-left + * language. Currently only left-to-right is supported. + */ + textAlign +}; + +/** +* @desc Sets all pixels in the rectangle at (`x`,`y`) with size +* (`width`, `height`) to black, erasing any previously drawn content. +* +* `ctx.clearRect(0, 0, 144, 168);` +* +* @param {Number} x - The x-axis coordinate of the rectangle's starting point +* @param {Number} y - The y-axis coordinate of the rectangle's starting point +* @param {Number} width - The rectangle's width +* @param {Number} height - The rectangle's height +*/ +CanvasRenderingContext2D.clearRect = function(x, y, width, height) { }; + +/** +* @desc Draws a filled rectangle at (`x`,`y`) with size (`width`, `height`), +* using the current fill style. +* +* `ctx.fillRect(0, 30, 144, 30);` +* +* @param {Number} x - The x-axis coordinate of the rectangle's starting point +* @param {Number} y - The y-axis coordinate of the rectangle's starting point +* @param {Number} width - The rectangle's width +* @param {Number} height - The rectangle's height +*/ +CanvasRenderingContext2D.fillRect = function(x, y, width, height) { }; + +/** +* @desc Paints a rectangle at (`x`,`y`) with size (`width`, `height`), +* using the current stroke style. +* +* `ctx.strokeRect(0, 30, 144, 30);` +* +* @param {Number} x - The x-axis coordinate of the rectangle's starting point +* @param {Number} y - The y-axis coordinate of the rectangle's starting point +* @param {Number} width - The rectangle's width +* @param {Number} height - The rectangle's height +*/ +CanvasRenderingContext2D.strokeRect = function(x, y, width, height) { }; + +/** +* @desc Draws (fills) `text` at the given (`x`,`y`) position. +* +* `ctx.fillText('Hello World', 0, 30, 144);` +* +* @param {String} text - The text to draw +* @param {Number} x - The x-axis coordinate of the text's starting point +* @param {Number} y - The y-axis coordinate of the text's starting point +* @param {Number} maxWidth - (Optional) Maximum width to draw. If specified, +* and the string is wider than the width, the font is adjusted to use a +* smaller font. +*/ +CanvasRenderingContext2D.fillText = function(text, x, y, maxWidth) { }; + +/** +* @desc Returns a {@link #TextMetrics TextMetrics} object containing +* information about `text`. +* +* `var dimensions = ctx.measureText('Hello World');` +* +* @returns {TextMetrics} - A ``TextMetrics`` object with information about +* the measured text +* +* @param {String} text - The text to measure +*/ +CanvasRenderingContext2D.measureText = function(text) { }; + +/** +* @desc Starts a new path by emptying the list of sub-paths. Call this +* method when you want to create a new path. +* +* `ctx.beginPath();` +*/ +CanvasRenderingContext2D.beginPath = function() { }; + +/** +* @desc Causes the point of the pen to move back to the start of the +* current sub-path. It tries to add a straight line (but does not +* actually draw it) from the current point to the start. If the shape has +* already been closed or has only one point, this function does nothing. +* +* `ctx.closePath();` +*/ +CanvasRenderingContext2D.closePath = function() { }; + +/** +* @desc Moves the starting point of a new sub-path to the (`x`,`y`) +* coordinates. +* +* `ctx.moveTo(10, 20);` +* +* @param {Number} x - The destination point on the x-axis +* @param {Number} y - The destination point on the y-axis +*/ +CanvasRenderingContext2D.moveTo = function(x, y) { }; + +/** +* @desc Connects the last point of the sub-path to the (`x`,`y`) +* coordinates with a straight line. +* +* `ctx.lineTo(10, 20);` +* +* @param {Number} x - The destination point on the x-axis +* @param {Number} y - The destination point on the y-axis +*/ +CanvasRenderingContext2D.lineTo = function(x, y) { }; + +/** +* @desc Adds an arc to the path which is centered at (`x`,`y`) +* position with radius `r` starting at `startAngle` and ending at +* `endAngle` going in the direction determined by the `anticlockwise` +* parameter (defaulting to clockwise). +* +* If `startAngle` > `endAngle` nothing will be drawn, and if the difference +* between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn. +* +* `// Draw a full circle outline
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.arc(72, 84, 40, 0, 2 * Math.PI, false);
ctx.stroke();` +* +* Please note this function does not work with `.fill`, you must use +* {@link #rockyFillRadial CanvasRenderingContext2D.rockyFillRadial} instead. +* +* @param {Number} x - The x-axis coordinate of the arc's center +* @param {Number} y - The y-axis coordinate of the arc's center +* @param {Number} r - The radius of the arc +* @param {Number} startAngle - The angle at which the arc starts, measured +* clockwise from the positive x axis and expressed in radians. +* @param {Number} endAngle - The angle at which the arc ends, measured +* clockwise from the positive x axis and expressed in radians. +* @param {Bool} [anticlockwise] - (Optional) `Boolean` which, if `true`, +* causes the arc to be drawn counter-clockwise between the two angles +* (`false` by default) +*/ +CanvasRenderingContext2D.arc = function(x, y, r, startAngle, endAngle, anticlockwise) { }; + +/** +* @desc Creates a path for a rectangle at position (`x`,`y`) with a +* size that is determined by `width` and `height`. Those four points are +* connected by straight lines and the sub-path is marked as closed, so +* that you can fill or stroke this rectangle. +* +* `ctx.rect(0, 30, 144, 50);` +* +* @param {Number} x - The x-axis coordinate of the rectangle's starting point +* @param {Number} y - The y-axis coordinate of the rectangle's starting point +* @param {Number} width - The rectangle's width +* @param {Number} height - The rectangle's height +*/ +CanvasRenderingContext2D.rect = function(x, y, width, height) { }; + +/** +* @desc Fills the current path with the current {@link #fillStyle fillStyle}. +* +* `ctx.fill();` +*/ +CanvasRenderingContext2D.fill = function() { }; + +/** +* @desc Strokes the current path with the current {@link #strokeStyle strokeStyle}. +* +* `ctx.stroke();` +*/ +CanvasRenderingContext2D.stroke = function() { }; + +/** +* @desc Saves the entire state of the canvas by pushing the current +* state onto a stack. +* +* `ctx.save();` +*/ +CanvasRenderingContext2D.save = function() { }; + +/** +* @desc Restores the most recently saved canvas state by popping the +* top entry in the drawing state stack. If there is no saved state, this +* method does nothing. +* +* `ctx.restore();` +*/ +CanvasRenderingContext2D.restore = function() { }; + +/** +* @desc Fills a circle clockwise between startAngle and endAngle where +* 0 is the start of the circle beginning at the 3 o'clock position on a +* watchface. +* +* If `startAngle` > `endAngle` nothing will be drawn, and if the difference +* between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn. +* +* `// Draw a filled circle
ctx.fillStyle = 'white';
ctx.rockyFillRadial(72, 84, 0, 30, 0, 2 * Math.PI);` +* +* @param {Number} x - The x-axis coordinate of the radial's center point +* @param {Number} y - The y-axis coordinate of the radial's center point +* @param {Number} innerRadius - The inner radius of the circle. Use 0 for a full circle +* @param {Number} outerRadius - The outer radius of the circle +* @param {Number} startAngle - Radial starting angle +* @param {Number} endAngle - Radial finishing angle. If smaller than `startAngle` nothing is drawn +*/ +CanvasRenderingContext2D.rockyFillRadial = function(x, y, innerRadius, outerRadius, startAngle, endAngle) { }; diff --git a/devsite/js-docs/rocky/Console.js b/devsite/js-docs/rocky/Console.js new file mode 100644 index 00000000..ae7cc0fe --- /dev/null +++ b/devsite/js-docs/rocky/Console.js @@ -0,0 +1,64 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @namespace console + * @desc This provides an interface to the app's debugging console. + * + * If you're using {@link https://cloudpebble.net CloudPebble}, these logs + * will appear when you press 'View Logs' after launching your application. + * + * If you're using the local SDK, you can use the `$ pebble logs` command or: + * + * `$ pebble install --emulator basalt --logs` + * + * You can find out more about logging in our + * {@link /guides/debugging/debugging-with-app-logs/ Debugging with App Logs} guide. + */ +var console = new Object(); + +/** + * @desc Outputs a message to the app's debugging console. + * + * `console.log(rocky.watchInfo.platform);` + * + * @param {...Object} obj - One or more JavaScript objects to output. The string + * representations of each of these objects are appended together in the order + * listed and output. + */ +console.log = function (obj) { }; + +/** + * @desc Outputs a warning message to the app's debugging console. + * + * `console.warn('Something seems wrong');` + * + * @param {...Object} obj - One or more JavaScript objects to output. The string + * representations of each of these objects are appended together in the order + * listed and output. + */ +console.warn = function (obj) { }; + +/** + * @desc Outputs an error message to the app's debugging console. + * + * `console.error(JSON.stringify(obj));` + * + * @param {...Object} obj - One or more JavaScript objects to output. The string + * representations of each of these objects are appended together in the order + * listed and output. + */ +console.error = function (obj) { }; diff --git a/devsite/js-docs/rocky/Date.js b/devsite/js-docs/rocky/Date.js new file mode 100644 index 00000000..dfb714af --- /dev/null +++ b/devsite/js-docs/rocky/Date.js @@ -0,0 +1,121 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @namespace Date + * + * @desc Creates a JavaScript Date instance that represents a single moment in + * time. Date objects are based on a time value that is the number of + * milliseconds since 1 January, 1970 UTC. + * + * `var d = new Date();` + * + * We fully implement standard JavaScript `Date` functions, such as: `getDay()`, `getHours()` etc. + * + * For full `Date` documentation see: + * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date} + * + * ### Locale Date Methods + * + * The locale date methods ({@link #toLocaleString toLocaleString}, + * {@link #toLocaleTimeString toLocaleTimeString} and + * {@link #toLocaleDateString toLocaleDateString}) are currently limited in + * their initial implementation. + * + * Available **options**: + * + * **hour12** + * + * Use 12-hour time (as opposed to 24-hour time). Possible values are `true` and `false`; the default is locale dependent (Pebble setting). + * + * **weekday** + * + * The representation of the weekday. Possible values are "narrow", "short", "long". + * + * **year** + * + * The representation of the year. Possible values are "numeric", "2-digit". + * + * **month** + * + * The representation of the month. Possible values are "numeric", "2-digit", "narrow", "short", "long". + * + * **day** + * + * The representation of the day. Possible values are "numeric", "2-digit". + * + * **hour** + * + * The representation of the hour. Possible values are "numeric", "2-digit". + * + * **minute** + * + * The representation of the minute. Possible values are "numeric", "2-digit". + * + * **second** + * + * The representation of the second. Possible values are "numeric", "2-digit". + * + * + * Please note that locale based date and time functions have the following + * limitations at this time: + * + * * You cannot manually specify a locale, it's automatically based upon the + * current device settings. Locale is optional, or you can specify `undefined`. + * + * `console.log(d.toLocaleDateString());` + * + * * Only a single date/time value can be requested in each method call. Do + * NOT request multiple options. e.g. `{hour: 'numeric', minute: '2-digit'}` + * + * `console.log(d.toLocaleTimeString(undefined, {hour: 'numeric'}));` + */ +var Date = new Object(); + +/** + * @desc This method returns a string with a language sensitive representation + * of this date object. + * + * `d.toLocaleString();` + * + * @param {String} locale - (Optional) The name of the locale. + * @param {Object} options - (Optional) Only a single option is currently supported. + */ +Date.toLocaleString = function(locale, options) { }; + +/** + * @desc This method returns a string with a language sensitive representation + * of the date portion of this date object. + * + * `d.toLocaleTimeString(undefined, {hour: 'numeric'});` + * + * @param {String} locale - (Optional) The name of the locale. + * @param {Object} options - (Optional) Only a single option is currently supported. + */ +Date.toLocaleTimeString = function(locale, options) { }; + +/** + * @desc This method returns a string with a language sensitive representation + * of the time portion of this date object. + * + * `d.toLocaleDateString(undefined, {weekday: 'long'});` + * + * @param {String} locale - (Optional) The name of the locale. + * @param {Object} options - (Optional) Only a single option is currently supported. + */ +Date.toLocaleDateString = function() { }; + + diff --git a/devsite/js-docs/rocky/global.js b/devsite/js-docs/rocky/global.js new file mode 100644 index 00000000..d6a2d154 --- /dev/null +++ b/devsite/js-docs/rocky/global.js @@ -0,0 +1,58 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Global Functions, Members, and Typedefs + +/** + * @desc Calls a function after a specified delay. + * + * `var timeoutId = setTimeout(function(...){}, 10000);` + * + * @returns {Number} timeoutId - The ID of the timeout + * @param {Function} fct - The function to execute + * @param {Number} delay - The delay (in ms) + */ +setTimeout = function(fct, delay) { }; + +/** + * @desc Clears the delay set by ``setTimeout``. + * + * `clearTimeout(timeoutId);` + * + * @param {Number} timeoutId - The ID of the timeout you wish to clear. + */ +clearTimeout = function(timeoutId) { }; + +/** + * @desc Repeatedly calls a function, with a fixed time delay between + * each call. + * + * `var intervalId = setInterval(function(...){}, 10000);` + * + * @returns {Number} intervalId - The ID of the interval + * @param {Function} fct - The function to execute + * @param {Number} delay - The delay (in ms) + */ +setInterval = function(fct, delay) { }; + +/** + * @desc Clears the interval set by ``setInterval``. + * + * `clearInterval(intervalId);` + * + * @param {Number} intervalId - The ID of the interval you wish to clear + */ +clearInterval = function(intervalId) { }; diff --git a/devsite/js-docs/rocky/rocky.js b/devsite/js-docs/rocky/rocky.js new file mode 100644 index 00000000..f5f13a96 --- /dev/null +++ b/devsite/js-docs/rocky/rocky.js @@ -0,0 +1,262 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @namespace rocky + * + * @desc Provides an interface for interacting with application context and + * events. Developers can access the Rocky object with the following line of + * code: + * + * `var rocky = require('rocky');` + * + */ +var rocky = { + /** + * @typedef {Object} WatchInfo + * @desc Provides information about the currently connected Pebble smartwatch. + * + * @property {String} model - The name of the Pebble model. (e.g. pebble_time_round_silver_20mm) + * @property {String} platform - The name of the Pebble platform. (e.g. basalt) + * @property {String} language - Not available yet. + * @property {String} firmware - An object with the following fields: + * * `major` - The major version of the smartwatch's firmware. + * * `minor` - The minor version of the smartwatch's firmware. + * * `patch` - The patch version of the smartwatch's firmware. + * * `suffix` - The suffix of the smartwatch's firmware. (e.g. beta3) + */ + + /** + * @typedef {Object} UserPreferences + * @desc Provides access to user related settings from the currently connected Pebble smartwatch. + * The size itself will vary between platforms, see the + * {@link /guides/user-interfaces/content-size/ ContentSize guide} for more information. + * + * @property {String} contentSize - Pebble > System > Notifications > Text Size: + * * `small` - Not available on Emery. + * * `medium` - The default setting. + * * `large` - The default setting on Emery. + * * `x-large` - Only available on Emery. + */ + + /** + * @typedef {Function} RockyDrawCallback + * @desc The callback function signature to be used with the draw {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `context` - A {@link /docs/rockyjs/CanvasRenderingContext2D CanvasRenderingContext2D} + * object that can be used to draw information on the disply. + */ + + /** + * @typedef {Function} RockyTickCallback + * @desc The callback function signature to be used with the `secondchange`, + * `minutechange`, `hourchange` and `daychange` {@link #on events}. + * + * In addition to firing these tick events at the appropriate time change, + * they are also emitted when the application starts. + * + * @param {Object} event - An object containing information about the event: + * * `date` - A JavaScript + * {@link http://www.w3schools.com/jsref/jsref_obj_date.asp date} object + * representing the current time. + */ + + /** + * @typedef {Function} RockyMessageCallback + * @desc The callback function signature to be used with the `message` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + * * `data` - The data sent within the message. + */ + + /** + * @typedef {Function} RockyPostMessageErrorCallback + * @desc The callback function signature to be used with the `postmessageerror` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + * * `data` - The data failed to send within the message. + */ + + /** + * @typedef {Function} RockyPostMessageConnectedCallback + * @desc The callback function signature to be used with the `postmessageconnected` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + */ + + /** + * @typedef {Function} RockyPostMessageDisconnectedCallback + * @desc The callback function signature to be used with the `postmessagedisconnected` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `type` - The type of event which was triggered. + */ + + /** + * @typedef {Function} RockyMemoryPressureCallback + * @desc The callback function signature to be used with the `memorypressure` + * {@link #on event}. + * + * @param {Object} event - An object containing information about the event: + * * `level` (String) - The current level of memory pressure. + * + * * `high` - This is a critical level, indicating that the application will + * be terminated if memory isn't immediately free'd. + * + * Important Notes: + * - Avoid creating any new objects/arrays/strings when this level is raised. + * - Drop object properties you don't need using the `delete` operator or by assigning `undefined` to it. + * - Don't use the `in` operator in the handler for large objects/arrays. Avoid `for (var x in y)` due to memory constraints. + * - Array has large memory requirements for certain operations/methods. Avoid `Array.pop()`, `Array.slice` and length assignment `Array.length = 123`, on large arrays. + * + * * `normal` - Not yet implemented. + * + * * `low` - Not yet implemented. + */ + + /** + * @desc A {@link #WatchInfo WatchInfo} object containing information about the + * connected Pebble smartwatch. + * + * `console.log(rocky.watchInfo.model);
> pebble_2_hr_lime` + * + */ + watchInfo, + + /** + * @desc A {@link #UserPreferences UserPreferences} object access to user related settings from the currently connected Pebble smartwatch. + * + * `console.log(rocky.userPreferences.contentSize);
> medium` + * + */ + userPreferences +}; + +/** + * @desc Attaches an event handler to the specified events. You may subscribe + * with multiple handlers, but at present there is no way to unsubscribe. + * + * `rocky.on('minutechange', function() {...});` + * + * #### Event Type Options + * + * Possible values: + * + * * `draw` - Provide a {@link #RockyDrawCallback RockyDrawCallback} as the + * callback. The draw event is being emitted after each call to + * {@link #requestDraw requestDraw} but at most once for each screen update, + * even if {@link #requestDraw requestDraw} is called frequently the 'draw' + * event might also fire at other meaningful times (e.g. upon launch). + * * `secondchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the + * callback. The secondchange event is emitted every time the clock's second changes. + * * `minutechange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the + * callback. The minutechange event is emitted every time the clock's minute changes. + * * `hourchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the + * callback. The hourchange event is emitted every time the clock's hour changes. + * * `daychange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the + * callback. The daychange event is emitted every time the clock's day changes. + * * `memorypressure` - Provides a {@link #RockyMemoryPressureCallback RockyMemoryPressureCallback}. The + * event is emitted every time there is a notable change in available system memory. + * You can see an example implementation of memory pressure handling {@link https://github.com/pebble-examples/rocky-memorypressure here}. + * * `message` - Provide a {@link #RockyMessageCallback RockyMessageCallback} + * as the callback. The message event is emitted every time the application + * receives a {@link #postMessage postMessage} from the mobile companion. + * * `postmessageconnected` - Provide a {@link #RockyPostMessageConnectedCallback RockyPostMessageConnectedCallback} + * as the callback. The event may be emitted immediately upon subscription, + * if the subsystem is already connected. It is also emitted when connectivity is established. + * * `postmessagedisconnected` - Provide a {@link #RockyPostMessageDisconnectedCallback RockyPostMessageDisconnectedCallback} + * as the callback. The event may be emitted immediately upon subscription, + * if the subsystem is already disconnected. It is also emitted when connectivity is lost. + * * `postmessageerror` - Provide a {@link #RockyPostMessageErrorCallback RockyPostMessageErrorCallback} + * as the callback. The event is emitted when a transmission error occurrs. The type + * of error is not provided, but the message has not been delivered. + * + * @param {String} type - The event being subscribed to. + * @param {Function} callback - A callback function that will be executed when + * the event occurs. See below for more details. + * + */ +rocky.on = function(type, callback) { }; + +/** + * @desc Attaches an event handler to the specified events. Synonymous with + * [rocky.on()](#on). + * + * `rocky.addEventListener(type, callback);` + * + * @param {String} type - The event being subscribed to. + * @param {Function} callback - A callback function that will be executed when + * the event occurs. See below for more details. + */ +rocky.addEventListener = function(type, callback) { }; + +/** + * @desc Remove an existing event listener previously registered with + * [rocky.on()](#on) or [rocky.addEventListener()](#addEventListener). + * + * @param {String} type - The type of the event listener to be removed. See + * [rocky.on()](#on) for a list of available event types. + * @param {Function} callback - The existing developer-defined function that was + * previously registered. + */ +rocky.removeEventListener = function(type, callback) { }; + +/** + * @desc Remove an existing event handler from the specified events. Synonymous + * with [rocky.removeEventListener()](#removeEventListener). + * + * `rocky.off(type, callback);` + * + * @param {String} type - The type of the event listener to be removed. See + * [rocky.on()](#on) for a list of available event types. + * @param {Function} callback - The existing developer-defined function that was + * previously registered. + */ +rocky.off = function(type, callback) { }; + +/** + * @desc Sends a message to the mobile companion component. Please be aware + * that messages should be kept concise. Each message is queued, so + * `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued + * messages may still be delivered, or automatically removed from the queue + * after a few seconds of failed connectivity. Any transmission failures, or + * out of memory errors will be raised via the `postmessageerror` event. + * + * `rocky.postMessage({cmd: 'fetch'});` + * + * @param {Object} data - An object containing the data to deliver to the mobile + * device. This will be received in the `data` field of the `event` + * delivered to the `on('message', ...)` handler. + */ +rocky.postMessage = function(data) { }; + +/** + * @desc Flags the canvas (display) as requiring a redraw. Invoking this method + * will cause the {@link #on draw event} to be emitted. Only 1 draw event + * will occur, regardless of how many times the redraw is requested before + * the next draw event. + * + * `rocky.on('secondchange', function(e) {
  rocky.requestDraw();
});` + */ +rocky.requestDraw = function() { }; diff --git a/devsite/lib/c_docs/doc_class.rb b/devsite/lib/c_docs/doc_class.rb new file mode 100644 index 00000000..cc3ae747 --- /dev/null +++ b/devsite/lib/c_docs/doc_class.rb @@ -0,0 +1,153 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + # DocClass is a special type of DocElement for structs and unions. + # It acts like a DocGroup in that it parses an XML file, but it acts like a + # DocMember in that it belongs to a group etc. + class DocClass < DocElement + attr_reader :summary, :description, :kind, :position, :id, :name + + def initialize(root, dir, platform, kind, id, group) + super(root, platform) + @dir = dir + @group = group + @kind = kind + @children = [] + @xml = {} + @code = id + @doxygen_processor = DoxygenProcessor.new(platform) + load_xml(platform) + end + + def load_xml(platform) + @xml[platform] = Nokogiri::XML(File.read("#{@dir}/#{platform}/xml/#{@kind}_#{@code}.xml")) + @data[platform] = {} + @name = @xml[platform].at_css('compounddef > compoundname').content.to_s + @id = @xml[platform].at_css('compounddef')['id'] + @path = "#{@group.path}##{@name}" + create_members(platform) + end + + def to_liquid + { + 'name' => @name, + 'summary' => @summary, + 'description' => @description, + 'url' => url, + 'children' => @children, + 'data' => @data, + 'platforms' => @xml.keys, + 'uniform' => uniform? + } + end + + def process(mapping) + @xml.each do |platform, xml| + @data[platform]['summary'] = @doxygen_processor.process_summary( + xml.at_css('compounddef > briefdescription'), mapping + ) + description = xml.at_css('compounddef > detaileddescription') + process_simplesects(description, mapping, platform) + @data[platform]['description'] = @doxygen_processor.process_description( + description, mapping) + process_members(mapping, platform) + end + @children = @children.reject { |child| child.name.match(/^@/) } + @children.sort! { |m, n| m.position <=> n.position } + end + + def uniform? + identical = @data['aplite'].to_json == @data['basalt'].to_json + identical &&= @children.all?(&:uniform?) + identical + end + + private + + def create_members(platform) + @xml[platform].css('memberdef').each do |child| + variable = DocClassVariable.new(@root, child, @group, platform) + existing = @children.select { |ex| ex.name == variable.name }.first + if existing.nil? + @children << variable + else + existing.add_platform(platform, child) + end + end + end + + def process_members(mapping, platform) + @children.each { |child| child.process(mapping, platform) } + end + end + + # DocClassVariable is a DocElement subclass that handles the members (or + # variables) of a struct or union. + class DocClassVariable < DocElement + attr_reader :name, :position + + def initialize(root, node, group, platform) + super(root, platform) + @name = node.at_css('name').content.to_s + @group = group + @nodes = { platform => node } + @platforms = [platform] + @path = "#{@group.path}##{@name}" + @position = node.at_css(' > location')['line'].to_i + end + + def add_platform(platform, node) + @nodes[platform] = node + @platforms << platform + @data[platform] = {} + end + + def to_liquid + { + 'name' => @name, + 'data' => @data, + 'url' => url, + 'type' => @type, + 'platforms' => @platforms + } + end + + def uniform? + @data['aplite'].to_json == @data['basalt'].to_json + end + + def process(mapping, platform) + return unless @platforms.include? platform + @data[platform]['summary'] = @doxygen_processor.process_summary( + @nodes[platform].at_css('briefdescription'), mapping + ) + description = @nodes[platform].at_css('detaileddescription') + process_simplesects(description, mapping, platform) + process_type(mapping, platform) + @data[platform]['description'] = @doxygen_processor.process_description( + description, mapping) + end + + def process_type(mapping, platform) + if @nodes[platform].at_css('type > ref').nil? + @data[platform]['type'] = @nodes[platform].at_css('type').content.to_s + else + type_node = @nodes[platform].at_css('type').clone() + @doxygen_processor.process_node_ref(type_node.at_css('ref'), mapping) + @data[platform]['type'] = type_node.to_html.to_s + end + end + end +end diff --git a/devsite/lib/c_docs/doc_element.rb b/devsite/lib/c_docs/doc_element.rb new file mode 100644 index 00000000..5f8136ff --- /dev/null +++ b/devsite/lib/c_docs/doc_element.rb @@ -0,0 +1,98 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + # DocElement is an abstract C Documentation class that is subclasses for + # each of the various items that build up a documentation page, symbol or + # tree item. + class DocElement + KNOWN_SECT_TYPES = %w(return note) + attr_reader :url + + def initialize(root, platform) + @root = root + @data = {} + @data[platform] = {} + @doxygen_processor = DoxygenProcessor.new(platform) + end + + def to_symbol + { + name: @name, + url: url, + summary: default_data('summary') + } + end + + def to_mapping + { + id: @id, + url: url + } + end + + private + + def default_data(key) + return @data['basalt'][key] unless @data['basalt'].nil? || @data['basalt'][key].nil? + return @data['aplite'][key] unless @data['aplite'].nil? || @data['aplite'][key].nil? + '' + end + + def url + "#{@root}#{@path}" + end + + def add_data(type, value, platform) + @data[platform] = {} if @data[platform].nil? + @data[platform][type] = [] if @data[platform][type].nil? + @data[platform][type] << value + end + + def process_simplesects(node, mapping, platform) + if node.name.to_s == 'detaileddescription' + desc = node + else + desc = node.at_css('detaileddescription') + end + return if desc.nil? + desc.css('simplesect').each do |sect| + if KNOWN_SECT_TYPES.include?(sect['kind']) + process_simplesect_basic(sect, mapping, platform) + elsif sect['kind'] == 'see' + process_simplesect_see(sect, mapping, platform) + end + end + end + + def process_simplesect_basic(sect, mapping, platform) + value = @doxygen_processor.process_summary(sect.clone, mapping) + add_data(sect['kind'], value, platform) + sect.remove + end + + def process_simplesect_see(sect, mapping, platform) + if sect.at_css('para > ref').nil? + add_data(sect['kind'], + @doxygen_processor.process_paragraph(sect.at_css('para'), + mapping), platform) + else + see_node = sect.at_css('para > ref').clone + @doxygen_processor.process_node_ref(see_node, mapping) + add_data(sect['kind'], see_node.to_html.to_s, platform) + end + sect.remove + end + end +end diff --git a/devsite/lib/c_docs/doc_enum_value.rb b/devsite/lib/c_docs/doc_enum_value.rb new file mode 100644 index 00000000..32ad5006 --- /dev/null +++ b/devsite/lib/c_docs/doc_enum_value.rb @@ -0,0 +1,58 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + # DocEnumValue is a DocElement that is one of the possible values of an enum. + class DocEnumValue < DocElement + attr_reader :position, :summary, :id, :platforms, :name, :data + + def initialize(root, node, group, platform) + super(root, platform) + @name = node.at_css('name').content.to_s + @id = node['id'] + @group = group + @path = "#{@group.path}##{@name}" + @nodes = { platform => node } + @platforms = [platform] + @doxygen_processor = DoxygenProcessor.new(platform) + end + + def add_platform(node, platform) + @nodes[platform] = node + @platforms << platform + @data[platform] = {} + end + + def process(mapping, platform) + return unless @platforms.include? platform + process_simplesects(@nodes[platform], mapping, platform) + @data[platform]['summary'] = @doxygen_processor.process_summary( + @nodes[platform].at_css('briefdescription'), mapping + ) + end + + def uniform? + data['aplite'].to_json == data['basalt'].to_json + end + + def to_liquid + { + 'name' => @name, + 'data' => @data, + 'url' => url, + 'platforms' => @platforms + } + end + end +end diff --git a/devsite/lib/c_docs/doc_group.rb b/devsite/lib/c_docs/doc_group.rb new file mode 100644 index 00000000..8b6aefd2 --- /dev/null +++ b/devsite/lib/c_docs/doc_group.rb @@ -0,0 +1,178 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'doc_element.rb' +require_relative 'doc_member.rb' +require_relative 'doc_class.rb' +require_relative 'doxygen_processor.rb' + +module Pebble + # A DocGroup is a a collection of members, structs and subgroups that will + # become a page in the C documentation. + class DocGroup < DocElement + attr_accessor :groups, :members, :name, :path, :menu_path, :classes, :id + + def initialize(root, dir, platform, id, menu_path = []) + super(root, platform) + @dir = dir + @menu_path = Array.new(menu_path) + @xml = {} + @groups = [] + @members = [] + @classes = [] + @group_id = id + @doxygen_processor = DoxygenProcessor.new(platform) + @root = root + load_xml(platform) + end + + def load_xml(platform) + @xml[platform] = Nokogiri::XML(File.read("#{@dir}/#{platform}/xml/group___#{@group_id}.xml")) + @id = @xml[platform].at_css('compounddef')['id'] + @name = @xml[platform].at_css('compounddef > title').content.to_s + @menu_path << @name if @path.nil? + @path = @menu_path.join('/').gsub(' ', '_') + '/' + create_descendents(platform) + end + + def process(mapping, platform) + return if @xml[platform].nil? + @data[platform] = {} if @data[platform].nil? + @data[platform]['summary'] = @doxygen_processor.process_summary( + @xml[platform].at_css('compounddef > briefdescription'), mapping) + description = @xml[platform].at_css('compounddef > detaileddescription') + process_simplesects(description, mapping, platform) + @data[platform]['description'] = @doxygen_processor.process_description( + description, mapping) + process_descendents(mapping, platform) + end + + def to_page(site) + PageDocC.new(site, @root, site.source, "#{@path}index.html", self) + end + + def to_branch + { + 'name' => @name, + 'url' => url, + 'children' => @groups.map(&:to_branch), + 'summary' => default_data('summary') + } + end + + def mapping_array + mapping = [to_mapping] + @groups.each { |group| mapping += group.mapping_array } + @members.each { |member| mapping << member.to_mapping } + @classes.each { |cls| mapping << cls.to_mapping } + mapping + end + + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def to_liquid + { + 'name' => @name, + 'url' => url, + 'path' => @menu_path, + 'groups' => @groups, + 'members' => @members, + 'functions' => @members.select { |member| member.kind == 'function' }, + 'enums' => @members.select { |member| member.kind == 'enum' }, + 'defines' => @members.select { |member| member.kind == 'define' }, + 'typedefs' => @members.select { |member| member.kind == 'typedef' }, + 'structs' => @classes.select { |member| member.kind == 'struct' }, + 'unions' => @classes.select { |member| member.kind == 'union' }, + 'data' => @data, + 'basalt_only' => !@xml.key?('aplite'), + 'summary' => default_data('summary'), + 'description' => default_data('description') + } + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + private + + def create_descendents(platform) + create_inner_groups(platform) + create_members(platform) + create_inner_classes(platform) + @members.sort! { |m, n| m.position <=> n.position } + end + + def create_inner_groups(platform) + @xml[platform].css('innergroup').each do |child| + id = child['refid'].sub(/^group___/, '') + new_group = DocGroup.new(@root, @dir, platform, id, @menu_path) + group = @groups.select { |grp| new_group.name == grp.name }.first + if group.nil? + @groups << new_group + else + group.load_xml(platform) + end + end + end + + def create_members(platform) + @xml[platform].css('memberdef').map do |child| + new_member = DocMember.new(@root, child, self, platform) + member = @members.select { |mem| mem.name == new_member.name }.first + if member.nil? + @members << new_member + else + member.add_platform(platform, child) + end + end + end + + def create_inner_classes(platform) + @xml[platform].css('innerclass').map do |child| + next if child.content.to_s.match(/__unnamed__/) + next if child.content.to_s.match(/\./) + if child['refid'].match(/^struct_/) + create_struct(child, platform) + elsif child['refid'].match(/^union_/) + create_union(child, platform) + end + end + end + + def create_struct(node, platform) + id = node['refid'].sub(/^struct_/, '') + new_struct = DocClass.new(@root, @dir, platform, 'struct', id, self) + struct = @classes.select { |str| new_struct.name == str.name }.first + if struct.nil? + @classes << new_struct + else + struct.load_xml(platform) + end + end + + def create_union(node, platform) + id = node['refid'].sub(/^union_/, '') + new_union = DocClass.new(@root, @dir, platform, 'union', id, self) + union = @classes.select { |un| un.name == new_union.name }.first + if union.nil? + @classes << new_union + else + union.load_xml(platform) + end + end + + def process_descendents(mapping, platform) + @groups.each { |group| group.process(mapping, platform) } + @members.each { |member| member.process(mapping, platform) } + @classes.each { |member| member.process(mapping) } + end + end +end diff --git a/devsite/lib/c_docs/doc_member.rb b/devsite/lib/c_docs/doc_member.rb new file mode 100644 index 00000000..25bcb55e --- /dev/null +++ b/devsite/lib/c_docs/doc_member.rb @@ -0,0 +1,165 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'doc_enum_value.rb' + +module Pebble + # A DocMember is a function, enum, typedef that will become a symbol + # and be a part of a documentation page. Belongs to a DocGroup. + class DocMember < DocElement + attr_accessor :children, :kind, :name, :summary, :children, :position, :data, :id + + def initialize(root, node, group, platform) + super(root, platform) + @group = group + @children = [] + @platforms = [platform] + @nodes = { platform => node } + @name = node.at_css('name').content.to_s + @kind = node['kind'] + @id = node['id'] + @path = "#{@group.path}##{@name}" + @position = node.at_css(' > location')['line'].to_i + @doxygen_processor = DoxygenProcessor.new(platform) + create_enum_values(node, platform) if @kind == 'enum' + end + + def add_platform(platform, node) + @platforms << platform + @nodes[platform] = node + @data[platform] = {} + create_enum_values(node, platform) if @kind == 'enum' + end + + def to_liquid + { + 'name' => @name, + 'url' => url, + 'children' => @children, + 'position' => @position, + 'data' => @data, + 'uniform' => uniform?, + 'platforms' => @platforms + } + end + + def process(mapping, platform) + return unless @platforms.include? platform + @data[platform]['summary'] = @doxygen_processor.process_summary( + @nodes[platform].at_css(' > briefdescription'), mapping + ) + process_data(@nodes[platform], mapping, platform) + @data[platform]['description'] = @doxygen_processor.process_description( + @nodes[platform].at_css(' > detaileddescription'), mapping + ) + @children.each { |child| child.process(mapping, platform) } + end + + def uniform? + identical = data['aplite'].to_json == data['basalt'].to_json + identical &&= children.all?(&:uniform?) if @kind == 'enum' + identical + end + + private + + def create_enum_values(node, platform) + node.css('enumvalue').each do |value| + enum_value = DocEnumValue.new(@root, value, @group, platform) + existing_value = @children.select { |ev| ev.name == enum_value.name }.first + if existing_value.nil? + @children << enum_value + else + existing_value.add_platform(value, platform) + end + end + end + + def process_data(node, mapping, platform) + process_typedef(node, mapping, platform) if @kind == 'typedef' + process_function(node, mapping, platform) if @kind == 'function' + process_define(node, mapping, platform) if @kind == 'define' + process_simplesects(node, mapping, platform) + end + + def process_typedef(node, mapping, platform) + process_return_type(node, mapping, platform) + @data[platform]['argsstring'] = node.at_css('argsstring').content.to_s + process_parameter_list(node, mapping, platform) + end + + def process_function(node, mapping, platform) + process_return_type(node, mapping, platform) + process_params(node, mapping, platform) unless node.css('param').nil? + process_parameter_list(node, mapping, platform) + end + + def process_define(node, mapping, platform) + unless node.at_css('initializer').nil? + @data[platform]['initializer'] = process_to_html( + node.at_css('initializer'), mapping + ) + end + process_return_type(node, mapping, platform) + process_parameter_list(node, mapping, platform) + process_params(node, mapping, platform) unless node.css('param').nil? + end + + def process_return_type(node, mapping, platform) + @data[platform]['type'] = process_to_html(node.at_css('> type'), mapping) + end + + def process_params(node, mapping, platform) + @data[platform]['params'] = node.css('param').map do |elem| + params = {} + unless elem.at_css('declname').nil? + params['name'] = elem.at_css('declname').content.to_s + end + unless elem.at_css('type').nil? + params['type'] = process_to_html(elem.at_css('type'), mapping) + end + unless elem.at_css('defname').nil? + params['name'] = elem.at_css('defname').content.to_s + end + params + end + end + + def process_to_html(node, mapping) + return '' if node.nil? + node.css('ref').each do |ref| + @doxygen_processor.process_node_ref(ref, mapping) + end + node.inner_html.to_s + end + + def process_parameter_list(node, mapping, platform) + return if node.at_css('parameterlist').nil? + parameter_list = node.at_css('parameterlist').clone + node.at_css('parameterlist').remove + @data[platform]['parameters'] = parameter_list.css('parameteritem').map do |item| + { + 'name' => get_parameter_name(item), + 'summary' => @doxygen_processor.process_summary(item.at_css('parameterdescription'), mapping) + } + end + end + + def get_parameter_name(item) + name = item.at_css('parameternamelist > parametername') + direction = name.attr('direction').to_s + direction.nil? || direction == '' ? name.content.to_s : "#{name.content.to_s} (#{direction})" + end + end +end diff --git a/devsite/lib/c_docs/doxygen_processor.rb b/devsite/lib/c_docs/doxygen_processor.rb new file mode 100644 index 00000000..eb4060a5 --- /dev/null +++ b/devsite/lib/c_docs/doxygen_processor.rb @@ -0,0 +1,154 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + # Class of methods for processing Doxygen XML into 'sane' HTML. + # rubocop:disable Metrics/ClassLength + class DoxygenProcessor + def initialize(platform) + @platform = platform + end + + def process_summary(node, mapping) + process_description(node, mapping) + end + + def process_description(node, mapping) + return '' if node.nil? + node.children.map { |para| process_paragraph(para, mapping) }.join("\n").strip + end + + # rubocop:disable Metrics/MethodLength, Methods/CyclomaticComplexity + # rubocop:disable Methods/AbcSize + def process_paragraph(node, mapping) + return '' if node.nil? + node.name = 'p' + node.children.each do |child| + case child.name + when 'ref' + process_node_ref(child, mapping) + when 'programlisting' + process_code(child) + when 'simplesect' + # puts node.content.to_s + # process_blockquote(child) + when 'heading' + process_node_heading(child) + when 'htmlonly' + process_node_htmlonly(child) + when 'itemizedlist' + process_list(child, mapping) + when 'image' + process_image(child) + when 'computeroutput' + process_computer_output(child) + when 'emphasis' + child.name = 'em' + when 'bold' + child.name = 'strong' + when 'linebreak' + child.name = 'br' + when 'preformatted' + child.name = 'pre' + when 'ndash' + child.name = 'span' + child.content = '-' + when 'ulink' + child.name = 'a' + child['href'] = child['url'].sub(%r{^https?://developer.pebble.com/}, '/') + child.remove_attribute('url') + when 'text' + # SKIP! + else + # puts child.name, node.content.to_s + end + end + node.to_html.to_s.strip + end + # rubocop:enable Metrics/MethodLength, Methods/CyclomaticComplexity + # rubocop:enable Methods/AbcSize + + def process_code(node) + xml = node.to_xml.gsub('', ' ') + doc = Nokogiri::XML(xml) + highlight = Pygments.highlight(doc.content.to_s.strip, lexer: 'c') + node.content = '' + node << Nokogiri::XML(highlight).at_css('pre') + node.name = 'div' + node['class'] = 'highlight' + end + + def process_node_ref(child, mapping) + child.name = 'a' + map = mapping.select { |m| m[:id] == child['refid'] }.first + child['href'] = map[:url] unless map.nil? + end + + def process_node_heading(node) + node.name = 'h' + node['level'] + end + + def process_node_htmlonly(node) + decoder = HTMLEntities.new + xml = Nokogiri::XML('' + decoder.decode(node.content) + '') + node_count = xml.root.children.size + process_node_htmlonly_simple(node, xml) if node_count == 2 + process_node_htmlonly_complex(node, xml) if node_count > 2 + end + + def process_node_htmlonly_simple(node, xml) + child = xml.at_css('root').children[0] + node.name = child.name + child.attributes.each { |key, value| node[key] = value } + node.content = child.content + end + + def process_node_htmlonly_complex(node, xml) + node.name = 'div' + node.content = '' + node << xml.root.children + end + + def process_blockquote(node) + node.name = 'blockquote' + node['class'] = 'blockquote--' + node['kind'] + process_paragraph(node.children[0]) if node.children[0].name == 'para' + node.to_html.to_s + end + + def process_list(node, mapping) + node.name = 'ul' + node.children.each do |child| + process_list_item(child, mapping) + end + end + + def process_list_item(node, mapping) + node.name = 'li' + node.children.each do |child| + process_paragraph(child, mapping) if child.name == 'para' + end + end + + def process_image(node) + node.name = 'img' + node['src'] = "/assets/images/docs/c/#{@platform}/#{node['name']}" + end + + def process_computer_output(node) + node.name = 'code' + end + end + # rubocop:enable Metrics/ClassLength +end diff --git a/devsite/lib/pebble_documentation.rb b/devsite/lib/pebble_documentation.rb new file mode 100644 index 00000000..aaa77ae1 --- /dev/null +++ b/devsite/lib/pebble_documentation.rb @@ -0,0 +1,49 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + class Documentation + LANGUAGE = '' + def initialize(site) + @site = site + @symbols = [] + @pages = [] + @tree = [] + end + + def load_symbols(symbols) + symbols.concat(@symbols) + end + + def create_pages(pages) + pages.concat(@pages) + end + + def build_tree(tree) + tree.concat(@tree) + end + + private + + def add_symbol(symbol) + symbol[:language] = language + symbol[:summary] = symbol[:summary].strip unless symbol[:summary].nil? + @symbols << symbol + end + + def language + LANGUAGE + end + end +end diff --git a/devsite/lib/pebble_documentation_c.rb b/devsite/lib/pebble_documentation_c.rb new file mode 100644 index 00000000..b59b5b63 --- /dev/null +++ b/devsite/lib/pebble_documentation_c.rb @@ -0,0 +1,189 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'pygments' +require 'zip' +require 'nokogiri' +require 'htmlentities' +require_relative 'c_docs/doc_group.rb' + +module Pebble + # Pebble C documentation processing class. + class DocumentationC < Documentation + MASTER_GROUP_IDS = %w(foundation graphics u_i smartstrap worker standard_c) + + def initialize(site, source, root, language='c') + super(site) + @site = site + @url_root = root + @source = source + @tmp_dir = 'tmp/docs/c' + @groups = [] + @language = language + run + end + + private + + def language + @language + end + + def run + cleanup + download_and_extract(@source, @tmp_dir) + hack_smartstraps + process + add_images + end + + def cleanup + FileUtils.rmtree @tmp_dir + end + + def download_and_extract(zip, folder) + open(zip) do | zf | + Zip::File.open(zf.path) do | zipfile | + zipfile.each do | entry | + path = File.join(folder, entry.name).sub('/doxygen_sdk/', '/') + FileUtils.mkdir_p(File.dirname(path)) + zipfile.extract(entry, path) unless File.exist?(path) + end + end + end + end + + # This is a hack to get around a limitation with the documentation generator. + # At present, it cannot handle the situation where a top level doc group exists on + # Basalt but not Aplite. + # Smartstraps is the only group that fits this pattern at the moment. + # This hack copies the XML doc from the Basalt folder to the Aplite folder and removes + # all of its contents. + def hack_smartstraps + basalt_xml = Nokogiri::XML(File.read("#{@tmp_dir}/basalt/xml/group___smartstrap.xml")) + basalt_xml.search('.//memberdef').remove + basalt_xml.search('.//innerclass').remove + basalt_xml.search('.//sectiondef').remove + File.open("#{@tmp_dir}/aplite/xml/group___smartstrap.xml", 'w') do |file| + file.write(basalt_xml.to_xml) + end + end + + def process + DocumentationC::MASTER_GROUP_IDS.each do |id| + @groups << DocGroup.new(@url_root, @tmp_dir, 'aplite', id) + end + @groups.each { |group| group.load_xml('basalt') } + + mapping = [] + @groups.each { |group| mapping += group.mapping_array } + @groups.each do |group| + group.process(mapping, 'aplite') + group.process(mapping, 'basalt') + end + + add_symbols(@groups) + @groups.each { |group| @tree << group.to_branch } + add_pages(@groups) + add_redirects(mapping) + end + + def add_images + move_images('aplite') + move_images('basalt') + images = Dir.glob("#{@tmp_dir}/assets/images/**/*.png") + images.each do |img| + source = File.join(@site.source, '../tmp/docs/c/') + if File.exists?(img) + img.sub!('tmp/docs/c', '') + @site.static_files << Jekyll::StaticFile.new(@site, source, '', img) + end + end + end + + def move_images(platform) + images = Dir.glob("#{@tmp_dir}/#{platform}/**/*.png") + dir = File.join(@tmp_dir, 'assets', 'images', 'docs', 'c', platform) + FileUtils.mkdir_p(dir) + images.each do |img| + FileUtils.cp(img, File.join(dir, File.basename(img))) + end + end + + # TODO: Make the groups handle their own subgroups and members etc + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def add_symbols(groups) + groups.each do |group| + add_symbol(group.to_symbol) + group.members.each do |member| + add_symbol(member.to_symbol) + member.children.each do |child| + add_symbol(child.to_symbol) + end + end + group.classes.each do |child| + add_symbol(child.to_symbol) + # OPINION: I don't think we want to have struct members as symbols. + # struct.children.each do |child| + # add_symbol(child.to_symbol) + # end + end + add_symbols(group.groups) + end + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + def add_pages(groups) + groups.each do |group| + page = group.to_page(@site) + page.set_language(@language) + @pages << page + add_pages(group.groups) + end + end + + def add_redirects(mapping) + mapping.each do |map| + next if map[:id].match(/_1/) + @site.pages << Jekyll::RedirectPage.new(@site, @site.source, @url_root, map[:id] + '.html', map[:url]) + end + end + end + + # Jekyll Page subclass for rendering the C documentation pages. + class PageDocC < Jekyll::Page + attr_reader :group + + def initialize(site, root, base, dir, group) + @site = site + @base = base + @dir = root + @name = dir + @group = group + process(@name) + read_yaml(File.join(base, '_layouts', 'docs'), 'c.html') + data['title'] = @group.name + data['platforms'] = %w(aplite basalt) + + end + + def set_language(language) + data['docs_language'] = language + end + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w(group)) + end + end +end diff --git a/devsite/lib/pebble_documentation_js.rb b/devsite/lib/pebble_documentation_js.rb new file mode 100644 index 00000000..62230454 --- /dev/null +++ b/devsite/lib/pebble_documentation_js.rb @@ -0,0 +1,120 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'zip' + +module Pebble + # Rock.js documentation processing class. + class DocumentationJs < Documentation + + def initialize(site, source, root, language, preview = false) + super(site) + @url_root = root + @language = language + @preview = preview + json = site.data[source] + json.each { |js_module| process_module(js_module) } + end + + private + + def process_module(js_module) + js_module[:path] = js_module['name'] + js_module[:url] = module_url(js_module) + js_module[:processed_functions] = [] + js_module[:processed_members] = [] + js_module[:processed_typedefs] = [] + js_module[:children] = [] + + process_members(js_module) + add_to_tree(js_module) + + # Create and add the page + page = PageDocJS.new(@site, module_url(js_module), js_module) + page.set_data(@language, @preview) + @pages << page + end + + def process_members(js_module) + js_module['members'].each do |type, members| + members.each do |member| + + kind = member.key?('kind') ? member['kind'] : 'member' + processed_type = 'processed_' + kind + 's' + url = child_url(js_module, member) + + symbol = { + :name => member['name'], + :description => member['description'], + :type => member['type'], + :returns => member['returns'], + :params => member['params'], + :properties => member['properties'], + :url => url, + :kind => kind, + :summary => member['summary'] + } + add_symbol(symbol) + js_module[:children].push(symbol) + js_module[processed_type.to_sym].push(symbol) + end + end + end + + def add_to_tree(js_module) + @tree << js_module + end + + def module_url(js_module) + "#{@url_root}#{js_module['name']}/" + end + + def child_url(js_module, child) + "#{module_url(js_module)}##{child['name']}" + end + + def child_path(js_module, child) + [js_module['name'], child['name']].join('.') + end + + def language + @language + end + end + + # Jekyll Page subclass for rendering the JS documentation pages. + class PageDocJS < Jekyll::Page + attr_reader :js_module + + def initialize(site, url, js_module) + @site = site + @base = site.source + @dir = url + @name = 'index.html' + @js_module = JSON.parse(js_module.to_json) + process(@name) + read_yaml(File.join(@base, '_layouts', 'docs'), 'js.html') + data['title'] = js_module['name'] + end + + def set_data(language, preview) + data['docs_language'] = language + data['preview'] = preview + end + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w(js_module)) + end + end +end diff --git a/devsite/lib/pebble_documentation_pebblekit_android.rb b/devsite/lib/pebble_documentation_pebblekit_android.rb new file mode 100644 index 00000000..4f7f124a --- /dev/null +++ b/devsite/lib/pebble_documentation_pebblekit_android.rb @@ -0,0 +1,203 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'zip' +require 'nokogiri' +require_relative 'pebble_documentation' + +# TODO: Error handling. +# TODO: Introduce some DRY +# TODO: Fix the internal links! +# TODO: Bring back the summarys which I broke. +# TODO: Android Index Page + +module Pebble + class DocumentationPebbleKitAndroid < Documentation + def initialize(site, source) + super(site) + @path = '/docs/pebblekit-android/' + open(source) do | zf | + Zip::File.open(zf.path) do | zipfile | + entry = zipfile.glob('javadoc/overview-summary.html').first + summary = Nokogiri::HTML(entry.get_input_stream.read) + process_summary(zipfile, summary) + + @pages << PageDocPebbleKitAndroid.new(@site, @site.source, 'docs/pebblekit-android/com/constant-values/', 'Constant Values', process_html(Nokogiri::HTML(zipfile.glob('javadoc/constant-values.html').first.get_input_stream.read).at_css('.constantValuesContainer').to_html, '/docs/pebblekit-android/'), nil) + @pages << PageDocPebbleKitAndroid.new(@site, @site.source, 'docs/pebblekit-android/com/serialized-form/', 'Serialized Form', process_html(Nokogiri::HTML(zipfile.glob('javadoc/serialized-form.html').first.get_input_stream.read).at_css('.serializedFormContainer').to_html, '/docs/pebblekit-android/'), nil) + end + end + end + + private + + def language + 'pebblekit_android' + end + + def process_summary(zipfile, summary) + summary.css('tbody tr').each do | row | + name = row.at_css('td.colFirst').content + package = { + name: name, + url: "#{@path}#{name_to_url(name)}/", + children: [], + methods: [], + enums: [], + exceptions: [], + path: [name] + } + add_symbol(name: name, url: "#{@path}#{name_to_url(name)}/") + @tree << package + end + + @tree.each do | package | + entry = zipfile.glob("javadoc/#{name_to_url(package[:name])}/package-summary.html").first + summary = Nokogiri::HTML(entry.get_input_stream.read) + process_package(zipfile, package, summary) + end + end + + def process_package(zipfile, package, summary) + url = "#{@path}#{name_to_url(package[:name])}" + + html = summary.at_css('.contentContainer').to_html + html = process_html(html, url) + + @pages << PageDocPebbleKitAndroid.new(@site, @site.source, url, package[:name], html, package) + + class_table = summary.css('table[summary~="Class Summary"]') + class_table.css('tbody tr').each do | row | + name = row.at_css('td.colFirst').content + package[:children] << { + name: name, + summary: row.at_css('.colLast').content, + url: "#{url}/#{name}", + path: package[:path].clone << name, + type: 'class', + children: [], + methods: [], + enums: [], + exceptions: [] + } + add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}") + end + + enum_table = summary.css('table[summary~="Enum Summary"]') + enum_table.css('tbody tr').each do | row | + name = row.at_css('.colFirst').content + package[:children] << { + name: name, + summary: row.at_css('.colLast').content, + path: package[:path].clone << name, + url: "#{url}/#{name}", + type: 'enum', + children: [], + methods: [], + enums: [], + exceptions: [] + } + add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}") + end + + summary.css('table[summary~="Exception Summary"]').css('tbody tr').each do | row | + name = row.at_css('td.colFirst').content + package[:children] << { + name: name, + summary: row.at_css('.colLast').content, + path: package[:path].clone << name, + url: "#{url}/#{name}", + type: 'exception', + children: [], + methods: [], + enums: [], + exceptions: [] + } + add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}") + end + + package[:children].each do | child | + filename = "javadoc/#{name_to_url(package[:name])}/#{child[:name]}.html" + child_url = '/docs/pebblekit-android/' + package[:name].split('.').join('/') + '/' + child[:name] + '/' + + entry = zipfile.glob(filename).first + summary = Nokogiri::HTML(entry.get_input_stream.read) + + method_table = summary.css('table[summary~="Method Summary"]') + method_table.css('tr').each do | row | + next unless row.at_css('.memberNameLink') + name = row.at_css('.memberNameLink').content + child[:methods] << { + name: name, + summary: row.at_css('.block') ? row.at_css('.block').content : '', + url: child_url + '#' + name, + type: 'method' + } + add_symbol(name: [package[:name], child[:name], name].join('.'), url: child_url + '#' + name) + end + html = summary.at_css('.contentContainer').to_html + html = process_html(html, child_url) + @pages << PageDocPebbleKitAndroid.new(@site, @site.source, child_url, child[:name], html, child) + end + end + + def name_to_url(name) + name.split('.').join('/') + end + + def process_html(html, root) + contents = Nokogiri::HTML(html) + contents.css('a').each do | link | + next if link['href'].nil? + href = File.expand_path(link['href'], root) + href = href.sub('/com/com/', '/com/') + href = href.sub('.html', '/') + link['href'] = href + end + contents.css('.memberSummary caption').remove + contents.to_html + end + end + + class PageDocPebbleKitAndroid < Jekyll::Page + def initialize(site, base, dir, title, contents, group) + @site = site + @base = base + @dir = dir + @name = 'index.html' + @contents = contents + @group = group + + process(@name) + read_yaml(File.join(base, '_layouts', 'docs'), 'pebblekit-android.html') + data['title'] = title.to_s + end + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w( + contents + group + )) + end + + attr_reader :contents + + def group + if @group.nil? + {} + else + JSON.parse(JSON.dump(@group)) + end + end + end +end diff --git a/devsite/lib/pebble_documentation_pebblekit_ios.rb b/devsite/lib/pebble_documentation_pebblekit_ios.rb new file mode 100644 index 00000000..b4a2112e --- /dev/null +++ b/devsite/lib/pebble_documentation_pebblekit_ios.rb @@ -0,0 +1,240 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'zip' +require 'nokogiri' +require 'slugize' +require 'open-uri' + +module Pebble + # PebbleKit iOS documentation processing class. + class DocumentationPebbleKitIos < Documentation + def initialize(site, source, root) + super(site) + @site = site + @url_root = root + open(source) do |zf| + Zip::File.open(zf.path) do |zipfile| + zipfile.each { |entry| process_entry(entry) } + end + end + end + + private + + def language + 'pebblekit_ios' + end + + def process_entry(entry) + return unless File.extname(entry.name) == '.html' + doc = Nokogiri::HTML(entry.get_input_stream.read) + process_index(doc) if File.basename(entry.name) == 'index.html' + process_normal_entry(doc, entry) + end + + def process_normal_entry(doc, entry) + doc_entry = DocEntryPebbleKitIos.new(entry, doc, @url_root) + add_symbol(doc_entry.to_symbol) + doc_entry.anchor_symbols.map { |symbol| add_symbol(symbol) } + @pages << doc_entry.create_page(@site) + end + + def process_index(doc) + headers = doc.at_css('#content').css('h2').map(&:content) + lists = doc.at_css('#content').css('ul').map { | list | list.css('li') } + headers.each_with_index do |header, index| + process_index_header(header, index, lists) + end + end + + def process_index_header(header, index, lists) + tree_item = { + name: header, + url: "#{@url_root}##{header.slugize}", + children: [] + } + lists[index].each { |item| process_index_header_item(tree_item, item) } + @tree << tree_item + end + + def process_index_header_item(tree_item, item) + tree_item[:children] << { + name: item.content, + url: "#{@url_root}#{item.at_css('a')['href'].sub('.html', '/').gsub('+', '%2B')}", + path: [item.content], + children: [] + } + end + end + + # DocEntryIos is an iOS documentation class used to process a single page + # of the iOS documentation. + class DocEntryPebbleKitIos + def initialize(entry, doc, url_root) + @entry = entry + @doc = doc + @url_root = url_root + end + + def to_symbol + { name: name, url: url.gsub('+', '%2B') } + end + + def anchor_symbols + @doc.css('a[name^="//api"][title]').map do |anchor| + anchor_to_symbol(anchor) + end + end + + def create_page(site) + return nil if @doc.at_css('#content').nil? + contents = @doc.at_css('#content') + title = @doc.at_css('.title').content + group = { 'path' => [File.basename(path)] } + PageDocPebbleKitIos.new(site, url, title, contents, group) + end + + private + + def name + File.basename(@entry.name).sub('.html', '') + end + + def url + @url_root + path + end + + def path + @entry.name.sub('.html', '/') + end + + def anchor_to_symbol(anchor) + summary = @doc.at_css("a[name=\"#{anchor['name']}\"] + h3 + div") + { + name: anchor['title'], + url: (url + '#' + anchor['name']).gsub('+', '%2B'), + summary: summary.content + } + end + end + + # Jekyll Page subclass for rendering the iOS documentation pages. + class PageDocPebbleKitIos < Jekyll::Page + attr_reader :group + + def initialize(site, dir, title, contents, group) + @site = site + @base = site.source + @dir = dir + @name = 'index.html' + @contents = contents + @group = group + process(@name) + process_contents + read_yaml(File.join(@base, '_layouts', 'docs'), 'pebblekit-ios.html') + data['title'] = title + end + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w( + contents + group + )) + end + + def contents + @contents.to_html + end + + private + + def process_contents + # Clean up links + @contents.css('a').each { |link| process_page_link(link) } + + remove_duplicated_title + switch_specification_section_table_headers_to_normal_cells + clean_up_method_titles + switch_parameter_tables_into_definition_lists + remove_footer + end + + def process_page_link(link) + process_page_link_class(link) unless link['name'].nil? + process_page_link_href(link) unless link['href'].nil? + end + + def process_page_link_class(link) + link['class'] = '' if link['class'].nil? + link['class'] << ' anchor' + end + + def process_page_link_href(link) + link['href'] = link['href'].gsub('../', '../../') + link['href'] = link['href'].gsub('.html', '/') + link['href'] = link['href'].gsub('+', '%2B') + end + + def remove_duplicated_title + @contents.css('h1').each(&:remove) + end + + def switch_specification_section_table_headers_to_normal_cells + @contents.css('.section-specification th').each do |n| + n.node_name = 'td' + n['class'] = 'specification-title' + end + end + + def clean_up_method_titles + # Remove the tags inside h3.method-title nodes, strip nbsp and + # add the subsubtitle class. + @contents.css('h3.method-title').each do |n| + method_title = n.at_css('code a') + n.content = method_title.content.gsub(/\A\u00A0+/, '') if method_title + n['class'] = 'subsubtitle method-title' + end + end + + def switch_parameter_tables_into_definition_lists + # Change the table node into a definition list + # For each row recover the parameter name and the description, and add + # them to the list as term and definition. + @contents.css('table.argument-def').each do |table| + table.node_name = 'dl' + + parameters = table.css('tr').map do |row| + parameter = row.at_css('th.argument-name code') + parameter.node_name = 'em' + dt = Nokogiri::XML::Element.new('dt', table.document) + dt.add_child parameter + + definition = row.at_css('td:not(.argument-name)').content + dd = Nokogiri::XML::Element.new('dd', table.document) + dd.children = definition + + [dt, dd] + end.flatten(1) + + table.children.unlink + parameters.each { |p| table.add_child p } + end + end + + def remove_footer + @contents.css('footer').each(&:remove) + end + end +end diff --git a/devsite/lib/search_markdown.rb b/devsite/lib/search_markdown.rb new file mode 100644 index 00000000..58cef5be --- /dev/null +++ b/devsite/lib/search_markdown.rb @@ -0,0 +1,140 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Pebble + + class SearchMarkdown < Redcarpet::Render::HTML + + def initialize() + @contents = Array.new + @sections = [] + @section = { + :title => nil, + :contents => [] + } + super() + end + + def get_contents() + @contents.join(" \n ") + end + + def get_sections() + unless @section.nil? + @sections << @section + end + @sections.map do | section | + section[:contents] = section[:contents].join("\n") + section + end + @sections + end + + def block_code(code, language) + "" + end + + def header(text, header_level) + unless @section.nil? + @sections << @section + end + @section = { + :title => text, + :contents => [] + } + @contents << text + "" + end + + def paragraph(text) + @contents << text + @section[:contents] << text + "" + end + + def codespan(text) + text + end + + def image(link, title, alt_text) + "" + end + + def link(link, title, content) + content + end + + def list(contents, type) + @contents << contents + @section[:contents] << contents + "" + end + + def list_item(text, list_type) + @contents << text + @section[:contents] << text + "" + end + + def autolink(link, link_type) + link + end + + def double_emphasis(text) + text + end + + def emphasis(text) + text + end + + def linebreak() + "" + end + + def raw_html(raw_html) + "" + end + + def triple_emphasis(text) + text + end + + def strikethrough(text) + text + end + + def superscript(text) + text + end + + def underline(text) + text + end + + def highlight(text) + text + end + + def quote(text) + text + end + + def footnote_ref(number) + "" + end + + end + +end \ No newline at end of file diff --git a/devsite/lib/toc_generator.rb b/devsite/lib/toc_generator.rb new file mode 100644 index 00000000..01bef8f6 --- /dev/null +++ b/devsite/lib/toc_generator.rb @@ -0,0 +1,81 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'redcarpet' +require 'nokogiri' + +module Pebble + + class TocGenerator + + def initialize(max_depth=-1) + @max_depth = max_depth + @markdown = TocMarkdown.new() + @redcarpet = Redcarpet::Markdown.new(@markdown, + fenced_code_blocks: true, + autolink: true, + tables: true, + no_intra_emphasis: true, + strikethrough: true, + highlight: true) + end + + def generate(content) + @redcarpet.render(content) + page_toc = @markdown.get_toc + toc_array(toc_normalised(page_toc)) + end + + private + + # Convert the ToC array of hashes into an array of array so that it can + # be used in the Liquid template. + def toc_array(array) + array.map { |entry| [ entry[:id], entry[:title], entry[:level] ] } + end + + # Normalised the ToC array by ensuring that the smallest level number is 1. + def toc_normalised(array) + min_level = 100 + array.each { |entry| min_level = [ min_level, entry[:level] ].min } + level_offset = min_level - 1 + array.map { |entry| entry[:level] -= level_offset; entry }.select do |entry| + @max_depth == -1 || entry[:level] <= @max_depth + end + end + + end + + class TocMarkdown < Redcarpet::Render::HTML + + def initialize() + @toc = Array.new + @depth = 0 + super() + end + + def get_toc() + @toc + end + + def header(text, header_level) + text = Nokogiri::HTML(text).text if text.include?('<') + entry = { title: text, id: text.slugize, level: header_level } + @toc << entry + "" + end + + end + +end diff --git a/devsite/plugins/blog_authors_generator.rb b/devsite/plugins/blog_authors_generator.rb new file mode 100644 index 00000000..b2aaaed0 --- /dev/null +++ b/devsite/plugins/blog_authors_generator.rb @@ -0,0 +1,53 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generates a list page for each blog author. +# The list of authors is in /source/_data/blog_authors.yml + +module Jekyll + + class AuthorPage < Page + + def initialize(site, base, dir, author) + @site = site + @base = base + @dir = dir + @name = author[0] + '/index.html' + + self.process(@name) + self.read_yaml(File.join(base, '_layouts'), 'blog/author_page.html') + self.data['posts'] = site.posts.docs.select { |post| post['author'] == author[0] } + self.data['author_name'] = author[1]['name'] + self.data['author'] = author[0] + self.data['title'] = "Pebble Developer Blog: #{author[1]['name']}" + end + + end + + class AuthorPageGenerator < Generator + + def generate(site) + if ! site.layouts.key? 'blog/author_page' + throw 'Layout for the blog author pages is missing.' + end + dir = site.config['tag_dir'] || 'blog/authors' + site.data['authors'].each do |author| + author[1]['num_posts'] = site.posts.docs.select { |post| post['author'] == author[0] }.length + site.pages << AuthorPage.new(site, site.source, dir, author) + end + end + + end + +end diff --git a/devsite/plugins/blog_tags_generator.rb b/devsite/plugins/blog_tags_generator.rb new file mode 100644 index 00000000..83da28aa --- /dev/null +++ b/devsite/plugins/blog_tags_generator.rb @@ -0,0 +1,69 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generates a list page for each blog tag. + +require 'slugize' + +module Jekyll + + class TagPage < Page + + def initialize(site, base, dir, tag) + @site = site + @base = base + @dir = dir + @name = tag[0].slugize + '/index.html' + + self.process(@name) + self.read_yaml(File.join(base, '_layouts'), 'blog/tag_page.html') + self.data['posts'] = tag[1].sort_by(&:date).reverse + self.data['name'] = tag[0] + self.data['tag'] = tag[0] + self.data['title'] = "Pebble Developer Blog: #{tag[0]}" + end + + end + + class TagPageRedirect < Page + + def initialize(site, base, dir, tag) + @site = site + @base = base + @dir = dir + @name = tag[0].slugize + '.html' + + self.process(@name) + self.read_yaml(File.join(base, '_layouts'), 'utils/redirect_permanent.html') + self.data['path'] = '/' + File.join(dir, tag[0].slugize) + '/' + end + + end + + class TagPageGenerator < Generator + + def generate(site) + if ! site.layouts.key? 'blog/tag_page' + throw 'Layout for the blog tag pages is missing.' + end + dir = site.config['tag_dir'] || 'blog/tags' + site.tags.each do |tag| + site.pages << TagPage.new(site, site.source, dir, tag) + site.pages << TagPageRedirect.new(site, site.source, dir, tag) + end + end + + end + +end diff --git a/devsite/plugins/environment_generator.rb b/devsite/plugins/environment_generator.rb new file mode 100644 index 00000000..b23dad93 --- /dev/null +++ b/devsite/plugins/environment_generator.rb @@ -0,0 +1,50 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Jekyll + + class EnvironmentGenerator < Generator + + priority :highest + + def initialize(site) + # TODO: Figure out how to check for the environment type. + require 'dotenv' + Dotenv.load + end + + def generate(site) + if !ENV.has_key?('URL') && ENV.has_key?('HEROKU_APP_NAME') + ENV['URL'] = "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com" + ENV['HTTPS_URL'] = "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com" + end + site.data['env'].each do |item| + if ENV.has_key?(item['env']) + set_config(site.config, item['config'], ENV[item['env']]) + elsif item.has_key?('default') + set_config(site.config, item['config'], item['default']) + end + end + end + + private + + # TODO: Rewrite this function to allow for nested keys. + def set_config(config, key, value) + config[key] = value + end + + end + +end diff --git a/devsite/plugins/filter_assetify.rb b/devsite/plugins/filter_assetify.rb new file mode 100644 index 00000000..6ccabb90 --- /dev/null +++ b/devsite/plugins/filter_assetify.rb @@ -0,0 +1,23 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# FilterAssetify is a Liquid filter to prepend the asset_path when needed +module FilterAssetify + def assetify(input) + asset_path = @context.registers[:site].config['asset_path'] + %r{^/[^/]}.match(input) ? (asset_path + input) : input + end +end + +Liquid::Template.register_filter(FilterAssetify) diff --git a/devsite/plugins/filter_basename.rb b/devsite/plugins/filter_basename.rb new file mode 100644 index 00000000..a36ba19f --- /dev/null +++ b/devsite/plugins/filter_basename.rb @@ -0,0 +1,21 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module FilterBasename + def basename(input, suffix) + File.basename(input, suffix) + end +end + +Liquid::Template.register_filter(FilterBasename) diff --git a/devsite/plugins/filter_fake_platform.rb b/devsite/plugins/filter_fake_platform.rb new file mode 100644 index 00000000..d22d0460 --- /dev/null +++ b/devsite/plugins/filter_fake_platform.rb @@ -0,0 +1,28 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module FilterFakePlatform + def fake_platform(input) + case input + when 'aplite' + 'SDK 3' + when 'basalt' + 'SDK 4' + else + '??' + end + end +end + +Liquid::Template.register_filter(FilterFakePlatform) diff --git a/devsite/plugins/filter_hash_sort.rb b/devsite/plugins/filter_hash_sort.rb new file mode 100644 index 00000000..316775a8 --- /dev/null +++ b/devsite/plugins/filter_hash_sort.rb @@ -0,0 +1,29 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module FilterHashSort + def hash_sort(hash, property=nil) + return [] if hash.nil? + sorted_hash = [] + hash.each { |key, value| sorted_hash << [key, value] } + if property.nil? + sorted_hash.sort! { |a, b| a[0] <=> b[0] } + else + sorted_hash.sort! { |a, b| a[1][property] <=> b[1][property] } + end + sorted_hash + end +end + +Liquid::Template.register_filter(FilterHashSort) diff --git a/devsite/plugins/filter_pluralize.rb b/devsite/plugins/filter_pluralize.rb new file mode 100644 index 00000000..2daee2a8 --- /dev/null +++ b/devsite/plugins/filter_pluralize.rb @@ -0,0 +1,28 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Liquid filter that does magic to make plurals easy. +module Pluralize + def pluralize(number, singular, plural = nil) + if number == 1 + "#{number} #{singular}" + elsif plural.nil? + "#{number} #{singular}s" + else + "#{number} #{plural}" + end + end +end + +Liquid::Template.register_filter(Pluralize) diff --git a/devsite/plugins/filter_slugize.rb b/devsite/plugins/filter_slugize.rb new file mode 100644 index 00000000..63ac3151 --- /dev/null +++ b/devsite/plugins/filter_slugize.rb @@ -0,0 +1,25 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'slugize' + +# Liquid filter that turns a string into a slug. +# Used to turn tag names into the tag URL part. +module FilterSlugize + def slugize(input) + input.slugize + end +end + +Liquid::Template.register_filter(FilterSlugize) diff --git a/devsite/plugins/generator_algolia.rb b/devsite/plugins/generator_algolia.rb new file mode 100644 index 00000000..6c74f25c --- /dev/null +++ b/devsite/plugins/generator_algolia.rb @@ -0,0 +1,236 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'htmlentities' +require 'algoliasearch' +require 'slugize' +require 'dotenv' +require 'securerandom' + +module Jekyll + class GeneratorAlgolia < Generator + # Do this last so everything else has been processed already. + priority :lowest + + def initialize(_config) + Dotenv.load + end + + def generate(site) + @site = site + return unless check_config? + @prefix = site.config['algolia_prefix'] || '' + @random_code = random_code + Algolia.init(application_id: site.config['algolia_app_id'], + api_key: site.config['algolia_api_key']) + @indexes = setup_indexes + generate_all + end + + private + + def check_config? + if @site.config['algolia_app_id'].nil? || @site.config['algolia_app_id'].empty? + Jekyll.logger.warn( + 'Config Warning:', + 'You did not provide a ALGOLIA_APP_ID environment variable.' + ) + return false + end + if @site.config['algolia_api_key'].nil? || @site.config['algolia_api_key'].empty? + Jekyll.logger.warn( + 'Config Warning:', + 'You did not provide a ALGOLIA_API_KEY environment variable.' + ) + return false + end + true + end + + def generate_all + generate_blog_posts + generate_guides + generate_documentation + generate_none_guide_guides + generate_other + end + + def random_code + SecureRandom.hex + end + + def setup_indexes + indexes = {} + @site.data['search_indexes'].each do |name, properties| + index = Algolia::Index.new(@prefix + name) + unless properties['settings'].nil? + index.set_settings(properties['settings']) + end + indexes[name] = index + end + indexes + end + + def generate_documentation + return if @site.data['docs'].nil? + + documents = @site.data['docs'][:symbols].map do |item| + next if item[:language] == 'c_preview' + + if item[:summary].nil? || item[:summary].strip.length == 0 + Jekyll.logger.warn( + 'Search Warning:', + "There was no summary for the symbol '#{item[:name]}' in #{item[:language]}." + ) + end + { + 'objectID' => item[:url], + 'title' => item[:name], + 'splitTitle' => item[:name].split(/(?=[A-Z])/).join(' '), + 'url' => item[:url], + 'summary' => item[:summary], + 'kind' => item[:kind], + 'language' => item[:language], + 'type' => 'documentation', + 'ranking' => doc_language_rank[item[:language]] * 1000, + 'randomCode' => @random_code + } + end.compact + @indexes['documentation'].save_objects(documents) + end + + def generate_blog_posts + documents = [] + @site.posts.docs.each do | post | + # Calculate the age of the post so we can prioritise newer posts + # over older ones. + # NOTE: post.date is actually a Time object, despite its name + age = (Time.now - post.date).round + author = post.data['author'] + + post.get_sections.each do | section | + # Ignore sections without any contents. + if section[:contents].strip.size == 0 + next + end + + if section[:title].nil? + url = post.url + else + url = post.url + '#' + section[:title].slugize + end + + document = { + 'objectID' => url, + 'title' => post.data['title'], + 'sectionTitle' => section[:title], + 'url' => url, + 'urlDisplay' => post.url, + 'author' => author, + 'content' => HTMLEntities.new.decode(section[:contents]), + 'posted' => post.date, + 'age' => age, + 'type' => 'blog post', + 'randomCode' => @random_code + } + documents << document + end + end + @indexes['blog-posts'].save_objects(documents) + end + + def generate_guides + documents = [] + return if @site.collections['guides'].nil? + + @site.collections['guides'].docs.each do | guide | + group = @site.data['guides'][guide.data['guide_group']] + unless group.nil? || group['subgroups'].nil? || guide.data['guide_subgroup'].nil? + subgroup = group.nil? ? '' : group['subgroups'][guide.data['guide_subgroup']] + end + + guide.get_sections.each do | section | + url = guide.url + unless section[:title].nil? + url = url + '#' + section[:title].slugize + end + + document = { + 'objectID' => url, + 'title' => guide.data['title'], + 'sectionTitle' => section[:title], + 'url' => url, + 'urlDisplay' => guide.url, + 'content' => HTMLEntities.new.decode(section[:contents]), + 'group' => group.nil? ? '' : group['title'], + 'subgroup' => subgroup.nil? ? '' : subgroup['title'], + 'type' => 'guide', + 'randomCode' => @random_code + } + documents << document + end + end + + @indexes['guides'].save_objects(documents) + end + + def generate_none_guide_guides + documents = [] + gs_pages = @site.pages.select { |page| page.data['search_index'] } + gs_pages.each do |page| + page.get_sections.each do |section| + url = page.url + url = url + '#' + section[:title].slugize unless section[:title].nil? + document = { + 'objectID' => url, + 'title' => page.data['title'], + 'sectionTitle' => section[:title], + 'url' => url, + 'urlDisplay' => page.url, + 'content' => HTMLEntities.new.decode(section[:contents]), + 'group' => page.data['search_group'], + 'subgroup' => page.data['sub_group'], + 'type' => 'not-guide', + 'randomCode' => @random_code + } + documents << document + end + end + @indexes['guides'].save_objects(documents) + end + + def generate_other + documents = @site.data['search-other'].map do |other| + { + 'objectID' => other['id'], + 'title' => other['title'], + 'url' => other['url'], + 'content' => other['description'], + 'randomCode' => @random_code + } + end + @indexes['other'].save_objects(documents) + end + + def doc_language_rank + { + 'c' => 10, + 'rockyjs' => 9, + 'pebblekit_js' => 8, + 'pebblekit_android' => 6, + 'pebblekit_ios' => 4 + } + end + end +end diff --git a/devsite/plugins/generator_docs.rb b/devsite/plugins/generator_docs.rb new file mode 100644 index 00000000..aa43faac --- /dev/null +++ b/devsite/plugins/generator_docs.rb @@ -0,0 +1,166 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'open-uri' +require 'zip' +require 'nokogiri' + +require_relative '../lib/pebble_documentation_pebblekit_android.rb' +require_relative '../lib/pebble_documentation_c.rb' +require_relative '../lib/pebble_documentation_js.rb' +require_relative '../lib/pebble_documentation_pebblekit_ios.rb' +require_relative '../lib/toc_generator.rb' + +OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax') +OpenURI::Buffer.const_set 'StringMax', 0 + +# Master plugins for processing the documentation on the site. +# The actual work is done in individual classes for each language type. +# Each class generates three types of data: +# - Symbols +# - Pages +# - Tree +# +# The Symbols are a list of objects that are things such as methods, classes +# or enums, that are used to populate the search indexes, and power the double +# backtick docs linking. +# +# The Pages are the Jekyll pages that will be part of the built site and are +# what the user will see. +# +# The Tree is used to build the site navigation. +# +# Note: The docs_url variable is created from the environment. +# See environment.md for more information. + +module Jekyll + class DocsGenerator < Generator + priority :high + + def generate(site) + @site = site + @docs = { + symbols: [], + pages: [], + tree: {} + } + if @site.config['docs_url'].nil? || @site.config['docs_url'].empty? + Jekyll.logger.warn( + 'Config Warning:', + 'You did not provide a DOCS_URL environment variable.' + ) + elsif !@site.config['skip_docs'].nil? && (@site.config['skip_docs'] == 'true') + Jekyll.logger.info('Docs Generation:', 'Skipping documentation generation...') + else + Jekyll.logger.info('Docs Generation:', 'Generating pages...') + generate_docs + render_pages + Jekyll.logger.info('Docs Generation:', 'Done.') + end + set_data + end + + private + + def generate_docs + # The order of these functions will determine the order of preference + # when looking up symbols e.g. double backticks + # DO NOT CHANGE THE ORDER UNLESS YOU KNOW WHAT YOU ARE DOING + generate_docs_c + generate_docs_c_preview unless @site.data['docs']['c_preview'].nil? + generate_docs_rocky_js + generate_docs_pebblekit_js + generate_docs_pebblekit_android + generate_docs_pebblekit_ios + end + + def render_pages + @docs[:pages].each { |page| @site.pages << page } + end + + def set_data + # A somewhat ugly hack to let the Markdown parser have access + # to this data. + @site.config[:docs] = @docs + @site.data['docs'] = @docs + # Another ugly hack to make accessing the data much easier from Liquid. + @site.data['docs_tree'] = JSON.parse(JSON.dump(@docs[:tree])) + @site.data['symbols'] = JSON.parse(JSON.dump(@docs[:symbols])) + end + + def generate_docs_c + docs = Pebble::DocumentationC.new( + @site, + @site.config['docs_url'] + @site.data['docs']['c'], + '/docs/c/' + ) + load_data(docs, :c) + end + + def generate_docs_c_preview + docs = Pebble::DocumentationC.new( + @site, + @site.config['docs_url'] + @site.data['docs']['c_preview'], + '/docs/c/preview/', + 'c_preview' + ) + load_data(docs, :c_preview) + end + + def generate_docs_rocky_js + docs = Pebble::DocumentationJs.new( + @site, + @site.data['docs']['rocky_js'], + '/docs/rockyjs/', + 'rockyjs', + true + ) + load_data(docs, :rockyjs) + end + + def generate_docs_pebblekit_js + docs = Pebble::DocumentationJs.new( + @site, + @site.data['docs']['pebblekit_js'], + '/docs/pebblekit-js/', + 'pebblekit_js' + ) + load_data(docs, :pebblekit_js) + end + + def generate_docs_pebblekit_android + docs = Pebble::DocumentationPebbleKitAndroid.new( + @site, + @site.config['docs_url'] + @site.data['docs']['pebblekit_android'] + ) + load_data(docs, :pebblekit_android) + end + + def generate_docs_pebblekit_ios + docs = Pebble::DocumentationPebbleKitIos.new( + @site, + @site.config['docs_url'] + @site.data['docs']['pebblekit_ios'], + '/docs/pebblekit-ios/' + ) + load_data(docs, :pebblekit_ios) + end + + def load_data(docs, type) + @docs[:tree][type] = [] + docs.load_symbols(@docs[:symbols]) + docs.create_pages(@docs[:pages]) + docs.build_tree(@docs[:tree][type]) + end + end +end diff --git a/devsite/plugins/generator_examples.rb b/devsite/plugins/generator_examples.rb new file mode 100644 index 00000000..fa365d2e --- /dev/null +++ b/devsite/plugins/generator_examples.rb @@ -0,0 +1,65 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Jekyll + class GeneratorExamples < Generator + def initialize(_config) + end + + def generate(site) + @site = site + process_tags + process_languages + process_hardware_platforms + @site.data['examples_metadata'] = { + 'tags' => @tags, + 'languages' => @languages, + 'hardware_platforms' => @hardware_platforms + } + end + + def process_tags + @tags = {} + @site.data['examples'].each do |example| + next if example['tags'].nil? + example['tags'].each do |tag| + @tags[tag] = { 'count' => 0 } unless @tags.has_key?(tag) + @tags[tag]['count'] += 1 + end + end + end + + def process_languages + @languages = {} + @site.data['examples'].each do |example| + next if example['languages'].nil? + example['languages'].each do |language| + @languages[language] = { 'count' => 0 } unless @languages.has_key?(language) + @languages[language]['count'] += 1 + end + end + end + + def process_hardware_platforms + @hardware_platforms = {} + @site.data['examples'].each do |example| + next if example['hardware_platforms'].nil? + example['hardware_platforms'].each do |hw| + @hardware_platforms[hw] = { 'count' => 0 } unless @hardware_platforms.has_key?(hw) + @hardware_platforms[hw]['count'] += 1 + end + end + end + end +end diff --git a/devsite/plugins/generator_guides.rb b/devsite/plugins/generator_guides.rb new file mode 100644 index 00000000..5a97ed05 --- /dev/null +++ b/devsite/plugins/generator_guides.rb @@ -0,0 +1,94 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'htmlentities' +require 'algoliasearch' +require 'slugize' +require 'dotenv' + +module Jekyll + + class GeneratorGuides < Generator + + priority :highest + + def initialize(config) + end + + def generate(site) + @site = site + site.collections['guides'].docs.each do |guide| + group = find_group(guide) + subgroup = find_subgroup(guide, group) + guide.data['group_data'] = group + guide.data['subgroup_data'] = subgroup + unless subgroup.nil? + subgroup['guides'] << { + 'title' => guide.data['title'], + 'url' => guide.url, + 'menu' => guide.data['menu'], + 'order' => guide.data['order'], + 'summary' => guide.data['description'] + } + else + unless group.nil? + group['guides'] << { + 'title' => guide.data['title'], + 'url' => guide.url, + 'menu' => guide.data['menu'], + 'order' => guide.data['order'], + 'summary' => guide.data['description'] + } + end + end + end + + site.data['guides'] = [] if site.data['guides'].nil? + + site.data['guides'].each do |id, group| + group['url'] = "/guides/#{id}/" + if group['subgroups'].nil? + group['subgroups'] = [] + next + end + group['subgroups'].each do |id, subgroup| + subgroup['url'] = "#{group['url']}#{id}/" + end + end + end + + def find_group(guide) + @site.data['guides'].each do |id, group| + if id == guide.data['guide_group'] + group['guides'] = [] if group['guides'].nil? + return group + end + end + nil + end + + def find_subgroup(guide, group) + return if group.nil? || group['subgroups'].nil? + group['subgroups'].each do |id, subgroup| + if id == guide.data['guide_subgroup'] + subgroup['guides'] = [] if subgroup['guides'].nil? + return subgroup + end + end + nil + end + + end + +end diff --git a/devsite/plugins/generator_meetups.rb b/devsite/plugins/generator_meetups.rb new file mode 100644 index 00000000..4bdb4284 --- /dev/null +++ b/devsite/plugins/generator_meetups.rb @@ -0,0 +1,35 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'googlestaticmap' + +module Jekyll + class GeneratorMeetups < Generator + def initialize(config) + end + + def generate(site) + @site = site + map = GoogleStaticMap.new(:width => 700, :height => 500) + site.data['meetups'].each do |meetup| + map.markers << MapMarker.new(:color => "0x9D49D5FF", + :location => MapLocation.new(:latitude => meetup['pin']['latitude'], + :longitude => meetup['pin']['longitude'] + ) + ) + end + @site.data['meetups_map_url'] = map.url(:auto) + end + end +end diff --git a/devsite/plugins/generator_minify_js.rb b/devsite/plugins/generator_minify_js.rb new file mode 100644 index 00000000..335c4f4f --- /dev/null +++ b/devsite/plugins/generator_minify_js.rb @@ -0,0 +1,58 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'uglifier' +require 'digest' + +module Jekyll + # Jekyll Generator for concatenating and minifying JS for production site + class GeneratorMinifyJS < Generator + priority :highest + + def initialize(_) + end + + def generate(site) + return if site.config['rack_env'] == 'development' + @site = site + @tmp_dir = File.join(site.source, '../tmp/') + @js_dir = 'assets/js/' + @tmp_js_dir = File.join(@tmp_dir, @js_dir) + libs_js = uglify_libs + libs_md5 = Digest::MD5.hexdigest(libs_js) + @site.data['js']['lib_hash'] = libs_md5 + create_libs_js(libs_js, libs_md5) + end + + private + + def uglify_libs + ugly_libs = [] + @site.data['js']['libs'].each do |lib| + lib_path = File.join(@site.source, 'assets', lib['path']) + ugly_libs << Uglifier.compile(File.read(lib_path)) + end + ugly_libs.join("\n\n") + end + + def create_libs_js(js, md5) + FileUtils.mkdir_p(@tmp_js_dir) + File.open(File.join(@tmp_js_dir, "libs-#{md5}.js"), 'w') do |f| + f.write(js) + end + @site.static_files << Jekyll::StaticFile.new(@site, @tmp_dir, @js_dir, + "libs-#{md5}.js") + end + end +end diff --git a/devsite/plugins/generator_redirects.rb b/devsite/plugins/generator_redirects.rb new file mode 100644 index 00000000..edc34173 --- /dev/null +++ b/devsite/plugins/generator_redirects.rb @@ -0,0 +1,62 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Jekyll + + class GeneratorRedirects < Generator + + priority :lowest + + def initialize(config) + end + + def generate(site) + @site = site + site.data['redirects'].each do |redirect| + if is_infinite_redirect?(redirect[0], redirect[1]) + Jekyll.logger.warn "Redirect Warning:", "Skipping redirect of #{redirect[0]} to #{redirect[1]}" + next + end + @site.pages << RedirectPage.new(@site, @site.source, File.dirname(redirect[0]), File.basename(redirect[0]), redirect[1]) + end + end + + private + + # Returns true if the redirect pair (from, to) will cause an infinite + # redirect. + def is_infinite_redirect?(from, to) + return true if from == to + return true if File.basename(from) == 'index.html' && File.dirname(from) == File.dirname(to + 'index.html') + false + end + + end + + class RedirectPage < Page + + def initialize(site, base, dir, name, redirect_to) + @site = site + @base = base + @dir = dir + @name = name.empty? ? 'index.html' : name + + self.process(@name) + self.read_yaml(File.join(base, '_layouts', 'utils'), 'redirect_permanent.html') + self.data['redirect_to'] = redirect_to + end + + end + +end \ No newline at end of file diff --git a/devsite/plugins/jekyll_convertible.rb b/devsite/plugins/jekyll_convertible.rb new file mode 100644 index 00000000..d0494ad0 --- /dev/null +++ b/devsite/plugins/jekyll_convertible.rb @@ -0,0 +1,54 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'redcarpet' +require_relative '../lib/search_markdown' + +module Jekyll + + module Convertible + + def get_output + process_search + @search_markdown.get_contents + end + + def get_sections + process_search + @search_markdown.get_sections + end + + private + + def process_search + unless @search_markdown.nil? + return + end + @search_markdown = Pebble::SearchMarkdown.new() + redcarpet = Redcarpet::Markdown.new(@search_markdown, + fenced_code_blocks: true, + autolink: true, + tables: true, + no_intra_emphasis: true, + strikethrough: true, + highlight: true) + payload = {} + info = { :filters => [Jekyll::Filters], :registers => { :site => site, :page => payload['page'] } } + raw_content = render_liquid(content, payload, info, '.') + redcarpet.render(raw_content) + end + + end + +end \ No newline at end of file diff --git a/devsite/plugins/jekyll_document.rb b/devsite/plugins/jekyll_document.rb new file mode 100644 index 00000000..4fb6c5be --- /dev/null +++ b/devsite/plugins/jekyll_document.rb @@ -0,0 +1,85 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative '../lib/toc_generator' + +module Jekyll + + class Document + + include Convertible + + alias_method :parent_to_liquid, :to_liquid + + def to_liquid + Utils.deep_merge_hashes parent_to_liquid, { + 'toc' => toc, + 'related_docs' => related_docs + } + end + + private + + def toc + unless @toc + generate_toc if data['generate_toc'] + end + (@toc.nil? || @toc.empty?) ? nil : @toc + end + + def related_docs + # Skip the warning, we don't want docs or links to them + if !@site.config['skip_docs'].nil? && (@site.config['skip_docs'] == 'true') + return + end + + return nil if data['related_docs'].nil? + + docs = data['related_docs'].map do | doc | + # Use existing doc data if it exists + if !doc.nil? and doc.is_a?(Hash) and doc.has_key?("name") and doc.has_key?("url") + doc + else + # use nil if data is formated in an unexpected way + if doc.nil? or !doc.is_a? String + next + else + # Otherwise search for the symbol + symbol = @site.config[:docs][:symbols].find do |symbol| + symbol[:name].downcase == doc.downcase + end + + if symbol.nil? + Jekyll.logger.warn "Related Warning:", "Could not find symbol '#{doc}' in '#{data['title']}'" + next + else + { + 'name' => symbol[:name], + 'url' => symbol[:url], + } + end + end + end + end + + end + + def generate_toc + generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1) + @toc = generator.generate(content) + end + + end + +end diff --git a/devsite/plugins/jekyll_page.rb b/devsite/plugins/jekyll_page.rb new file mode 100644 index 00000000..ec4c9e00 --- /dev/null +++ b/devsite/plugins/jekyll_page.rb @@ -0,0 +1,45 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative '../lib/toc_generator' + +# Overriding the Jekyll Page class to do magic + +module Jekyll + + class Page + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w[ + toc + ]) + end + + private + + def toc + unless @toc + generate_toc if data['generate_toc'] + end + (@toc.nil? || @toc.empty?) ? nil : @toc + end + + def generate_toc + generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1) + @toc = generator.generate(content) + end + + end + +end diff --git a/devsite/plugins/jekyll_post.rb b/devsite/plugins/jekyll_post.rb new file mode 100644 index 00000000..23182467 --- /dev/null +++ b/devsite/plugins/jekyll_post.rb @@ -0,0 +1,45 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative '../lib/toc_generator' + +# Overriding the Jekyll Page class to do magic + +module Jekyll + + class Post + + def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID) + super(attrs + %w[ + toc + ]) + end + + private + + def toc + unless @toc + generate_toc + end + (@toc.nil? || @toc.empty?) ? nil : @toc + end + + def generate_toc + generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1) + @toc = generator.generate(content) + end + + end + +end diff --git a/devsite/plugins/pebble_markdown_parser.rb b/devsite/plugins/pebble_markdown_parser.rb new file mode 100644 index 00000000..d93258df --- /dev/null +++ b/devsite/plugins/pebble_markdown_parser.rb @@ -0,0 +1,323 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'redcarpet' +require 'pygments' +require 'slugize' +require 'nokogiri' + +module Jekyll + module Converters + # Jekyll Markdown Converter wrapper for Redcarpet using the PebbleMarkdown + # HTML render class. + class Markdown::PebbleMarkdownParser + def initialize(config) + @site_config = config + end + + def convert(content) + content = '' if content.nil? + pbl_md = PebbleMarkdown.new(@site_config) + Redcarpet::Markdown.new(pbl_md, + fenced_code_blocks: true, + autolink: true, + tables: true, + no_intra_emphasis: true, + strikethrough: true, + highlight: true).render(content) + end + + # Redcarpet HTML render class to handle the extra functionality. + class PebbleMarkdown < Redcarpet::Render::HTML + def initialize(config) + @site_config = config + super() + end + + def preprocess(document) + process_links_with_double_backticks(document) + process_double_backticks(document) + document + end + + # Add ID and anchors to all headers. + def header(text, header_level) + if text.include?('<') + id = Nokogiri::HTML(text).text.slugize + else + id = text.slugize + end + str = "" + str += text + str += "" + str + end + + def paragraph(text) + if (match = /^\^(CP|LC)\^/.match(text)) + "

#{text[(match[1].length + 2)..-1].strip}

" + else + "

#{text}

" + end + end + + # Use Pygments to generate the syntax highlighting markup. + def block_code(code, language) + classes = ['highlight'] + if /^nc\|/.match(language) + classes << 'no-copy' + language = language[3..-1] + end + if language == 'text' + "
#{code}
" + else + set_classes(Pygments.highlight(code, lexer: language), classes) + end + end + + def link(url, title, content) + if content == 'EMBED' + embed(url) + else + classes = [] + if /^DOCS:/.match(title) + title = title[5..-1] + classes << 'link--docs' + end + # URL begins with a single slash (but not double slash) + url = baseurl + url if %r{^/[^/]}.match(url) + data_str = '' + if (match = regex_button.match(content)) + classes << 'btn' + classes << 'btn--markdown' + classes.concat(match[3].split(',').map { |cls| 'btn--' + cls }) + content = match[1] + end + if (match = regex_link_data.match(title)) + match[3].split(',').each do |item| + item = item.split(':') + data_str += ' data-' + item[0] + '="' + item[1] + '"' + end + title = match[1] + end + "
#{content}" + end + end + + # Better image handling. + # * Add size specificiations (taken from RDiscount) + # * Prepend the site baselink to images that beings with / + # TODO: Handle the cases where image link begins with // + # TODO: Maybe add additional style choices (centered, inline, etc) + def image(link, title, alt_text) + if (size_match = /^(.*)\ =([0-9]+)x?([0-9]*)$/.match(link)) + link = size_match[1] + width = size_match[2] + height = size_match[3] + end + + classes = [] + if (match = regex_button.match(alt_text)) + classes.concat(match[3].split(',')) + alt_text = match[1] + end + + link = asset_path + link if %r{^/[^/]}.match(link) + + img_str = "
' \ + '" \ + '
' + end + + def youtube_playlist(id) + '
' \ + '" \ + '
' + end + + def vimeo(id) + '
' \ + '" \ + '
' + end + + def slideshare(id) + '
' \ + "'\ + '
' + end + + def gist(id) + '
' \ + "" \ + '
' + end + + def baseurl + @site_config['baseurl'] || '' + end + + def asset_path + @site_config['asset_path'] || '' + end + + def link_sdk(url, title, content) + + end + + def regex_youtube_video + %r{youtube\.com/(watch\?v=|v/|embed/)([a-z0-9A-Z\-_]*)} + end + + def regex_youtube_playlist + %r{^(https?://)?([w]{3}\.)?youtube\.com/playlist\?list=([a-z0-9A-Z\-]*)} + end + + def regex_vimeo_video + %r{vimeo.com/video/([0-9]+)} + end + + def regex_slideshare + %r{slideshare.net/slideshow/embed_code/key/([a-z0-9A-Z]*)} + end + + def regex_gist + %r{^(https?://)?gist.github\.com/(.*)} + end + + def regex_button + /^(.*)\ (>|>)\{?([a-z,0-9\-]*)\}?$/ + end + + def regex_link_data + /^(.*)\ (>|>)\{([a-z\-_,:0-9A-Z]+)\}$/ + end + + def shortcode_to_platform(shortcode) + platforms = { + 'CP' => 'cloudpebble', + 'LC' => 'local' + } + platforms[shortcode] + end + end + end + end +end diff --git a/devsite/plugins/tag_alert.rb b/devsite/plugins/tag_alert.rb new file mode 100644 index 00000000..14ae4168 --- /dev/null +++ b/devsite/plugins/tag_alert.rb @@ -0,0 +1,42 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Jekyll + class AlertBlock < Liquid::Block + alias_method :render_block, :render + + def initialize(tag_name, text, tokens) + super + @type = text.strip + end + + def render(context) + site = context.registers[:site] + converter = site.find_converter_instance(::Jekyll::Converters::Markdown) + content = converter.convert(render_block(context)) + + if @type == "important" + return "
" << "Important
" << "#{content}" << "
" + end + if @type == "notice" + return "
" << "Notice
" << "#{content}" << "
" + end + + Jekyll.logger.error "Liquid Error:", "Alert type '#{@type}' is not valid. Use 'important' or 'notice'." + return '' + end + end +end + +Liquid::Template.register_tag('alert', Jekyll::AlertBlock) diff --git a/devsite/plugins/tag_asset_css.rb b/devsite/plugins/tag_asset_css.rb new file mode 100644 index 00000000..2f1458a6 --- /dev/null +++ b/devsite/plugins/tag_asset_css.rb @@ -0,0 +1,31 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Liquid tag for including a style tag. +class TagAssetCss < Liquid::Tag + def initialize(tag_name, text, tokens) + super + @text = text + end + + def render(context) + style = context[@text] + site = context.registers[:site] + unless %r{^//}.match(style) + style = "#{site.config['asset_path']}/css/#{style}.css" + end + "" + end +end +Liquid::Template.register_tag('asset_css', TagAssetCss) diff --git a/devsite/plugins/tag_asset_js.rb b/devsite/plugins/tag_asset_js.rb new file mode 100644 index 00000000..3e97600d --- /dev/null +++ b/devsite/plugins/tag_asset_js.rb @@ -0,0 +1,31 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Liquid tag for including a script tag. +class TagAssetJs < Liquid::Tag + def initialize(tag_name, text, tokens) + super + @text = text + end + + def render(context) + script = context[@text] + site = context.registers[:site] + unless %r{^//}.match(script) + script = "#{site.config['asset_path']}/js/#{script}.js" + end + "" + end +end +Liquid::Template.register_tag('asset_js', TagAssetJs) diff --git a/devsite/plugins/tag_author_link.rb b/devsite/plugins/tag_author_link.rb new file mode 100644 index 00000000..7c271661 --- /dev/null +++ b/devsite/plugins/tag_author_link.rb @@ -0,0 +1,35 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Creates a link to a blog post author page and uses their full name when known. +class TagAuthorLink < Liquid::Tag + def initialize(tag_name, text, tokens) + super + @text = text + end + + def render(context) + author_name = context[@text] + site = context.registers[:site] + author = site.data['authors'][author_name] + if author + url = "#{site.baseurl}/blog/authors/#{author_name}/" + "#{author['name']}" + else + author_name + end + end +end + +Liquid::Template.register_tag('author_link', TagAuthorLink) diff --git a/devsite/plugins/tag_author_photo.rb b/devsite/plugins/tag_author_photo.rb new file mode 100644 index 00000000..1325744a --- /dev/null +++ b/devsite/plugins/tag_author_photo.rb @@ -0,0 +1,36 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class TagAuthorPhoto < Liquid::Tag + def initialize(tag_name, text, tokens) + super + pieces = text.split(' ') + @name = pieces[0] + @size = pieces[1].to_i + end + + def render(context) + author_name = context[@name] + site = context.registers[:site] + author = site.data['authors'][author_name] + unless author && author['photo'] + author = site.data['authors']['pebble'] + end + photo = author['photo'] + photo = site.config['asset_path'] + photo if %r{^/[^/]}.match(photo) + "" + end +end + +Liquid::Template.register_tag('author_photo', TagAuthorPhoto) diff --git a/devsite/plugins/tag_cloudpebble_edit_gist.rb b/devsite/plugins/tag_cloudpebble_edit_gist.rb new file mode 100644 index 00000000..22e8edb6 --- /dev/null +++ b/devsite/plugins/tag_cloudpebble_edit_gist.rb @@ -0,0 +1,58 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Liquid inline tag to produce an Edit Gist in CloudPebble button +class TagCloudPebbleEditGist < Liquid::Tag + def initialize(tag_name, gist_id, tokens) + super + @gist_id = gist_id.strip + end + + def render(context) + page = context.registers[:page] + extension = File.extname(page['name']) + if extension == '.md' + render_markdown + else + render_html + end + end + + def render_markdown + "[#{content} >{#{markdown_classes}}](#{url})" + end + + def render_html + "#{content}" + end + + private + + def url + "https://cloudpebble.net/ide/gist/#{@gist_id}" + end + + def html_classes + 'btn btn--wide btn--pink' + end + + def markdown_classes + 'wide,pink' + end + + def content + 'Edit in CloudPebble' + end +end +Liquid::Template.register_tag('cloudpebble_edit_gist', TagCloudPebbleEditGist) diff --git a/devsite/plugins/tag_git_contributors.rb b/devsite/plugins/tag_git_contributors.rb new file mode 100644 index 00000000..0adfebf8 --- /dev/null +++ b/devsite/plugins/tag_git_contributors.rb @@ -0,0 +1,43 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Liquid tag that displays the names of everyone who contributed on the file. +class TagGitContributors < Liquid::Tag + def initialize(tag_name, text, tokens) + super + @text = text + end + + def render(context) + list = '
    ' + contributors(context).each do |name| + list += "
  • #{name}
  • " unless name.empty? + end + list += '
' + list + end + + private + + def contributors(context) + site = context.registers[:site] + page = context[@text] + file_path = page['relative_path'] || page['path'] + full_path = './' + site.config['source'] + file_path + names = `git log --follow --format='%aN |' "#{full_path}" | sort -u` + names.split('|').map { |name| name.strip } + end +end + +Liquid::Template.register_tag('git_contributors', TagGitContributors) diff --git a/devsite/plugins/tag_guide_link.rb b/devsite/plugins/tag_guide_link.rb new file mode 100644 index 00000000..fac4d0ec --- /dev/null +++ b/devsite/plugins/tag_guide_link.rb @@ -0,0 +1,67 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class TagGuideLink < Liquid::Tag + def initialize(tag_name, text, tokens) + super + @text = text.strip + end + + def make_html(title, url) + return "#{title}" + end + + def render(context) + guide_path = @text.split('#')[0].strip + guide_hash = (@text.split('#').length > 1 ? @text.split('#')[1] : '').strip + if guide_hash.length > 1 + guide_hash = (guide_hash.split(' ')[0]).strip + end + + # Custom title? + guide_title = nil + index = @text.index('"') + if index != nil + guide_title = (@text.split('"')[1]).strip + guide_title = guide_title.gsub('"', '') + guide_path = guide_path.split(' ')[0] + end + + site = context.registers[:site] + site.collections['guides'].docs.each do |guide| + path = guide.relative_path.sub(/^_guides\//, '').sub(/\.md$/, '') + + # Check if it's a 'section/guide' path + if path.index('/') != nil + if path == guide_path + return make_html(guide_title != nil ? guide_title : guide.data['title'], + "#{guide.url}#{guide_hash == '' ? '' : "##{guide_hash}"}") + end + end + + # Check if it's a 'section' path + site.data['guides'].each do |id, group| + if id == guide_path + return make_html(guide_title != nil ? guide_title : group['title'], "/guides/#{guide_path}") + end + end + end + + # No match + Jekyll.logger.error "Liquid Error:", "Could not find the guide or section for #{@text}." + return '' + end +end + +Liquid::Template.register_tag('guide_link', TagGuideLink) diff --git a/devsite/plugins/tag_highlight.rb b/devsite/plugins/tag_highlight.rb new file mode 100644 index 00000000..396394b8 --- /dev/null +++ b/devsite/plugins/tag_highlight.rb @@ -0,0 +1,35 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'pygments' + +# Liquid block tag to run code through Pygments syntax highlighter. +class Highlight < Liquid::Block + def initialize(tag_name, markup, tokens) + super + options = JSON.parse(markup) + return unless options + @language = options['language'] + @classes = options['classes'] + @options = options['options'] || {} + end + + def render(context) + str = Pygments.highlight(super.strip, lexer: @language, options: @options) + str.gsub!(/
/,
+              "
")
+    str
+  end
+end
+Liquid::Template.register_tag('highlight', Highlight)
diff --git a/devsite/plugins/tag_if_starts_with.rb b/devsite/plugins/tag_if_starts_with.rb
new file mode 100644
index 00000000..89e466ce
--- /dev/null
+++ b/devsite/plugins/tag_if_starts_with.rb
@@ -0,0 +1,38 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class TagIfStartsWith < Liquid::Block
+  def initialize(tag_name, text, tokens)
+    super
+    matches = /^([A-Za-z\.\_\-\/]+) ([A-Za-z\.\_\-\/\']+)$/.match(text.strip)
+    @pieces = {
+      :outer => matches[1],
+      :inner => matches[2]
+    }
+    @text = text
+  end
+
+  def render(context)
+    outer = context[@pieces[:outer]]
+    inner = context[@pieces[:inner]]
+    if inner.nil? || outer.nil?
+      return ""
+    end
+    if outer.downcase.start_with?(inner.downcase)
+      return super.to_s.strip
+    end
+    ""
+  end
+end
+Liquid::Template.register_tag('if_starts_with', TagIfStartsWith)
diff --git a/devsite/plugins/tag_import_js.rb b/devsite/plugins/tag_import_js.rb
new file mode 100644
index 00000000..e3325bba
--- /dev/null
+++ b/devsite/plugins/tag_import_js.rb
@@ -0,0 +1,30 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class TagImportJs < Liquid::Tag
+
+  def initialize(tag_name, text, tokens)
+    super
+    @text = text
+  end
+
+  def render(context)
+    site = context.registers[:site]
+    filename = File.join(site.source, "_js", @text).strip
+    File.read(filename)
+  end
+end
+
+Liquid::Template.register_tag('import_js', TagImportJs)
+
diff --git a/devsite/plugins/tag_markdown.rb b/devsite/plugins/tag_markdown.rb
new file mode 100644
index 00000000..ee5147fe
--- /dev/null
+++ b/devsite/plugins/tag_markdown.rb
@@ -0,0 +1,35 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Jekyll
+  class MarkdownBlock < Liquid::Block
+    alias_method :render_block, :render
+
+    def initialize(tag_name, markup, tokens)
+      super
+    end
+
+    # Uses the default Jekyll markdown parser to
+    # parse the contents of this block
+    #
+    def render(context)
+      site = context.registers[:site]
+      converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
+      converter.convert(render_block(context))
+    end
+  end
+end
+
+Liquid::Template.register_tag('markdown', Jekyll::MarkdownBlock)
+
diff --git a/devsite/plugins/tag_platform.rb b/devsite/plugins/tag_platform.rb
new file mode 100644
index 00000000..f0ea4b92
--- /dev/null
+++ b/devsite/plugins/tag_platform.rb
@@ -0,0 +1,39 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Jekyll
+  class PlatformBlock < Liquid::Block
+    alias_method :render_block, :render
+
+    def initialize(tag_name, text, tokens)
+      super
+      @platform = text.strip
+    end
+
+    def render(context)
+      if (@platform == "local") || (@platform == "cloudpebble")
+        site = context.registers[:site]
+        converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
+        content = converter.convert(super)
+
+        return "
#{content}
" + end + + Jekyll.logger.error "Liquid Error:", "Platform '#{@platform}' is not valid. Use 'local' or 'cloudpebble'." + return '' + end + end +end + +Liquid::Template.register_tag('platform', Jekyll::PlatformBlock) diff --git a/devsite/plugins/tag_screenshot_viewer.rb b/devsite/plugins/tag_screenshot_viewer.rb new file mode 100644 index 00000000..280585dc --- /dev/null +++ b/devsite/plugins/tag_screenshot_viewer.rb @@ -0,0 +1,50 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'json' + +class TagScreenshotViewer < Liquid::Block + def render(context) + site = context.registers[:site] + data = JSON.parse(super) + + viewer_html = '
' + + viewer_html += '
' + data['platforms'].each do |platform| + viewer_html += "

#{platform['hw']}

" + end + viewer_html += '
' + + viewer_html += '
' + data['platforms'].each do |platform| + viewer_html += "
" + image_url = make_image_url(data, platform) + viewer_html += "" + viewer_html += '
' + end + viewer_html += '
' + + viewer_html += '
' + viewer_html + end + + private + + def make_image_url(data, platform) + File.dirname(data['image']) + '/' + File.basename(data['image'], File.extname(data['image'])) + + "~#{platform['hw']}" + File.extname(data['image']) + end +end +Liquid::Template.register_tag('screenshot_viewer', TagScreenshotViewer) diff --git a/devsite/scripts/generate-rocky-docs.sh b/devsite/scripts/generate-rocky-docs.sh new file mode 100755 index 00000000..4c14df53 --- /dev/null +++ b/devsite/scripts/generate-rocky-docs.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +documentation build ./js-docs/rocky -f json > source/_data/jsdocs-rocky.json +documentation build ./js-docs/pkjs -f json > source/_data/jsdocs-pkjs.json diff --git a/devsite/scripts/update-templates.sh b/devsite/scripts/update-templates.sh new file mode 100755 index 00000000..2c0d6d40 --- /dev/null +++ b/devsite/scripts/update-templates.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Updates the compiled Handlebars templates file from the source templates. + +handlebars source/_js/templates/*.tpl -f source/assets/js/templates.js -e tpl diff --git a/devsite/scripts/video-encode.sh b/devsite/scripts/video-encode.sh new file mode 100755 index 00000000..fda19d2b --- /dev/null +++ b/devsite/scripts/video-encode.sh @@ -0,0 +1,33 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SOURCE=$1; +DIR=$(dirname "$SOURCE") +FILENAME=$(basename "$SOURCE") +EXT="${FILENAME##*.}" +FILENAME="${FILENAME%.*}" + +if [ -z "$1" ] + then + echo "\nUsage: $0 \n" + exit 1; +fi + +echo "Creating OGV file..." +ffmpeg -i $SOURCE -loglevel panic -q 5 -pix_fmt yuv420p -acodec libvorbis -vcodec libtheora $DIR/$FILENAME.ogv; +echo "Creating WEBM file..." +ffmpeg -i $SOURCE -loglevel panic -c:v libvpx -c:a libvorbis -pix_fmt yuv420p -quality good -b:v 2M -crf 5 -vf "scale=trunc(in_w/2)*2:trunc(in_h/2)*2" $DIR/$FILENAME.webm; +echo "Creating PNG poster..." +ffmpeg -i $SOURCE -loglevel panic -f image2 -ss 0 -vframes 1 $DIR/$FILENAME.png; +echo "Done!" diff --git a/devsite/source/404.html b/devsite/source/404.html new file mode 100644 index 00000000..0a7ab415 --- /dev/null +++ b/devsite/source/404.html @@ -0,0 +1,51 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: default +title: Oops! 404, Page not found +footer: true +error404: true +scripts: + - '404' + +menu_section: none +--- +
+ +
+
+
+
+

404 Page Not Found

+

+ Sorry, we couldn't find the page you were looking for. +

+

+ If you clicked a link on the site and it led you here, we would + appreciate you letting us know that we + broke something. +

+ + +
+
+
+
+
diff --git a/devsite/source/_changelogs/2.0-BETA0.md b/devsite/source/_changelogs/2.0-BETA0.md new file mode 100644 index 00000000..6ed6ee4a --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA0.md @@ -0,0 +1,42 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +title: Pebble SDK 2.0 BETA0 - Changelog +date: 2013-11-01 +--- + +This version is a preview of what will be publicly released soon as a BETA. This means that it is the last time we introduce large changes to the APIs, they will be much more stable in the future. + +It includes some last significant changes that will impact every application. + + * We have changed the format of the `wscript` file. **You must update your wscript file.** The easiest way to do this is to generate a new project with `pebble new-project` and use the generated `wscript`. + * Header files `pebble_os.h`, `pebble_app.h` and `pebble_fonts.h` are replaced by `pebble.h` + * `click_config_provider()` signature has changed and instead of filling a struct, you call `window_*_click_subscribe`. Please refer to the [Migration Guide](/guides/migration/). + * On AppMessage: + * We have changed the signature of most AppMessage functions. Please refer to the [Migration Guide](/guides/migration/). + * We have added functions to query the size of the AppMessage buffers. They still return the same value that in previous versions ... for now. + * We have added a [Mobile Developer Guide](/guides/communication/) covering PebbleKit iOS and Android. Please take a look at them, they should answer lots of questions. + * [PebbleKit Android Documentation](/guides/communication/using-pebblekit-android) is now available on the website and in the SDK `Documentation` folder. + * We have done a lot of work on PebbleKit JavaScript: + * The [documentation](/guides/communication/using-pebblekit-js) describes the new model for loading and stopping JavaScript apps. You should take a look. + * On Android only (for now) apps will automatically start when they get a message from Pebble. + * On Android only (for now) you can use the gear icon to open a configuration window on the phone. + * You can now call `Pebble.addEventListener` instead of `PebbleEventListener.addEventListener` + * DataLogging is now supported on Android, iOS6 and iOS7 + + * And of course we have fixed a large quantities of bugs. + +This is a private release under NDA. diff --git a/devsite/source/_changelogs/2.0-BETA1.md b/devsite/source/_changelogs/2.0-BETA1.md new file mode 100644 index 00000000..a8fa48cd --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA1.md @@ -0,0 +1,74 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA1 - Changelog +date: 2013-11-06 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + * ANCS Notifications (aka BLE notifications) are not supported for iOS users in this version + +Updates: + + * 2013 11 07: Added a firmware for watch with serial number starting with a 'Q' (aka hardware 1.5) + +## What has changed since BETA0 + +### User Bug Fixes and Feature Enhancements + + - Fixed crashing bugs on the iOS app. Users should experience improved stability. + - New iOS users no longer need to manage access to their address book in order to see Caller ID on their Pebble. + - The iOS app does not overflow the banner bar (at the top of the screen) on iOS7 + - The Pebble now can show >80 unread notifications, up from 8 previously. + - Backlight is triggered on a tap from any of the 6 axes of the watch + - Android app stability has been improved + - On Android, switching orientation while updating firmware does not stop the firmware update + - The music app now stays open rather than switching back to the menu after 1 minute + +### Known User Issues + + - The status indication button in the main screen sometimes repeatedly throbs green then red, repeatedly. + - (iOS7 users, iPhone4S and higher) If you select "Enable Notifications" and select the Cancel button in the system alert that comes up, it can take up to 30 seconds for the iOS app to allow selection of "Enable Notifications" again. As a workaround, if you launched this screen from the Status screen, you can hit the up arrow, then the red "Not receiving notifications" button, and retry enabling notifications again. + - In certain conditions if you enable and disable Airplane mode on your Pebble, you may need to restart the Pebble iOS app completely in order to re-enable notifications again + - On Android, you may need to restart the Pebble app after installing a new version of a JavaScript app to ensure that your changes are successfully loaded. + - On Android, use of HTML5 local storage does not guarantee data will be saved across sessions. + - Duplicate APP_LOG messages can be received while using the pebble tool; these are intermittent and developers should use timestamps to identify duplicates + - If there is not enough app heap remaining, some essential functions that allocate on that heap will fail, such as system fonts or persistent storage + - The iOS app can sometimes crash when opening a PBW file if it is not already running + +### Developer Bug Fixes and Enhancements (Major Feature Enhancements are covered in the SDK) + + - Apps now only need one Pebble specific header, pebble.h + - Exiting an app showing no windows will now not crash the Pebble + - Pebble will not crash when cancelling an already cancelled timer + - Pebble will not crash when cancelling an unregistered timer + - Holding the up or down buttons now cause repeated clicks in menus + - Changed the default stroke color to Black instead of White, as the default background color is White + - Apps now cannot overwrite the system memory, and will be terminated if they attempt to + - Int type changes on many APIs to ensure future compatability + - User data can be attached to a window + - The pebble tool displays an error message if you try to install an application that is not compatible with the target firmware + - The menu icon resource is displayed even if it is not the first resource + - Libpebble times out if no apps are installed + - The valid range for UUIDs has changed - see the developer documentation + - The Android app now installs bundles in Gmail attachments + - System fonts now show capital W, Q and O + - `pebble Install`` will now install even if the Android app is left in the “Update” screen + - Apps will not crash if a text layer is not large enough to hold the requested text + - `pebble install --logs` proceeds to tail logs even when install fails + - Apps will not crash when popping/removing already popped/removed windows from the stack + diff --git a/devsite/source/_changelogs/2.0-BETA2.md b/devsite/source/_changelogs/2.0-BETA2.md new file mode 100644 index 00000000..743ba620 --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA2.md @@ -0,0 +1,97 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA2 - Changelog +date: 2013-11-14 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + +## What has changed since BETA1 + +Overview: + + - We have included ANCS in 2.0 - iOS users will get all notifications + - We have added a screenshot tool + - We have increased the AppMessage buffer size for PebbleKit JS Apps + - We have changed a few firmware APIs to always pass parameter in this order: (buffer, size) + - We have fixed many bugs + +Known problems and bugs: + + - We are still working actively on improving datalogging on iOS and Android. If you wish to use this framework, please get in touch with us and tell us about your experience. + - JavaScript apps on Android will only run if the phone is turned on and the Pebble app running (the easiest way to check this is to bring it to the foreground). This will be fixed soon. + + - If you downloaded the SDK before 5pm PST on 2013-11-14, your API documentation is probably broken. We have fixed this and pushed a new release without updating the version number because there are absolutely no changes (except the doc is now there ;). + +### Firmware + + - Added support for ANCS + - Fix UI bug when getting phone calls + - Improved address book lookups when getting phone calls + - Changed the behaviour when an app is closed from PebbleKit: return to the last running app or watchface (instead of the launcher) + - Show malloc and free in the generated documentation + - Fix doc for AccelAxisType + - Do not animation a window disappearing if the window was pushed without animation + - Add `GCornersRight` in the documentation of `GCornerMask` + - Document `GTextOverflowMode` + - Document the return value of the `persist_*` functions + - Document `AppTimerCallback` + - When exiting an app, all unload handlers will be called for loaded windows + - Changed the order of parameters for `persist_read_data()`, `persist_read_string()`, `persist_write_data()`, `dict_calc_buffer_size()`, `dict_serialize_tuplets_to_buffer_with_iter()`: always ask for the pointer first and then the count or size + - Fix bug where the status bar would not be displayed properly + - Enabled Accelerometer high resolution output + - Automatically reset the accelerometer when app exits + - Removed the 1Hz accelerometer settings because it breaks the shake to backlight - Use peek() instead if you only need one sample per second. + - Updated the guaranteed minimum buffer sizes for appmessage. They are in fact 124 / 636. + - Fix bug where appLaunch commands would not be ACK'd + - Increased AppMessage buffer sizes for JavaScript apps: they get 2k in and out. + +### iOS App + + - Fixed several dataLogging bugs + - Fixed most common crashes reported by TestFlight + +### Android App + + - Fixed several dataLogging bugs + - Fixed most common crashes reported by TestFlight + +### PebbleKit iOS + + - DataLogging apps do not need to include an `appInfo.json` file anymore + - Use `setAppUUID` to give the UUID of the app you want to talk to + +### PebbleKit Android + + - Add `getWatchFWVersion()` to get a `FirmwareVersionInfo` object + - Add `isDataLoggingSupported` + +### SDK Tools + + - Added a `screenshot` command to the `pebble` tool + - Revert the change in the tool where we would enforce a specific range of uuids + - Improved error messages when the tools cannot be found + - Do not truncate log messages coming from the JavaScript console + - Only log app_log (and not system log) by default. Use `--verbose` to get all the logs. + +### Examples + + - Fix a bug in the dataspooling demo where sealions and pelicans got mixed up + - Fix PebbleKit Examples for the new `setAppUUID` style + - Fix examples to use the new parameter orders for `persist` functions + diff --git a/devsite/source/_changelogs/2.0-BETA3.md b/devsite/source/_changelogs/2.0-BETA3.md new file mode 100644 index 00000000..84bf0d39 --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA3.md @@ -0,0 +1,117 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA3 - Changelog +date: 2013-12-12 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + * If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. + +## What has changed since BETA2 + +Overview: + + - The Android app fixes a large number of JS-related bugs. + - The Android app fixes a bug where all messages sent to android would be automatically acknowledged. Your application should acknowledge app messages. + - Some new user features in the firmware: Notification settings (with Do Not Disturb), better Alarms + - Lots of small UI and stability fixes in Pebble. + +Known problems and bugs: + + +### Firmware + + - added a Notification menu in the Settings to disable Notifications and configure a DoNotDisturb time frame + - much better Alarm app with a nicer UI, snooze support, disabled alarms support + - fix bugs where incoming calls could cause the vibration to stay on continuously + - fix a rare condition where the accelerometer would crash if an interrupt comes too late or the accelerometer sent 0 samples + - fix accelerometer behaviour when only 1 sample is requested: only send one sample (instead of 2) + - fix a bug where an iOS device could disconnect just 1-2 seconds after connecting + - automatically reconnect when user leaves Airplane Mode + - show (in settings) that vibrations are disabled when Pebble is plugged + - improved the set date/time UI to use the new DateTime UI (as in Alarms) + - adjust the framebuffer offset for modal window transitions + - reduced BLE connection interval + - log more information when an application crashes + - do not crash if an app_message section is re-opened - display warning instead + - fix a bug which caused firmware updates to fail under some conditions (mostly android) + - appsync will only update a dictionary if it has enough memory to do so (instead of finding out half-way that it does not have enough memory) + - always return to the launcher after an app crash (to avoid a crash loop on a watchface) + - *_destroy() will accept NULL pointers without errors + - always go back to the top of the menu when launching the menu from a watchface (to make "blind" navigation easier) + - fix a bug where an actionbar button would still be "pressed" + - show Bluetooth Name on getting started screen + - automatically delete old apps that are not compatible with this firmware version + - accelerate scrolling on the menu + - use modal window icon in the status bar as long as the modal window is displayed + - Export dict_size so external developers don't have to do pointer math :) + - fix a bug where scrolling in a long list of notifications could cause the display to "bounce" + - fix a bug where lots of app logging messages could overflow the system task queue and kill app message + - API documentation completely reviewed and updated + - missed call modal views will timeout after 180s + - force quit app when the back button is held for 2 seconds + - menu_cell_basic_draw() now automatically center the text in the cell. If you do not want a subtitle, pass NULL (otherwise, space will be reserved for the subtitle). + - fixed some bluetooth settings to avoid duplicated messages (could cause screenshot to go over 100%, duplicated log entries, firmware upgrade to fail, etc) + - `peek()`ing the accelerometer is not allowed anymore when you have also subscribed to receive updates + - fix a bug where the accelerometer would get stuck after a few hours + +### iOS App + + - fix a bug where datalogging could dump messages to another phone on your local network + - fix a bug where datalogging would get into a deadlock + - fix a bug where the developer connection would appear active but would be closed + +### Android App + + - fix a number of cases where a JS app would not be launched + - fix bug where clicking the configure icon would not open the configuration view of an app + - fix a bug which caused every AppMessage sent to Android to be acknowledged by the system + - Select the Google Play Music App as the default music player + - fix support email to use the Pebble bluetooth name instead of the last four digits of the serial + - if there is an error when uploading an app, do not dismiss the update screen right away + - do not dump large logs if stats json is not found + - check for firmware update when foregrounded + - fix bug where a canceled app install would be reported as completed + - fix bug where an install would fail silently because the resources could not be loaded + - display specific error message when a user tries to install a 2.0 app on a 1.x Pebble + - fix a bug where the android app would display error message "Could not update" while looking for updates in the background + +### PebbleKit iOS + + - allow one iOS application to exchange messages with several Pebble apps (with different UUIDs) + - fix a crash trying to parse invalid firmware version + - add CocoaPods support (see pebblekit-ios readme for more info) + - enabled "all warnings" and fixed errors + +### PebbleKit Android + +No changes. + +### SDK Tools + + - added support to upload any bundle (including firmware) + - added test to detect missing tools + - better implementation of the --debug flag + - fix bug where tools would fail when installed in a folder with a space in it + - fix bug where tools would fail on project with a space in the name + - some 1.x to 2.x conversion bugs fixed + - automatically re-enable applog when the watch reconnects + +### Examples + + - fix crashing bugs in 91Dub diff --git a/devsite/source/_changelogs/2.0-BETA4.md b/devsite/source/_changelogs/2.0-BETA4.md new file mode 100644 index 00000000..982cd3d0 --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA4.md @@ -0,0 +1,102 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA4 - Changelog +date: 2013-12-23 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * SDK 2.0 will be released early next year as an over-the-air update to all Pebble users. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + * If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. + +**You can start uploading your application to the Pebble appstore TODAY - [Please do so](http://dev-portal.getpebble.com/)!** + +## What has changed since BETA3 + +Overview: + + - Fixed a problem where the iOS app would get killed after a few minutes in the background + - Lots of Data Logging fixes on the firmware and on Android + - Added timestamps on accelerometer samples + - Improved error handling for PebbleKit JS apps on iOS and Android + +### Firmware + + - Developers of apps can now register single and multi click handlers on the back button + - Holding down the back button for 1.5s will force quit an existing app + - Fixed bugs and optimize the filesystem: faster persist times, less problems with persistent storage, fix a bunch of rather complex problems where the recovery firmware could be damaged + - Fixed scroll offset bug when displaying notifications + - Dismiss missed call notfication after 180s + - Fixed a bug where unicode characters were not supported in appinfo.json + - Changed graphics_text_layout_get_max_used_size() to _not_ require a graphic context + - Fixed a few more bluetooth bugs + - Fixed a bug where Pebble could crash when you delete the last alarm + - Fixed memory read exception that can occur when using a malloc-ed appsync buffer + - Save notifications to storage during do not disturb + - Document AccelAxisType in API Documentation + - Fixed Music UI problems + - Automatically center on screen a selected cell in a SimpleMenuLayer + - Fixed bug where snprintf could crash the watch + - Display an error message if a 2.0 pebble is connected to a 1.x mobile app + - Fixed a bug where calling atoi() would crash an app + - Many DataLogging improvements and fixes based on new unit tests + - Display an alert on Pebble when we reset due to a system crash + - Ignore NULL pointer passed to text_layer_destroy() + - Limit the number of datalogging sessions to 20 + - Fixed a race condition that occurs when you set the sampling rate immediately after subscribing to the accel service + - Keep persistent storage intact when upgrading an application + - Added timestamps on accelerometer samples and a flag indicating if the watch was vibrating at the time of the sample + - Fixed a bug where psleep could crash pebble + - Fixed a bug where text_layer could add ellipsis to the wrong line + +### iOS App + + - Fixed a bug where the iOS app would get killed in the background after just a few minutes + - Show a local notification if a developer is connected but the app is about to get killed + - PebbleKit JS: Fixed a bug where apps could not access localStorage with the array syntax + - PebbleKit JS: Fixed a bug where a space in an URL opened with XmlHTTPRequest could crash the iOS app + - PebbleKit JS: Fixed a bug where sending a byte array with 0xff in it would send 0x00 instead + +### Android App + + - PebbleKit JS: Fixed a bug where a byte array would not be sent properly for named keys + - Use new Android KitKat (4.4) APIs to do pairing on 4.4 + - PebbleKit JS: Do not send ack for ack/nack messages + - Fixed Android crashing with OutOfMemory error when using Data Logging + - Fixed Android Data Logging of byte array that was not working + +### PebbleKit iOS + + - Do not ack ACKs... + +### PebbleKit Android + + - No changes + +### SDK Tools + + - Added support to libpebble to trigger reboot to recovery firmware + - Added support for computers where python2 and python3 co-exist + - Fixed an exception when receiving APP_LOG with extended characters + - Fixed a bug where unicode characters were not supported in characterRegex field of `appinfo.json` + - Fixed 30 second delay that can occur when building pebble apps on Ubuntu when there is no internet access + - Added Pillow python dependency: needed for the screenshot functionality + - Detect PIL/Pillow conflict and suggest a fix to the user + +### Examples + + - Added a License to the examples + diff --git a/devsite/source/_changelogs/2.0-BETA5.md b/devsite/source/_changelogs/2.0-BETA5.md new file mode 100644 index 00000000..07e5c21b --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA5.md @@ -0,0 +1,112 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA5 - Changelog +date: 2014-01-10 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + * If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. + +**You can start uploading your application to the Pebble appstore TODAY - [Please do so](http://dev-portal.getpebble.com/)!** + +## What has changed since BETA4 + +Overview: + + - Fixed Android datalogging bugs where data would get duplicated + - Merged datalogging fixes for iOS that were supposed to be in BETA4 (sorry) + - Added an end of session message on Android datalogging + - Fixed accelerometer bugs where the accelerometer would stop sending data + - Changed the animation when switching from one watchface to the next ... + - Changed the battery API to return values going up to 100% + +### Known Problems and limitations + + * **Accelerometer locking up**: Although we have fixed several bugs around the accelerometer, we have noticed a few instance of the accelerometer locking up and the accel callback not being called anymore. If you see this happen again, please use the "Contact Support" button to send us logs. Make sure you change the subject to "Accelerometer lockup". Thank you very much! + + * `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account. + This is tied with appstore functionnalities and not available yet in this beta build. + * Some crash due to internal timers and deadlock conditions are still being investigated. + * This version will reset your persistent storage when you install it + +### Changes for Firmware: + + - Added a script in the SDK to help analyze app memory usage (analyze_static_memory_usage) + - Changed the animation between watchfaces + - Fix various composition bugs during animations + - Several fix to the Pebble filesystem to fix problems occuring in persistent storage and datalogging + - Add `bitmap_layer_get_bitmap()` + - s/1 minutes/1 minute/ in the alarm app + - Do not crash when loading a font from a NULL resource (can happen when memory is tight!) + - Ignore buttons while animating from one window to another + - Fix the back button in the getting started + - Fix simplicity to show the time immediately + - Fix sliding text to animate the time in immediately + - Change simplicity to load the fonts as system fonts + - Invert modal window status bar icons + - Reworked `gpath_draw_filled()` to use less memory and use heap instead of stack + - Improve persistent storage behaviour under tight memory + - Enforce file size limits + - Improve number of sectors of the filesystem + - Fix a bug where in some condition going up and down after installing a watchface would not return to it + - Fix a bug where `text_layer_get_content_size()` could return values that caused the text to be truncated + - Do not crash in `gpath_draw_filled()` if called with 0 points + - Added event service unsubscribe for app_focus_event (fixes a crash for Glance and Upright) + - Changed the battery API to return values going up to 100% + +### Changes for Pebble iOS App: + + - Fixes to datalogging to avoid duplicated data and iOS app getting stuck + +### Changes for Pebble Android App: + + - Added an intent sent when a data logging session is finished + - Fix a problem where JavaScript would not start on android 4.0 + - Fix some bluetooth scanning bugs that could cause timeouts or pebbles not detected + - Improved bluetooth pairing screens for various Android versions + +### Changes for PebbleKit iOS: + + - Fix some threading/deadlock bugs + +### Changes for PebbleKit Android: + + - Do not retransmit same datalogging blocks more than once + - Add a callback when the datalogging session is finished + +### Changes for SDK Tools: + + - Added command `pebble analyze-size` to dump sections and symbol sizes + - Increase timeout of the wsclient (could be triggered when installing firmware) + - Added `--simple` option to `pebble new-project` to create a minimalist app + - Updated to websocket-client 1.12 and removed dependency to io_sock + +### Changes for Examples: + + - Update classio-battery-connection example to peek() the bluetooth connection status at startup + +### Changes for Documentation: + + - Updated JS configuration example + - Added link to the pebble-hacks/configurable project in the JS doc + - Removed reference to the 1Hz acc sampling rate (RIP) + - Added an example use of the `pebble install` command in the example page + - Updated the `app_focus_subscribe` documentation in the event guide + - Added a note in the datalogging guide to mention it's not a realtime system + - Added doc for `only_shown_on_communication` in the anatomy of a pebble app chapter + - Added that you can call `app_message_outbox_begin` in `outbox_sent` and `outbox_failed` now + - Fixed formatting of the appinfo.json example in the anatomy of a pebble app chapter diff --git a/devsite/source/_changelogs/2.0-BETA6.md b/devsite/source/_changelogs/2.0-BETA6.md new file mode 100644 index 00000000..573f4673 --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA6.md @@ -0,0 +1,92 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA6 - Changelog +date: 2014-01-17 +--- + + * Pebble SDK 2.0 is currently in BETA and intended for developers only. + * Applications written for Pebble 1.x are not compatible with SDK 2.0 + * If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. + + +> **IMPORTANT NOTES FOR iOS Users**: +> +> * You must delete the Pebble app on your phone before installing this new version. It will now be called "Pebble Dev" and not "Pebble.". You must also re-install all of your JavaScript apps after installing this new version. +> +> * iPhone5S, iPad Air and Retina iPad Mini users will need to manually pair in the **Settings** of the phone. + +## What has changed since BETA5 + +Overview: + + - The iOS Application distributed with BETA6 includes the new Pebble appstore + - The firmware fixes a number of hard to reproduce crashes with system timers. This will fix a lot of the "Dangerously rebooting" Pebble crashes. + +### Known Problems and limitations + + * `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account. + This is tied with appstore functionnalities and not available yet in this beta build. + * The bugs that were reported on datalogging-iOS on BETA5 are not fixed yet in this release + +### Changes for Firmware: + + * Rework the system timer to fix all timer related crashes + * Add support for Pebble Steel LED to show charging status + * Round rather than floor the battery charge percentage + * Reverted timings for stm32 for 64MHz system clock based on stable 16Hz SPI clock. Fixes display flicker at 30Hz, as well as saving power at the lower system clock (80->64) and sleeping more often due to faster display updates. + * Fix a crash when canceling the bluetooth pairing dialog + * Fix a bug where pushing a window in a window_unload callback would cause a crash + * Export AccelData structure in the API doc + * Vibrate when an app or watchface is installed + * Fix a bug where the phone modal window would not update properly + * Fix the light threshold for Pebble Steel + +### Changes for Pebble iOS App: + + * Added the Pebble appstore + * Added support for In-App Notifications + * Add support for migrating 1.x apps into 2.0 apps + * Fix a bug where the iOS app could crash when you switch away from a JavaScript app that has an ongoing network connection + * PebbleKit JS iOS: sendAppMessage() now returns a transaction id + +### Changes for Pebble Android App: + + * No changes. + +### Changes for PebbleKit iOS: + + * add isNewer convenience call to PBWatch+Version + * move NSJSONSerialization helper to PebbleVendor + * add isEqualVersionOnly to just compare version number components, ignoring timestamp & hash + +### Changes for PebbleKit Android: + + * No changes. + +### Changes for SDK Tools: + + * Fix spelling in an error message (s/Insure/Ensure/) + +### Changes for Examples: + + * No changes. + +### Changes for Documentation: + + * Fix a 404 on the pebble tool link in the JS guide + * Fix the persistence guide to reflect the new standardized parameters orders + * Fix a typo in the title of the UI framework guide + * Added designer resources in the UX design chapter diff --git a/devsite/source/_changelogs/2.0-BETA7.md b/devsite/source/_changelogs/2.0-BETA7.md new file mode 100644 index 00000000..e3cb7692 --- /dev/null +++ b/devsite/source/_changelogs/2.0-BETA7.md @@ -0,0 +1,145 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 BETA7 - Changelog +date: 2014-01-23 +--- + +Pebble SDK 2.0 is still in BETA and is **recommended only** for developers working on new applications for the upcoming Pebble appstore. + +## Update Jan 31st: 2.0 Release Candidate 3 + +We fixed two more crashes. iOS user will automatically get the update. Android users can download it from this site. + +Not sending an email to everyone this time because it really is a small changes and we want to spare your inbox before the week-end. + +## Update Jan 30th: 2.0 Release Candidate 2 + +On January 30th, we released a 2.0 Release Candidate version of the firmware with the following changes: + +* fixes a number of crashes +* app no longer gets killed when it cancels an invalid timer +* removes “persist_raw -9” message +* low battery message always uses the right icon +* fixes crash on watch shutdown +* fixes crash when using accel + +## Updated Jan 29th: 2.0 Release Candidate + +On January 29th, we released a 2.0 Release Candidate version of the firmware with the following changes: + +* Fixed numerous crashes +* %Z flag passed to strftime no longer crashes the watch +* Fixed iOS connected but not receiving anything issue +* Firmware will now delete all data logging data on factory reset +* Rate limit logging to prevent apps from crashing app with logging loops +* Fixed issues were buttons become unresponsive +* Fixed gpath getting clipped in some cases +* Fixed accel lockup issue +* Fixed accel not using the right sampling rate +* Added low battery warning +* Cancel snooze timer when alarm is deleted + +Please continue using BETA7 versions of the SDK and mobile applications. + +## What has changed since BETA6 + +Overview: + + - More random crashes fixed in the firmware + - Seriously improved datalogging on iOS (and some bugfixes on Android) + - Fixed the URL scheme to install Pebble applications. It did not work in Beta6. + - Added support for `getAccountToken()` in PebbleKit JS (iOS only at the moment) + - iOS application and PebbleKit iOS are now 64 bits compatible + - iOS application does not crash on iPhone 4 anymore + - Some breaking changes in PebbleKit iOS: We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category. + +### Known Problems and limitations + + - Android does not include the Pebble appstore yet + - PebbleKit iOS apps may see error messages about parsing firmware in their logs. This will be removed soon and does not impact anything in PebbleKit iOS. + +### Changes for Firmware: + + - fix bugs with modal windows over fullscreen apps + - fix bugs where action bar buttons could get "stuck" + - reduced the power used by Pebble Steel LEDs + - fix some data logging corruption issues on Pebble + - fix a bug where the time of a notification would not be displayed properly + - adjusted the battery charged thresholds so that Pebble Steel turns green when apps show 100% + - fix a bug where you could get 110% battery + - fix a bug where datalogging session could be incompletely initialized when pushed + - fix a bug that could happen when looking for notifications + - fix a bug where some original Pebbles (ev2_4) would never hit 100% battery + - fix infinite loop if you push a modal while one is closing + - fix some button problems on Pebble Steel + +### Changes for Pebble iOS App: + + - added support for 64bits compilation + - fix a bug where 64 bit devices would not display the bluetooth accessory picker + - native login / signup screen + - fix some button sizes to display text properly + - calculate the area of the buttons on the left menu to highlight them dynamically + - data logging: do not print error messages for partially fetched data - unless we are actually done + - fix some bugs around the Bluetooth accessory picker + - better management of the screens stack in onboarding process. allows users to go back. + - do not display icon for watchfaces in the my pebble screen + - fix bug where appstore url-scheme would not work + - add link to terms and conditions + - deal with timeout errors while installing apps + - downloading apps in the Caches directory instead of Documents since that one gets pruned automatically by the system (Fixes pebblekit#39) + - only allow to start dragging the center view if you start dragging from the left edge + - fix crash for iPhone 4 users + - fix bug where datalogging would try to send data to the Pebble app (instead of 3rd party apps) + - lazy loading the web appstore views to improve loading speed + - sort apps alphabetically in the locker + - memory optimization to stay in the background longer + - getAccountToken() now working in JavaScript + - fix a bug where the configuration view was not sometimes not dismissed + - rename the "Done" button of the configuration view to "Cancel" + - send empty string back to the JS if the user cancels the configuration view (as per documentation) + - only show the "notifications not set up" if there's a watch connected + - fix a JS bug where in some conditions the 'showConfiguration' event might be fired before the 'ready' event + +### Changes for Pebble Android App: + + - Datalogging: if a session contains bad data, just remove it at startup + +### Changes for PebbleKit iOS: + + - PebbleKit iOS is now 64 bits (armv7s) compatible + - We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category. + - Do not start the datalogging server if appUUID is all zeros + +### Changes for PebbleKit Android: + +No changes. + +### Changes for SDK Tools: + +No changes. + +### Changes for Examples: + + - Classio-battery-connection is a watchface + - Onthebutton is a watchface + - Rumbletime is a watchface + - Fuzzy Time is a watchface + - Changed example UUID's to avoid appstore collisions + +### Changes for Documentation: + + - Add note about datalogging size diff --git a/devsite/source/_changelogs/2.0-DP2.md b/devsite/source/_changelogs/2.0-DP2.md new file mode 100644 index 00000000..6be357a6 --- /dev/null +++ b/devsite/source/_changelogs/2.0-DP2.md @@ -0,0 +1,113 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 DP2 - Changelog +date: 2013-09-24 +--- + +>2.0 DP2.1 + +> We do not like making releases twice a week but we really wanted to fix the iOS/PebbleKit JS bug and so here it is. PebbleKit JS can now receive message on iOS and on Android. You only need to update your SDK for this to work. We will not release new versions of the mobile apps. + +>The rest of the DP2 release notice below still applies. + +### Known problems and bugs + + - Data Logging does not work on iOS7 + - The watch can run out of memory if the applications do not release the memory properly + - On iOS, PebbleKit JS can not receive app messages (fixed in the DP2.1 release) + + +### In a nutshell + + * The format of applications has changed. Every application now requires an `appinfo.json` file in its base directory. This file contains the UUID, name and resources of the application. For PebbleKit JS apps, it may also contain the keys used for app_message communication. + * The `pb-sdk` tool is gone. It is replaced by the `pebble` command line. + * Instead of having the phone connect to the developer box, the developer tools will connect to the mobile application. When you turn on developer mode on your phone, it will display the IP address that you should use to connect to the phone. You can set a `PEBBLE_PHONE` environment variable to avoid retyping this all the time. + * Fixed most blocking bugs reported on Developer Preview 1 (details below) + * The Developer Guide has been completely rewritten and also includes a migration guide. + +### Pebble Firmware + + - Fixed a bug where launching an application through the bluetooth protocol would cause the app to be re-launched if it was already running. + - Added support for the middle button in the Golf app. This will send a message that is received by PebbleKit on iOS and Android. + - Tap event is now disabled during a vibration (to avoid triggering the event) + - Bumped firmware version and added tests to make sure that old apps will not run on the new firmware and vice-versa + - Fixed various issue with firmware updates + - AppLog does not need to be enabled manually anymore. It is automatically enabled by the `pebble` tool. + +### Pebble SDK + + - Finalized conversion to new dynamic memory model: all _init functions have been replaced by _create() equivalent that return a pointer to a newly allocated and initialized structure (gbitmap_init functions, gpath_init, property_animation_init and app_sync_init have been updated to the new style) + - Trigger a battery event when the percentage of battery charge changes (will trigger every 2%) + - Data Spooling now takes a `void*` pointer (to avoid useless casting in developer code) + - Data spooling session ids are now random + - persist_read_int now returns 0 if the key does not exist (as per documentation) + - Global static variables were not initialized properly + - Fix a dataspooling bug where sometimes the close message did not contain the correct session id + - Added a bluetooth_connection_service_peek() function + - Export atol/atoi functions + - Export app_comm_get_sniff_interval + - As a developer, I can call the atan()/atan2() function to compute an arc-tangent + - Renamed DataSpooling into DataLogging + - Defined a new Design Pattern to subclass layers and included an example based on the famous Progress Bar layer (`watchapps/feature_layer_data`) + +### PebbleKit iOS/Android + + - Redesigned completely the Android API for Data Logging (ex data spooling) + +### PebbleKit JavaScript + + - Fixed a bug where only single digit integers would be passed as integers + - On Android, the apps are now available in the "Webapps" menu + - On iOS and Android, applications will keep on running once they are started (but they do not start automatically yet) + - On iOS, you need to tap the appname to run it. + - On iOS, to allow for `openURL` to work, you need to open the javascript console first + - On iOS and Android, javascript `console.log` are sent over the developer connection (available with `pebble logs`) + - You can now send array of bytes by passing an array of integers to `sendAppMessage()` + - The keys in the appinfo.json file is now optional and you can use integers in strings as keys + - If a received message as a key that is not listed in the `appKeys` block, the integer will be converted to a string + - A bug where the navigation stack could be corrupted when calling `openURL` is fixed + +### pb-sdk + + - Renamed pb-sdk into pebble + - Added a --version option + - Added a command to clean current project + - Added an example of using APPLOG in the default generated project + - Return meaningful error codes to the shell + - Fixed a bug where `pb-sdk list` would fail if no apps are installed + - Added a --javascript option to `pb-sdk new-project` to create a template of JS code and automatically generate the appinfo.json + - Automatically detect old projects, refuse to build and offer the option to update the project + - Added `convert-project` command to update old projects to the new style (note: this will not update the source code) + - Added clean error message if a resource is missing + +### iOS Application + + - The developer preview iOS application will automatically offer new version of the beta firmware + - Added support for middle button in golf app + - Some iOS7 fixes + - Switched to TestFlight for distribution and crash reports + +### Android Application + + - The developer preview Android application will automatically offer new version of the beta firmware + - Added support for middle button in golf app + - Switched to TestFlight for distribution and crash reports + +### Pebble SDK Examples + + - Added an example for the persistent storage APIs + - Fixed all the iOS examples to build out of the box + - Reworked the Ocean Data Survey example diff --git a/devsite/source/_changelogs/2.0-DP3.md b/devsite/source/_changelogs/2.0-DP3.md new file mode 100644 index 00000000..b5049876 --- /dev/null +++ b/devsite/source/_changelogs/2.0-DP3.md @@ -0,0 +1,122 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0 DP3 - Changelog +date: 2013-10-21 +--- + +This version brings some major improvements and a lot of bugfixes. In a nutshell, the big changes are: + + * PebbleKit JavaScript now supports geolocation on all platforms + * Pebble supports the ANCS protocol. See details below. + +This is a private release under NDA. + +### Pebble and ANCS + +Pebble has been working on integrating Bluetooth Low Energy (BLE) technology into our upcoming software releases. The initial goal for this work is to greatly enhance the notification experience between a Pebble and a BLE-capable iOS7 device (the iPhone4S and later) - this leverages the "ANCS" notification feature of iOS7. A requirement for the public release of BLE-capable Pebble SW is that it will not change the Android experience. We will work on enhancing the BLE experience specifically for Android users in future SW releases. + +If you wish to help Pebble test BLE and ANCS, please read this carefully, this is pre-release software and there are still areas of the experience we are actively enhancing. We greatly appreciate your help in testing these important features :-) + +Pebble SDK DP3 (and up) include BLEs capabilities. Download the firmware and mobile apps as instructed in the installation instructions. You do not need anything else. + +To configure ANCS and BLE: + +- If you already had email configured in the iOS Pebble app, go into the Pebble app and turn that OFF. With ANCS, email notifications will automatically mirror the notifications that show up on your phone. +- The first time you set this up (after you install BLE/ANCS firmware) you will need to pair your phone with the watch to make the BTLE connection. + - On the watch, go into the "Settings" view, and select "Bluetooth". + - On your iOS7 iPhone go into the "Settings" app, select "Bluetooth". + - You should see an entry called "Pebble-LExxxx" where xxxx is the 4 digit code that is shown at the top of the Pebble's Bluetooth screen. Select that entry, and confirm pairing. +- Ensure that BOTH traditional Bluetooth and BLE are paired. You will not be able to perform all of the functions (such as handling phone calls) if the Bluetooth-Classic connection is not working. +- We are actively working on enhancements to pairing, so this process will change as we near public release. + +Known issues: + +- Only pairing from iOS BT Settings works for now. In-Pebble-app pairing is still TODO. +- If you have a red bar "Notifications require additional setup" in your iOS app, this will not disappear when LE is paired / ANCS is activated. You can safely ignore it. +- Gmail/IMAP in Pebble app + ANCS = Duplicate emails. We recommend turning off email accounts from the Pebble iOS app. +- "Forget" from the watch's BT settings menu doesn't work as expected. iDevice immediately reconnects again. +- All notifications have same icon +- Max message length is shorter than Bluetooth Classic. +- Impact on battery life: we are actively characterizing and working on this, but it is currently less than Bluetooth-Classic only. + +Please report any bugs by email to: [ancsbug@getpebble.com](mailto:ancsbug@getpebble.com)! + +**Remember, this release is under NDA. Please do not share word of this new feature. Thanks a lot!** + +### Known problems and bugs + + * Data Logging still does not work on iOS7 + * On iOS, to try the "openURL()" function, you must first click the "Details Indicator" button on the table view that lists the JavaScript process + * On Android, to upgrade an existing JavaScript app, you must first kill it in the "JS App Processes" view (look for the Skull And Bones button) + * On some Android phones running 4.1, we have encountered a situation where location services were not working. This problem and the appropriate fix is described by Google [in this forum post](http://productforums.google.com/forum/#!msg/mobile/LEPcl9e3dYE/3LZEhiWACigJ). + +### Pebble Firmware + + - Fix a bug where Pebble would keep vibrating after answering a call + +### Pebble SDK + + - Fix a bug which caused all apps to share the same persistent storage file + +### PebbleKit iOS/Android + + - Removed some deprecated/private APIs call from PebbleKit-iOS + - Update PebbleKit-iOS project files to Xcode 5 + - Fix PebbleKit Android build - moved libraries to libs/ + +### PebbleKit JavaScript + + - Getting current location now works on iOS and Android. It is also possible to watch the current position and be notified when it changes. + - Fixed a bug on iOS where sending multiple digits number would not work + - Fixed a bug with the 2.1 release on Android where it would be impossible to use AppMessage (with PebbleKit JS) + - Receiving byte arrays now also works on iOS + - Added Pebble.getAccountToken() to get a unique token for the current user account (Note: this is not documented yet.) + +### pebble tool + + - Do not return 0 if something bad happened + - Display the footprint of the app in RAM and the available heap space + +### Pebble iOS Application + + - Fixed a UI bug on iOS7 when deleting an app + - Fixed the Developer Mode UI on iOS7 + +#### 2013 10 25 - 2.0-DP3.1 + +We have release a 3.1 update for the iOS application which should fix the most common crash for the app. + +### Pebble Android application + + - Fixed a bug where the Android app would continuously try to connect to the Pebble even after disconnecting/unpairing + - Fixed a bug where Facebook notifications would have duplicated content in the name field and the main field + - Automatically start an app after installation + - Fixed a bug where it would be impossible to skip the onboarding process + - Fixed a bug where switching the device orientation during firmware upgrade would cause the upgrade to start again + +### Pebble SDK Examples + + - Fixed a crash in dropzone + - Improved the weatherjs example to use geolocation and display the name of the city + - Added a very cool arcade game to demonstrate use of persistent storage (watchapps/pebble_arcade) + +### Documentation + + - Simplified installation instructions for Linux + - Fixed a lot of broken links + - Added a chapter on iOS whitelisting + - Added a chapter on fonts + - Reworked most of the developer guides diff --git a/devsite/source/_changelogs/2.0.0.md b/devsite/source/_changelogs/2.0.0.md new file mode 100644 index 00000000..e0f4a2e5 --- /dev/null +++ b/devsite/source/_changelogs/2.0.0.md @@ -0,0 +1,79 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0.0 - Changelog +date: 2014-02-03 +--- + +This is the first public release of Pebble SDK 2.0 and Pebble firmware 2.0. + +## What has changed since BETA7 + +Overview: + + * We have fixed various crashes in the firmware (this was pre-released as 2.0-RC, 2.0-RC2 and 2.0-RC3) + * We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool + * PebbleKit iOS now includes armv7s, arm64 and x86_64 libraries - There is a known bug in PebbleKit iOS 2.0.0 that can cause your application to crash when it is in the background. Please do not use this version to submit an application to Apple. + +## Known bugs and issues + + * DataLogging disabled + + Pebble iOS 2.0.0 app can enter a crashloop situation when corrupted datalogging bytes are received from Pebble. To avoid this problem, we have disabled the datalogging APIs in firmware 2.0.0. We will re-enable datalogging when the iOS app 2.0.1 is available on the App Store. + + * PebbleKit iOS 2.0.0 + + Can cause 3rd party applications to crash when it is in the background. Please do not use this version to submit an application to Apple. This will be fixed in 2.0.1. + +### Changes for Firmware: + +Changes since 2.0-RC3: + + * fix a deadlock when sending datalogging information + * remove the "Your Pebble has reset" message + +The changes between 2.0-BETA7 and 2.0-RC3 were: + + * fixes a number of crashes + * app no longer gets killed when it cancels an invalid timer + * removes “persist_raw -9” message + * low battery message always uses the right icon + * fixes crash on watch shutdown + * fixes crash when using accel + +### Changes for PebbleKit iOS: + + * Updated our version of CocoaLumberJack to fix a crash that could happen when logging in the background + * Updated the build script to actually produce armv7s, arm64 and x86_64 dynamic libraries + * Improve the datalogging protocol (between PebbleApp and PebbleKit) to be more efficient + +### Changes for PebbleKit Android: + +No changes. + +### Changes for SDK Tools: + + * We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool + * Better handling of timeout errors with the websockets + +### Changes for Examples: + +No changes. + +### Changes for Documentation: + + * Add parameter `did_vibrate` to AccelData and explanation. + * Add parameter `timestamp` to AccelData and explanation. + diff --git a/devsite/source/_changelogs/2.0.1.md b/devsite/source/_changelogs/2.0.1.md new file mode 100644 index 00000000..751f02a3 --- /dev/null +++ b/devsite/source/_changelogs/2.0.1.md @@ -0,0 +1,60 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0.1 - Changelog +date: 2014-02-20 +--- + +This is a minor update to the Pebble SDK and the Pebble firmware. + +## What has changed since 2.0.0 + +Overview: + + - We have re-enabled data logging in the Pebble firmware + +## Known bugs and issues + + * PebbleKit iOS 2.0.0 + + Can cause 3rd party applications to crash when it is in the background. Please do not use this version to submit an application to Apple. This will be fixed in a later release. + +## Detailed list of changes + +### Changes for Firmware: + + * We have re-enabled the data logging APIs. + +### Changes for PebbleKit iOS: + + * No changes. + +### Changes for PebbleKit Android: + + * No changes. + +### Changes for SDK Tools: + + * No changes. + +### Changes for Examples: + + * Reduce the accel discs step time + +### Changes for Documentation: + + * Updated documentation on Android intents + * Updated documentation on AppMessage, AppSync, Dictionary, Typlets + diff --git a/devsite/source/_changelogs/2.0.2.md b/devsite/source/_changelogs/2.0.2.md new file mode 100644 index 00000000..cab47312 --- /dev/null +++ b/devsite/source/_changelogs/2.0.2.md @@ -0,0 +1,63 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.0.2 - Changelog +date: 2014-03-18 +--- + +This is another minor update to the Pebble SDK and the Pebble firmware. + +## What has changed since 2.0.1 + +Overview: + + - Fixes issue that prevented some users from being able to upgrade to 2.0. + - Support for XCode 5.1 + - Removed Pillow as dependency for the SDK + +## Known bugs and issues + +None. + +## Detailed list of changes + +### Changes for Firmware: + + * Fix a bug that could prevent installation fo the firmware + +### Changes for PebbleKit iOS: + + * No changes. + +### Changes for PebbleKit Android: + + * No changes. + +### Changes for SDK Tools: + + * LibPebble upgrade to remove PIL dependency + * replaced PIL with pypng for taking screenshots + +### Changes for Examples: + + * Update the todolist example to use graphics_text_layout_get_content_size instead of graphics_text_layout_get_max_used_size + * Port improvements to simplicity from firmware to examples + * Update quotes app for ready event + +### Changes for Documentation: + + * Fixed error in API docs for accel + * Fix javascript close URL in the javascript doc + diff --git a/devsite/source/_changelogs/2.1.1.md b/devsite/source/_changelogs/2.1.1.md new file mode 100644 index 00000000..21155a3d --- /dev/null +++ b/devsite/source/_changelogs/2.1.1.md @@ -0,0 +1,70 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.1.1 - Changelog +date: 2014-05-08 +--- + +## What has changed since 2.1 + +This release fixes a bug which caused the `pebble` tool to throw an exception when a Pebble app crashed. This is the only fix and we are not releasing a firmware 2.1.1, only the SDK is updated. + +## What has changed since SDK 2.0.2 + +Overview: + + * Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice. + * With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue. + * IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0. + +## Detailed List of Changes: +### Changes for Firmware: + * Fixed crash caused by calling number_window_set_label + * Fixed white line at the bottom of MenuLayer when last row is selected + * Fixed an issue where the watch would get into a reset loop after boot + * Fixed issue that sometimes caused persistent storage values to not persist + * Fixed issue where caller ID shows info from the previous call + * Fixed caller ID sometimes not displaying on outgoing calls + * Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice + * Apps can no longer crash the watch on app exit + * Bluetooth reconnection is more reliable + * Battery monitor is more consistent + * Multiple power reduction improvements + * Documentation improvements + * Clip text instead of truncating when vertical space is inadequate + * Notifications can be cleared via the Notification section in the Settings menu + +### Changes for PebbleKit iOS: + + * Some improvements to datalogging to help troubleshoot issues + +### Changes for PebbleKit Android: + + * No changes + +### Changes for SDK Tools: + + * Allow firmware bundles to be installed with the install command + * Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable + * Replaced PIL with pypng for taking screenshots + * Fixed extra row always being added to screenshots + +### Changes for Examples: + + * Removed ToDoList demo from SDK examples + +### Changes for Documentation: + + * Various documentation fixes and improvements diff --git a/devsite/source/_changelogs/2.1.md b/devsite/source/_changelogs/2.1.md new file mode 100644 index 00000000..94a5c8a2 --- /dev/null +++ b/devsite/source/_changelogs/2.1.md @@ -0,0 +1,66 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.1 - Changelog +date: 2014-05-06 +--- + +## What has changed since SDK 2.0.2 + +Overview: + + * Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice. + * With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue. + * IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0. + +## Detailed List of Changes: +### Changes for Firmware: + * Fixed crash caused by calling number_window_set_label + * Fixed white line at the bottom of MenuLayer when last row is selected + * Fixed an issue where the watch would get into a reset loop after boot + * Fixed issue that sometimes caused persistent storage values to not persist + * Fixed issue where caller ID shows info from the previous call + * Fixed caller ID sometimes not displaying on outgoing calls + * Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice + * Apps can no longer crash the watch on app exit + * Bluetooth reconnection is more reliable + * Battery monitor is more consistent + * Multiple power reduction improvements + * Documentation improvements + * Clip text instead of truncating when vertical space is inadequate + * Notifications can be cleared via the Notification section in the Settings menu + +### Changes for PebbleKit iOS: + + * Some improvements to datalogging to help troubleshoot issues + +### Changes for PebbleKit Android: + + * No changes + +### Changes for SDK Tools: + + * Allow firmware bundles to be installed with the install command + * Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable + * Replaced PIL with pypng for taking screenshots + * Fixed extra row always being added to screenshots + +### Changes for Examples: + + * Removed ToDoList demo from SDK examples + +### Changes for Documentation: + + * Various documentation fixes and improvements \ No newline at end of file diff --git a/devsite/source/_changelogs/2.2.md b/devsite/source/_changelogs/2.2.md new file mode 100644 index 00000000..898d627e --- /dev/null +++ b/devsite/source/_changelogs/2.2.md @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.2 - Changelog +date: 2014-06-04 +--- + +## Detailed List of Changes: +### Changes for Firmware: + +* Music app redesign to fix some layout issues & add progress bar +* Fix persist reads returning too little data if previously partially read +* Additional stability improvements +* Alarm now vibrates for 10 min instead of 1 min +* Launcher menu is now re-orderable. Hold the select button to enter reorder mode +* Volume control in the music app. Hold the select button in the music app to enter volume control mode + +### Changes for PebbleKit iOS: + +* Removed PBWatch+PhoneVersion (moved to PebblePrivateKit) +* Make PBWatch+Version report the correct version +* Fixed a crash when calling PBNumber description + +### Changes for PebbleKit Android: + +No changes + +### Changes for SDK Tools: + +No changes + +### Changes for Examples: + +No changes + +### Changes for Documentation: + +No changes diff --git a/devsite/source/_changelogs/2.3.md b/devsite/source/_changelogs/2.3.md new file mode 100644 index 00000000..7c4fea65 --- /dev/null +++ b/devsite/source/_changelogs/2.3.md @@ -0,0 +1,68 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.3 - Changelog +date: 2014-06-30 +--- + +## Detailed List of Changes: +### Changes for Firmware: + +* Don't generate multiple single click events on release if a repeating click handler is also used +* Fixed a small memory leak when destroying number_layer objects +* Fixed a menu_layer display bug when header height is set to 0 +* Allow app developers to supply their own ldscript +* Give a better error message when an unsupported libc function is used +* *_destroy functions now correctly do nothing when called with NULL pointers +* Fixed some BT LE connectivity issues +* Fixed a crash when we ran out of persist space +* Fixed a crash on reconnect when a user had a lot of pending iOS notifications +* Fixed an issue where the watch would continue to vibrate after a call is ended +* Fixed a display issue in Bluetooth settings when the status bar incorrectly says "Now Discoverable" in airplane mode +* Fixed a display issue with the notification font settings +* Fixed a display issue with the music app showing stale information when bluetooth is disconnected. +* Added the ability to skip to the next and previous notification by double clicking the up and down buttons +* Disabled the use of the back button for the Bluetooth pairing screen and the Alarm screen +* Show a status bar icon when notifications are set to "Phone Calls Only" + +### Changes for PebbleKit iOS: + +* Removed Bluetooth LE code from PebbleKit +* Improvements to data logging to help troubleshoot issues +* Removed PBWatch+PhoneVersion and +Polling +* Made PBWatch+Version report the correct version +* Fixed a crash when calling PBNumber description +* Changed imports from \ to "HeaderName.h" format +* Fixed on rare race-condition when sending data between phone and watch +* Made PebbleKit.podspec pass most-recent CocoaPod linter +* Prefixed internally used logging classes to fix conflict when using CocoaLumberjack in your app +* Made existing logging more descriptive + +### Changes for PebbleKit Android: + +No changes + +### Changes for SDK Tools: + +No changes + +### Changes for Examples: + +No changes + +### Changes for Documentation: + +* Added documentation for the calloc libc function +* Documented that text drawing functions use UTF-8 and will return errors on invalid input diff --git a/devsite/source/_changelogs/2.4.1.md b/devsite/source/_changelogs/2.4.1.md new file mode 100644 index 00000000..cc75a89b --- /dev/null +++ b/devsite/source/_changelogs/2.4.1.md @@ -0,0 +1,44 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.4.1 - Changelog +date: 2014-08-12 +--- + +## Detailed List of Changes: +### Changes for Firmware: + +* Fix a compilation problem that caused firmware 2.4 to reduce the amount of memory available to apps + +### Changes for PebbleKit iOS: + +No changes + +### Changes for PebbleKit Android: + +No changes + +### Changes for SDK Tools: + +No changes + +### Changes for Examples: + +No changes + +### Changes for Documentation: + +No changes + diff --git a/devsite/source/_changelogs/2.4.md b/devsite/source/_changelogs/2.4.md new file mode 100644 index 00000000..551f07fe --- /dev/null +++ b/devsite/source/_changelogs/2.4.md @@ -0,0 +1,53 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.4 - Changelog +date: 2014-08-11 +--- + +## Detailed List of Changes: +### Changes for Firmware: + +* Fix a potential crash when using scroll layers or animations +* Added support for realloc +* Added a gbitmap_create\_blank function to create empty bitmaps of a fixed size +* Added number_window\_get_window() +* Fixed a crash with atan2_lookup when high input values were used +* Fixed a bug where TupletInteger could not be used with unsigned integers +* Fixed several bluetooth reliability issues +* Fixed a case where the "Setup notifications" banner would erroneously show in the iOS Pebble app +* Fixed a bug with the music app where media playing faster than real time could not be paused +* Fixed a bug where the notifications view could show a rapidly increasing counter for number of notifications when first displayed +* Fixed a bug where switching watchfaces could cause the same watchface to be relaunched + +### Changes for PebbleKit iOS: + +No changes + +### Changes for PebbleKit Android: + +No changes + +### Changes for SDK Tools: + +No changes + +### Changes for Examples: + +No changes + +### Changes for Documentation: + +* Improved documentation around click handling diff --git a/devsite/source/_changelogs/2.5.md b/devsite/source/_changelogs/2.5.md new file mode 100644 index 00000000..2759c56d --- /dev/null +++ b/devsite/source/_changelogs/2.5.md @@ -0,0 +1,84 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.5 - Changelog +date: 2014-09-18 +--- + +>If you are upgrading from a previous version of the SDK, you will need to run the `pebble clean` command before using the SDK 2.5 with your project. + +##Major Changes +* FW 2.5 includes an optimized version of ``snprintf`` (and related functions like ``APP_LOG``, etc) that does not support some length format specifiers previously supported (%hh, %ll, %j, %z, %t). The list of supported specifiers has been updated in the ``snprintf`` documentation. For those of you that use these previously-supported specifiers, please do not hesitate to [contact us](/contact) and we'd be happy to assist you with updating your code. +* Added [compass](/guides/events-and-services/compass) support. +* Enforced versionLabel formatting in appinfo.json in preparation for app auto updates. +* Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity. +* Added notification dismissal support on iOS8. +* Added emoji support to Pebble notifications and system fonts. + + +## Detailed List of Changes: +### Changes for Firmware: +* Added functions ``heap_bytes_free`` and ``heap_bytes_used`` to view current heap memory usage. +* Added support for ``uuid_equal`` and ``uuid_to_string``. +* Added function ``accel_raw_data_service_subscribe`` to get accelerometer data with a single timestamp for all samples (significantly reduces memory usage for apps that do not depend on timestamps). +* Added [compass](/guides/events-and-services/compass) support. +* Added emoji support to Pebble notifications and system fonts `GOTHIC_24_BOLD`, `GOTHIC_18` and `GOTHIC_18_BOLD`. +* Fixed a bug that would cause a crash if a screen shot was taken while one was already in progress. +* Fixed an issue where Pebble APIs would use non-reentrant versions of standard C functions causing unexpected changes to return values. +* Fixed a bug with accel_service that could result in memory being freed twice. +* Fixed a bug where Golf API would show stale information on disconnect. +* Fixed a bug that prevented calling ``menu_layer_set_selected_index`` before ``menu_layer_set_callbacks``. +* Fixed a bug which would sometimes cause the command line logging tool to crash when a watchapp crashed. +* Fixed a bug that would cause the sample rate of the accelerometer to be reset when subscribing. +* Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity. +* Added support for notification dismissal on iOS8. +* Fixed numerous bluetooth reliability & connection issues. +* Fixed a reset and other various bugs related to Data Logging. +* Fixed a bug that allowed backing out of FW update screen. +* Fixed a bug that would cause animations between windows to be slow. +* Fixed a bug where the Date UI would allow selection of invalid dates. +* Fixed a bug which would prevent the down button from scrolling through notification history. +* Fixed a bug with AVRCP that could lead to a crash. +* Set backlight to stay on during alarm ringing. +* Changed the default backlight setting to AUTO. +* Fixed a bug which would allow developers to ask for more than 25 accel samples per update. +* Added check for NULL parameter in ``gpath_draw_filled``. + +### Changes for PebbleKit iOS: + +PebbleKit iOS has been removed from the SDK download. Please find the latest PebbleKit iOS on [GitHub](https://github.com/pebble/pebble-ios-sdk) or on [CocoaPods](http://cocoapods.org/) under 'PebbleKit'. + +### Changes for PebbleKit Android: + +PebbleKit Android has been removed from the SDK download. Please find the latest PebbleKit Android on [GitHub](https://github.com/pebble/pebble-android-sdk). + +### Changes for SDK Tools: + +* Enforced versionLabel formatting in appinfo.json in preparation for app auto updates. + +### Changes for Examples: + +* Added [compass example]({{site.links.examples_org}}/feature-compass) application. + +### Changes for Documentation: +* Added ``CompassService`` API document. +* Added missing ``calloc`` and ``realloc`` documentation. +* Improved ``tick_timer_service_subscribe`` documentation. +* Added missing ``RotBitmapLayer`` documentation. +* Corrected ``window_single_click_subscribe`` API entry. +* Corrected time_t time() function to specify that epoch adjusts for timezone and DST. +* Fixed typo in the ``AppMessage`` documentation. +* Improved ``gbitmap_create_with_data`` documentation. +* Fixed typo in documentation for ``resource_get_handle``. diff --git a/devsite/source/_changelogs/2.6.1.md b/devsite/source/_changelogs/2.6.1.md new file mode 100644 index 00000000..748a3dfa --- /dev/null +++ b/devsite/source/_changelogs/2.6.1.md @@ -0,0 +1,35 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.6.1 - Changelog +date: 2014-10-01 +--- + +> This release is a hotfix for the SDK 2.6 release + +### Changes for SDK Tools: +* Fix bug preventing use of `pebble analyze-size` +* Fix bug that caused compile errors with the use of custom fonts + +--- +### Pebble SDK 2.6 Release Summary ([full changelog](/sdk/changelogs/2.6/)) +##### Major Changes: +* Add support for [background apps](/guides/events-and-services/background-worker) with ``AppWorker`` +* Add ``graphics_capture_frame_buffer``, ``graphics_release_frame_buffer``, ``graphics_frame_buffer_is_captured`` APIs to expose framebuffer +* Add ``WatchInfo`` APIs to expose watch color, watch model, and firmware version +* Add quick launch support +* Bring back select-button-to-dismiss-notification on Android & iOS < 8 +* Add --worker option to `pebble new-project` to create file structure for apps with background workers +* Add background worker [example]({{site.links.examples_org}}/feature-background-counter) diff --git a/devsite/source/_changelogs/2.6.md b/devsite/source/_changelogs/2.6.md new file mode 100644 index 00000000..4e1140ac --- /dev/null +++ b/devsite/source/_changelogs/2.6.md @@ -0,0 +1,47 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.6 - Changelog +date: 2014-09-30 +--- + +> The symbols for `NUM_ANIMATION_CURVE` and `AnimationTimingFunction` have been removed in SDK 2.6. They were exposed in pebble.h in a previous release, but were not documented and are not used for ``Animation`` or ``PropertyAnimation`` APIs. + +## Detailed List of Changes: +### Changes for Firmware: +* Add support for [background apps](/guides/events-and-services/background-worker/) with ``AppWorker`` +> NOTE: The Background Worker API is not intended to be used as a wakeup mechanism for timer-based events or activities. SDK 2.7 will include a new Wakeup API that will allow you to set a timer that automatically launches your app in the foreground. Please do not use the Background Worker API to set such wakeups. +* Improve bluetooth connection service by only reporting disconnections of a certain length in time +* Add ``graphics_capture_frame_buffer``, ``graphics_release_frame_buffer``, ``graphics_frame_buffer_is_captured`` APIs to expose framebuffer +* Add ``WatchInfo`` APIs to expose watch color, watch model, and firmware version +* Fix bug where reading an existing key from persistent storage would fail +* Fixed Sports API bug causing menu item to not always appear in app launcher +* Fix bug with PebbleKit iOS and AppMessage timeouts +* Add quick launch support +* Bring back select-button-to-dismiss-notification on Android & iOS < 8 +* Re-enable vibration when done charging +* Improve battery life + +### Changes for SDK Tools: +* Add a --generate command line option to the coredump command +* Add --worker option to `pebble new-project` to create file structure for apps with background workers + +### Changes for Examples: +* Add background worker [example]({{site.links.examples_org}}/feature-background-counter) + +### Changes for Documentation: +* Add [AppWorker Guide](/guides/events-and-services/background-worker/) +* Add documentation for ``Worker``, ``AppWorker``, ``WatchInfo`` + diff --git a/devsite/source/_changelogs/2.7.md b/devsite/source/_changelogs/2.7.md new file mode 100644 index 00000000..8ada2c1c --- /dev/null +++ b/devsite/source/_changelogs/2.7.md @@ -0,0 +1,39 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.7 - Changelog +date: 2014-10-16 +--- + +## Detailed List of Changes: +### Changes for Firmware: +* Add ``Wakeup`` API +* Add ``launch_reason`` API +* Fix a bug that caused ``watch_info_get_color`` to crash the app when used +* Add ``clock_to_timestamp`` API +* Add ``clock_is_timezone_set`` API. In firmware 2.7, this function will always return false + as timezone support is not yet implemented +* Improve Bluetooth reliability +* Fix bug showing 0% battery warning + +### Changes for SDK Tools: +No changes + +### Changes for Examples: +* Add [wakeup example]({{site.links.examples_org}}/feature-app-wakeup) + +### Changes for Documentation: +* Add ``Wakeup`` API documentation +* Fix bug with missing ``snprintf`` specifier documentation diff --git a/devsite/source/_changelogs/2.8.1.md b/devsite/source/_changelogs/2.8.1.md new file mode 100644 index 00000000..90cdec25 --- /dev/null +++ b/devsite/source/_changelogs/2.8.1.md @@ -0,0 +1,43 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.8.1 - Changelog +date: 2014-12-09 +--- + +This release fixes a number of bugs and improves BLE pairing on iOS. +See [FW 2.8 Changelog](/sdk/changelogs/2.8/) for new features and major fixes. + +## Detailed List of Changes: +### Changes for Firmware: + +* Fix bug that would cause the watch to crash when ``accel_data_service_unsubscribe`` was called in a `window_unload` handler +* Revert error return values from ``resource_load_byte_range`` to pre-2.8 behavior +* Speed up BLE pairing on iOS +* Fix a bug that would cause an app to be built incorrectly if the first resource in appinfo.json was declared twice. +* Reduce stack usage of resource handling to prevent stack overflows introduced in 2.8 +* Fix several strings in non-English languages +* Fix bug in ``AppMessage`` that would cause the watch to crash + + +### Changes for SDK Tools: + +* Fix an issue where the SDK failed to build apps with non-ascii characters in the name. +* Include locale.h in pebble.h + +### Changes for Examples: +No changes +### Changes for Documentation: +No changes diff --git a/devsite/source/_changelogs/2.8.md b/devsite/source/_changelogs/2.8.md new file mode 100644 index 00000000..b2406375 --- /dev/null +++ b/devsite/source/_changelogs/2.8.md @@ -0,0 +1,57 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.8 - Changelog +date: 2014-11-20 +--- + +This release changes the rendering behaviour of custom fonts in apps compiled with SDK 2.8. The change +improves the visual appearance of fonts, but also causes them to be slightly larger. If you rebuild with +SDK 2.8 and text no longer fits, you can revert to the old behaviour by setting `"compatibility": "2.7"` +in the resource block for that font, like so: + +```javascript +{ + "type": "font", + "file": "fonts/something.ttf", + "name": "FONT_SOMETHING_24", + "compatibility": "2.7" +} +``` + +System fonts are unaffected by this change. + +## Detailed List of Changes: +### Changes for Firmware: + +* All system `GOTHIC` fonts are expanded to contain 351 characters +* Add ``setlocale`` and ``i18n_get_system_locale`` APIs in preparation for internationalization support +* Fix an issue that could cause an incorrect accelerometer sampling rate to be used +* Fix an issue causing wakeup events scheduled less than thirty seconds in the future to fail +* Improve the performance of very small resource reads +* Fix an issue where iOS calendar alert notifications sometimes did not appear +* Fix an issue sometimes causing spurious "Loading..." notifications to appear on iOS +* Improve behaviour when trying to boot with a critically low battery + +### Changes for SDK Tools: +* Improve font rendering for custom fonts when compiling with SDK 2.8 + * This can change the font metrics. If the font no longer fits, add the flag `"compatibility": "2.7"` + to the resource entry for that font. + +### Changes for Examples: +No changes + +### Changes for Documentation: +* Fix explanation of the timezone of timestamps passed to ``wakeup_schedule`` \ No newline at end of file diff --git a/devsite/source/_changelogs/2.9.md b/devsite/source/_changelogs/2.9.md new file mode 100644 index 00000000..deae347a --- /dev/null +++ b/devsite/source/_changelogs/2.9.md @@ -0,0 +1,42 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 2.9 - Changelog +date: 2015-02-10 +--- + +This release introduces actionable notifications for Android and minor stability improvements. + +## Detailed List of Changes: +### Changes for Firmware: + +* Add support for [Android actionable notifications](/blog/2014/12/19/Leverage-Android-Actionable-Notifications/) +* Fix bug that caused crashes when ``mktime()`` was used +* Fix behavior of ``window_stack_pop_all`` so that only the last window is animated +* Compiler will now show an error when the resources limit is reached +* Improve the stability of ``Worker``on launch +* Fix bug where a ``Worker`` selected from the Activity menus would not be set to default +* Fix bug where a ``Worker`` launched by a new app would not be set to default after the default worker was deleted +* Fix an issue that caused ``AppMessage`` to report sends as failed when sending/recieving a high volume of messages +* Notification date format is standardized: "Wednesday 11, February" -> "Wednesday, February 11" + +### Changes for SDK Tools: +No changes + +### Changes for Examples: +* Update openweather apis used in example apps. + +### Changes for Documentation: +No changes diff --git a/devsite/source/_changelogs/3.0-beta10.md b/devsite/source/_changelogs/3.0-beta10.md new file mode 100644 index 00000000..9e864b8d --- /dev/null +++ b/devsite/source/_changelogs/3.0-beta10.md @@ -0,0 +1,70 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-beta10 - Changelog +date: 2015-04-30 +--- + +This is the first beta release of Pebble SDK 3.0. + +All applications compiled with previous SDK should work on 3.0 and up. +However, we have made several changes to the APIs in this release that will +require source code changes. + +We **strongly recommend** that all developers rebuild and retest their apps with +this version of the SDK. + +*All apps built with beta10 will build with future releases of the SDK.* + +## Detailed List of Changes: + +### Changes for Firmware: + +* The PDC format has changed. PDCs created for developer preview 9 or earlier will + no longer work. + +### Changes to SDK: + +* Add new ``StatusBarLayer`` that you should use in apps that you want to have + a status bar. We've added a + [section to the 3.0 migration guide](/sdk/migration/migration-guide-3/) + with example code and screenshots. +* Added `PBL_SDK_2` and `PBL_SDK_3` or detecting the major version of the SDK at + compile time. +* Added new `buffer_size` parameter to ``clock_get_timezone``. +* Added Pebble Time Steel models to ``WatchInfoModel``. + +### Changes for SDK Tools: + +* The emulator now works on 32 bit Linux machines. +* The timeline now works with Python 2.7.9. + +### Changes to Timeline: + +* Apps in sandbox mode no longer have a whitelist by default. Existing timeline + apps are not affected. Take a look at the + [Enabling the Timeline](/guides/pebble-timeline/) guide for further + information. +* There are more new icons you can use in your pins. Check out the + [guide on pin structure](/guides/pebble-timeline/pin-structure) + for more details. + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0-beta11.md b/devsite/source/_changelogs/3.0-beta11.md new file mode 100644 index 00000000..f3b13423 --- /dev/null +++ b/devsite/source/_changelogs/3.0-beta11.md @@ -0,0 +1,48 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-beta11 - Changelog +date: 2015-05-10 +--- + +This is the second beta release of Pebble SDK 3.0. It includes a number of fixes to improve stability as well as new guide for Design and Interaction. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Crashes within ``worker`` will now show up with a crash dialog on the watch. +* Fixed bug where ``Timeline`` events displayed improper start/finish times. +* Fixed bug where images were drawn incorrectly if bounds in ``layer_set_bounds`` were set differently than (0, 0, size.w, size.h). + +### Changes to SDK: + +* Fixed a bug where apps would fail to wake up because ``Wakeup`` expected time in UTC. + +### Changes for SDK Tools: + +*No changes* + +### Changes to Timeline: + +*No changes* + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +* Added [Design and Interaction](/guides/design-and-interaction/) guides. diff --git a/devsite/source/_changelogs/3.0-beta12.md b/devsite/source/_changelogs/3.0-beta12.md new file mode 100644 index 00000000..a60f0f50 --- /dev/null +++ b/devsite/source/_changelogs/3.0-beta12.md @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-beta12 - Changelog +date: 2015-05-17 +--- + +This SDK release includes improvements to stability including fixes for timeline and timezone. There's also a new guide for making your apps compatible on both platforms. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Fixed a bug with timezone that would result in reporting the incorrect time. +* Ongoing timeline events that started less than 10 minutes ago now show up in the future. + +### Changes to SDK: + +* Fixed ``Pebble.getAccountToken`` on Android to return the same token as iOS. + To learn how to convert a new token to an old one for the same account, read + the + [Migration Guide](/sdk/migration/migration-guide-3#pebblekit-js-account-token). + +### Changes for SDK Tools: + +* Fixed a bug with the `pebble analyze-size` command caused by an incorrect elf file location. + +### Changes to Timeline: + +* Fixed a bug where [Reminders](/guides/pebble-timeline/pin-structure/) would not be shown at the precise time they were set. + +### Changes for Examples: + +* Updated the layout and content of the [Examples](/examples/) page. + +### Changes for Documentation: + +* Added a new guide, [Building For Every Pebble](/guides/best-practices/building-for-every-pebble); this covers the best practices for building an app compatible with both platforms. diff --git a/devsite/source/_changelogs/3.0-dp1.md b/devsite/source/_changelogs/3.0-dp1.md new file mode 100644 index 00000000..71691979 --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp1.md @@ -0,0 +1,24 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp1 - Changelog +date: 2015-02-26 +--- + +This is the first Developer Preview release of the brand new Pebble SDK 3.0. + +We will not be providing a comprehensive changelog for this release, but you +can take a look at our guide to [What's New in SDK 3.0](/sdk/whats-new/) and +our [3.0 migration guide](/sdk/migration-guide/). diff --git a/devsite/source/_changelogs/3.0-dp2.md b/devsite/source/_changelogs/3.0-dp2.md new file mode 100644 index 00000000..7e03286c --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp2.md @@ -0,0 +1,44 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp2 - Changelog +date: 2015-03-05 +--- + +This is the second Developer Preview release of Pebble SDK 3.0. + +We have updated the Aplite SDK to include some macros that make developing apps +for both platforms easier. For example, you can now use `GColorEq` on both +Aplite and Basalt and the SDK will take care of the platform differences. + +## Detailed List of Changes: + +### Changes for Firmware: + +Multiple stability improvements. + +### Changes for SDK Tools: + +* Running the Pebble emulator on Mac OS X will no longer use up 100% CPU. +* Apps built with 3.0-dp2 will install correctly on iOS +* Fixed `png2pblpng` for case of 1 color causing bitdepth of 0. + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0-dp3.md b/devsite/source/_changelogs/3.0-dp3.md new file mode 100644 index 00000000..b482a8a1 --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp3.md @@ -0,0 +1,47 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp3 - Changelog +date: 2015-03-13 +--- + +This is the third Developer Preview release of Pebble SDK 3.0. + +We have slightly modified the build process in this release to separate out +logs from Aplite and Basalt when building your app. + +In order to take advantage of this change, you will need to update your wscript. +The easiest way to do is to create a new project and copy the wscript. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Multiple stability improvements. + +### Changes for SDK Tools: + +* Separated Aplite, Basalt and bundling log output when building an app. +* Added new [`hiddenApp`](/guides/tools-and-resources/app-metadata/) + property to appinfo.json. + +### Changes for Examples: + +Updated examples with new wscript. + +### Changes for Documentation: + +* Multiple improvements to various 3.0 documentation, including fixing broken +links and undocumented function parameters. diff --git a/devsite/source/_changelogs/3.0-dp4.md b/devsite/source/_changelogs/3.0-dp4.md new file mode 100644 index 00000000..6650113e --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp4.md @@ -0,0 +1,68 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp4 - Changelog +date: 2015-03-19 +--- + +This is the fourth Developer Preview release of Pebble SDK 3.0. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Added basic timeline interface for testing and development purposes. + **This is not the finished timeline UI**. +* Pressing the up/down button from the main emulator screen will now open the + timeline. +* Made the generic, sports, weather and calendar layout available to timeline + pins (see the [timeline guides](/guides/pebble-timeline/) for more information). + +### Changes to SDK: + +* Fixed ``property_animation_clone()`` compatibility macro (for Aplite binaries). +* Added ``launch_get_args()`` for getting the `launchCode` attribute from an + [`openWatchApp`](/guides/timeline/pin-structure/#pin-actions) timeline pin + action. + +### Changes for SDK Tools: + +* Emulator now has the proper timezone set automatically (in firmware and in + JavaScript). +* Added the `insert-pin` and `delete-pin` commands to the emulator to interact + with the timeline locally. +* Added the `pebble login` command to connect your emulator to your Pebble + account. *This is required for the timeline to work*. +* Fix a bug where the build would fail if the project contains a space in + the name. +* The [`targetPlatforms` attribute is now supported on resources](/guides/app-resources/platform-specific/). + You can use it to specify that a resource should only be available on one platform. + +#### Known Issues + +The timeline will not work if you have Python 2.7.8+. Please use Python 2.7.6 if +you want to work with the new timeline APIs. + +### Changes for Examples: + +Examples have been removed from the SDK download, and our old examples +repository has been deprecated. + +You can now find all our examples on +[pebble-examples on GitHub]({{site.links.examples_org}}/). + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0-dp5.md b/devsite/source/_changelogs/3.0-dp5.md new file mode 100644 index 00000000..537bcffa --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp5.md @@ -0,0 +1,54 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp5 - Changelog +date: 2015-03-27 +--- + +This is the fifth Developer Preview release of Pebble SDK 3.0. + +The biggest new features with this release are +[antialiasing and stroke width](/guides/graphics-and-animations/drawing-primitives-images-and-text/). + +## Detailed List of Changes: + +### Changes for Firmware: + +* Antialiasing is enabled by default for all apps built with SDK 3.0 or higher. + +### Changes to SDK: + +* Added ``graphics_context_set_antialiased`` to toggle adding antialiasing to + the graphics_draw_* functions. See our + [Drawing Graphics](/guides/graphics-and-animations/drawing-primitives-images-and-text/) + guide for more details. +* Added ``graphics_context_set_stroke_width`` to set the stroke width for + drawing routines. See our + [Drawing Graphics](/guides/graphics-and-animations/drawing-primitives-images-and-text/) + guide for more details. + +### Changes for SDK Tools: + +* Fixed bug where projects with png-trans resources would cause build failures. + +### Changes for Examples: + +* Added [ks-clock-face]({{site.links.examples_org}}/ks-clock-face) + example to demonstrate the new antialiasing and stroke width APIs. + +### Changes for Documentation: + +* Documented the [new pebble commands](/guides/tools-and-resources/pebble-tool/) + for working with the emulator, and improved the rest of the tool documentation. diff --git a/devsite/source/_changelogs/3.0-dp6.md b/devsite/source/_changelogs/3.0-dp6.md new file mode 100644 index 00000000..c5c0ebea --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp6.md @@ -0,0 +1,53 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp6 - Changelog +date: 2015-04-03 +--- + +This is the sixth Developer Preview release of Pebble SDK 3.0. + +## Detailed List of Changes: + +### Changes for Firmware: + +* The action bar has been updated for 3.0. It is now 30px wide and has new + animations when the buttons are selected. +* The antialiasing added in 3.0-dp5 has been improved. + +### Changes to SDK: + +* ``ActionBarLayer`` uses the new 3.0 action bar, which provides one new API + function: ``action_bar_layer_set_icon_animated``. + +### Changes for SDK Tools: + +*No changes* + +### Changes to Timeline: + +* `createMessage` and `updateMessage` have been renamed to `createNotification` + and `updateNotification`. + The [Node.js pebble-api][pebble-api-node] client has been updated to match. + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +*No changes* + +[pebble-api-node]: https://www.npmjs.org/package/pebble-api diff --git a/devsite/source/_changelogs/3.0-dp7.md b/devsite/source/_changelogs/3.0-dp7.md new file mode 100644 index 00000000..30e310fd --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp7.md @@ -0,0 +1,58 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp7 - Changelog +date: 2015-04-09 +--- + +This is the seventh Developer Preview release of Pebble SDK 3.0. + +## Detailed List of Changes: + +### Changes for Firmware: + +*No changes* + +### Changes to SDK: + +* Added ``graphics_draw_rotated_bitmap`` for drawing rotated bitmaps to a + GContext. +* Added new ``Draw Commands`` which allow for doing vector-like graphics and + even animating them. +* Added ``action_bar_layer_set_icon_press_animation`` for setting the direction + of the animation when selecting actions in the ``ActionBarLayer``. +* Updated ``MenuLayer`` with new methods and callbacks to support color + highlighting. + +### Changes for SDK Tools: + +* You are no longer required to log in to the Pebble tool. If you want to use + the Pebble timeline in the emulator you will still need to log in. + +### Changes to Timeline: + +* Added `foregroundColor`, `backgroundColor`, `headings`, and `paragraphs` as + new fields on the Layout object for Pins. Check out the + [Pin Structure guide](/guides/pebble-timeline/pin-structure/) for more + details. + +### Changes for Examples: + +* Created [cards-example]({{site.links.examples_org}}/cards-example) to + demonstrate the new [Pebble Draw Commands](``Draw Commands``). + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0-dp8.md b/devsite/source/_changelogs/3.0-dp8.md new file mode 100644 index 00000000..9269cbb5 --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp8.md @@ -0,0 +1,53 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp8 - Changelog +date: 2015-04-16 +--- + +This is the eighth Developer Preview release of Pebble SDK 3.0. This week we +were focussed on the firmware UI and so this release does not contain many +changes that are visible to developers. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Updated the timeline UI. +* In preparation for the new 3.0 design style, we have removed the old system status bar and all 3.0 apps are now fullscreen by default. We will be releasing a new StatusLayer in the future. + +### Changes to SDK: + +* Added [`Pebble.getActiveWatchInfo()`](/guides/communication/using-pebblekit-js) for getting details about the currently connected Pebble watch. + +### Changes for SDK Tools: + +* Fixed incorrect values when reporting the maximum sizes of apps. +* Added SDK emulator support to the + [pebble command line tools](/guides/publishing-tools/pebble-tool) for + `emu_tap`, `emu_bt_connection`, `emu_compass`, `emu_battery` and `emu_accel`. +* Fixed the issues with installing apps to the emulator. + +### Changes to Timeline: + +*No changes* + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0-dp9.md b/devsite/source/_changelogs/3.0-dp9.md new file mode 100644 index 00000000..d5e44f4f --- /dev/null +++ b/devsite/source/_changelogs/3.0-dp9.md @@ -0,0 +1,59 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0-dp9 - Changelog +date: 2015-04-27 +--- + +This is the ninth Developer Preview release of Pebble SDK 3.0. + +## Detailed List of Changes: + +### Changes for Firmware: + +*No changes* + +### Changes to SDK: + +* Added ``menu_layer_set_normal_colors`` and ``menu_layer_set_highlight_colors`` + to make using ``MenuLayer``s much simpler. +* Renamed `GColorEq` to ``gcolor_equal`` to be more consistent with similar + methods. +* `InverterLayer` has been + [deprecated](/guides/migration/migration-guide-3/) and removed from the + SDK. + +### Changes for SDK Tools: + +* The pebble tool will now use any running emulator before attempting to launch + the default Basalt emulator +* Fixed a bug causing an incorrect color for foregroundColor and backgroundColor + on timeline pins + +### Changes to Timeline: + +* There are now many more icons you can use in your timeline pins. Check out + the [guide on pin structure](/guides/timeline/pin-structure/#pin-icons) + for more details. **Note:** All the existing icons have been renamed. + + +### Changes for Examples: + +* Deprecated feature-inverter-layer SDK example (see deprecation notice on + [GitHub]({{site.links.examples_org}}/feature-inverter-layer)) + +### Changes for Documentation: + +*No changes* diff --git a/devsite/source/_changelogs/3.0.md b/devsite/source/_changelogs/3.0.md new file mode 100644 index 00000000..5b7db80f --- /dev/null +++ b/devsite/source/_changelogs/3.0.md @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.0 - Changelog +date: 2015-05-27 +--- + +This is the first public release of SDK 3.0. It includes a number of small fixes to polish the timeline ui as well as improve stability. + +## Detailed List of Changes: + +### Changes for Firmware: + +* Added a crash dialogue when watchfaces crash. + +### Changes to SDK: + +*No changes* + +### Changes for SDK Tools: + +- The emulator now supports WebSockets +- The emulator now persists JavaScript `localStorage` between emulator launches +- The emulator now caches app JavaScript between emulator launches +- The SDK no longer depends on cython to install correctly. + +### Changes to Timeline: + +* Fixed a bug where text would overlay in calendar reminders. + +### Changes for Examples: + +*No changes* + +### Changes for Documentation: + +* Fixed documentation for ``clock_to_timestamp`` to specify that it returns a timestamp in localtime on aplite. + diff --git a/devsite/source/_changelogs/3.1.md b/devsite/source/_changelogs/3.1.md new file mode 100644 index 00000000..a9b28aca --- /dev/null +++ b/devsite/source/_changelogs/3.1.md @@ -0,0 +1,63 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.1 - Changelog +date: 2015-06-30 +--- + +### Changes to Firmware + +* Fix watch reset on calling ``compass_service_subscribe`` from a worker. +* Fix bug where setting click config is ignored if a notification is displayed. +* Fix app crash on calling ``gdraw_command_sequence_destroy``. +* Fix bug causing valid PNG8 images with a zero-length `tRNS` chunk to not load. +* Fix app crashes on 2.x apps using MenuLayers. +* Fix app crashes on 2.x apps using ScrollLayer. +* Fix ActionBarLayer being drawn as white when set to GColorClear. +* Fix bug causing ``menu_cell_title_draw`` and ``menu_cell_basic_header_draw`` to always render text in black. +* Fix alarms sometimes crashing the watch after vibrating for ten minutes. +* Fix transparent zero-radius circles rendering incorrectly. +* Improve rendering of zero-length lines with stroke width greater than one. +* Correctly display a sloth after deleting all pins on the timeline. +* Improve Bluetooth reliability. +* Reduced applog output from the launcher menu. +* Fix multiple cells being highlighted when setting the Do Not Disturb time range. +* Improve responsiveness when returning to a watchface from the launcher. + +### Changes to SDK + +* `window_set_status_bar_icon` is now deprecated. + +### Changes to Emulator/Phonesim + +* Fix WebSocket connections staying open after closing the app. +* Improve reliability of Aplite emulator installs when there are many timeline pins +* XMLHttpRequest now correctly returns a Uint8Array instead of ArrayBuffer. + +### Changes to Pebble Tool + +* Add support for `pebble app-config` command. +* Modify `pebble rm` command to use --bank or --uuid on 2.x, and --uuid on 3.x +* Modify `pebble current`, `pebble list` and `pebble uuids` commands to return a no-op message on 3.x. +* Remove login warning when not using emulator/phonesim. +* Improve error logging for JSON parsing errors. +* Fix a minor analytics bug. +* Fix requirements.txt bug. + +### Changes to Documentation + +* Update documentation for `window_set_fullscreen`. +* Update documentation for ``clock_to_timestamp``. +* Fix typo in documentation for ``MenuLayerDrawBackgroundCallback``. diff --git a/devsite/source/_changelogs/3.10-beta1.md b/devsite/source/_changelogs/3.10-beta1.md new file mode 100644 index 00000000..8709f24f --- /dev/null +++ b/devsite/source/_changelogs/3.10-beta1.md @@ -0,0 +1,35 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.10-beta1 - Changelog +date: 2016-02-15 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +### Changes to Firmware + +* Added energy (Calorie) usage to the Health app. +* Changed "till" to "'til" in the low battery modals. +* Improved firmware stability. + +### Changes to SDK + +* Added ``health_service_get_measurement_system_for_display`` to retrieve the user's unit preference. +* Added ``health_service_sum_averaged`` and ``health_service_metric_averaged_accessible`` to access + average health data. These can be used to determine the goal line used by the Pebble Health app. +* Added ``HealthMetricRestingKCalories`` and ``HealthMetricActiveKCalories`` to retrieve Calorie burn + information from Pebble Health. diff --git a/devsite/source/_changelogs/3.10-beta2.md b/devsite/source/_changelogs/3.10-beta2.md new file mode 100644 index 00000000..8b2e4061 --- /dev/null +++ b/devsite/source/_changelogs/3.10-beta2.md @@ -0,0 +1,40 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.10-beta2 - Changelog +date: 2016-02-19 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +The following release notes list only changes since [3.10-beta1](/sdk/changelogs/3.10-beta1/). + +### Changes to Firmware + +* ``health_service_get_measurement_system_for_display`` no longer crashes when called on + real watches. +* ``rand`` is now seeded from the hardware RNG on app start, as it was in firmware 3.4 and + earlier. +* An issue causing the sleep graph to sometimes be blank on the deep sleep display of the + Health app was resolved. + + +### Changes to SDK + +* `CFLAGS` and `LINKFLAGS` env variables are now correctly honored by the app build process. +* JSON files under the `src/js/` directory are available to `require` in newly-created projects. +* ``persist_write_bool`` and ``persist_write_int`` are now correctly documented to return the + number of bytes written on success. diff --git a/devsite/source/_changelogs/3.10-beta6.md b/devsite/source/_changelogs/3.10-beta6.md new file mode 100644 index 00000000..5323c3ef --- /dev/null +++ b/devsite/source/_changelogs/3.10-beta6.md @@ -0,0 +1,33 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.10-beta6 - Changelog +date: 2016-03-04 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +The following release notes list only changes since [3.10-beta2](/sdk/changelogs/3.10-beta2/). + +### Changes to Firmware + +* Improved Health accuracy. +* Improved firmware stability. + +### Changes to SDK + +* Added information necessary to debug workers as well as apps. +* Added information about SDK calls to gdb. diff --git a/devsite/source/_changelogs/3.10.1.md b/devsite/source/_changelogs/3.10.1.md new file mode 100644 index 00000000..2a4f1ade --- /dev/null +++ b/devsite/source/_changelogs/3.10.1.md @@ -0,0 +1,41 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.10.1 - Changelog +date: 2016-03-10 +--- + +This is a hotfix for [SDK 3.10](/sdk/changelogs/3.10/). + +### Changes to Firmware + +* Restored the following emoji that were inadvertently removed: + * U+2192 RIGHTWARDS ARROW: → + * U+25BA BLACK RIGHT-POINTING POINTER: ► + * U+2605 BLACK STAR: ★ + * U+1F3A4 MICROPHONE: 🎤 + * U+1F3A5 MOVIE CAMERA: 🎥 + * U+1F435 MONKEY FACE: 🐵 + * U+1F4AA FLEXED BICEPS: 💪 + * U+1F4F7 CAMERA: 📷 + * U+1F648 SEE-NO-EVIL MONKEY: 🙈 + * U+1F3B5 MUSICAL NOTE: 🎵 + * U+1F381 WRAPPED PRESENT: 🎁 + * Note that these emoji are only available on Time-series Pebbles due to hardware constraints. +* Made another attempt at fixing the charging modals. Third time's the charm! + +### Changes to SDK + +None. diff --git a/devsite/source/_changelogs/3.10.md b/devsite/source/_changelogs/3.10.md new file mode 100644 index 00000000..e64154eb --- /dev/null +++ b/devsite/source/_changelogs/3.10.md @@ -0,0 +1,48 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.10 - Changelog +date: 2016-03-07 +--- + +### Changes to Firmware + +* Added energy (Calorie) usage to the Health app. +* Changed "till" to "'til" in the low battery modals. +* ``rand`` is now seeded from the hardware RNG on app start, as it was in firmware 3.4 and + earlier. +* Notifications containing only an emoji will now fill the screen with that emoji, if that + emoji supports this. +* Added support for filtering notifications by app on iOS (requires iOS app 3.10). +* Fixed a window stack crash affecting some 2.x apps running on 3.9 watches. +* Fixed an error on Pebble Classic and Pebble Steel where reminders did not include their + description. +* An issue causing the sleep graph to sometimes be blank on the deep sleep display of the + Health app was resolved. +* Improved Health accuracy. +* Improved firmware stability. + +### Changes to SDK + +* Added support for debugging apps with gdb, in conjunction with pebble tool 4.2. +* Added ``health_service_get_measurement_system_for_display`` to retrieve the user's unit preference. +* Added ``health_service_sum_averaged`` and ``health_service_metric_averaged_accessible`` to access + average health data. These can be used to determine the goal line used by the Pebble Health app. +* Added ``HealthMetricRestingKCalories`` and ``HealthMetricActiveKCalories`` to retrieve Calorie burn + information from Pebble Health. +* `CFLAGS` and `LINKFLAGS` env variables are now correctly honored by the app build process. +* JSON files under the `src/js/` directory are available to `require` in newly-created projects. +* ``persist_write_bool`` and ``persist_write_int`` are now correctly documented to return the + number of bytes written on success. diff --git a/devsite/source/_changelogs/3.11.1.md b/devsite/source/_changelogs/3.11.1.md new file mode 100644 index 00000000..65884165 --- /dev/null +++ b/devsite/source/_changelogs/3.11.1.md @@ -0,0 +1,27 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.11.1 - Changelog +date: 2016-04-04 15:00:00 +--- +This is a hotfix for [SDK 3.11](/sdk/changelogs/3.11/) + +### Changes to Firmware + +* Removed a non-functional app called `JavascriptTest` from the launcher menu. + +### Changes to SDK + +None. diff --git a/devsite/source/_changelogs/3.11.md b/devsite/source/_changelogs/3.11.md new file mode 100644 index 00000000..e13160b3 --- /dev/null +++ b/devsite/source/_changelogs/3.11.md @@ -0,0 +1,47 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.11 - Changelog +date: 2016-04-04 +--- + +### Changes to Firmware + +* Added a vibrations settings menu with new custom vibration patterns. +* Added sleep classification for naps and a pin to the timeline when a nap is completed. +* Changed the cutoff time for completed sleep sessions to be 9pm so sleep sessions ending after 9pm are recorded on the following day. +* Sleep summary screens are now hidden if no sleep data is collected overnight, and sleep graphs are hidden if no sleep data has been collected for the last week. +* Added a pin to the timeline when a long walk or run is completed, displaying distance, calorie burn and duration. +* Improved step-counting algorithm for Pebble Health. +* Fixed display of jumboji for larger font sizes. +* Fixed bug causing app resources not to be removed when uninstalling an application. +* Fixed watch reset on disconnecting smartstrap during communication. +* Fixed replying to SMS on iOS when a notification contains an attachment. +* Improved low battery screen and icons. +* Improved rendering of upper-case accented characters. +* Fixed watch crash caused by invalid ANCS messages that occur when two notification-receiving devices are connected. +* Fixed bug causing the "Dismiss" action to delete voicemails when a "Dismiss" option is not available for the notification in iOS. +* Fixed never-ending phone calls on iOS8 when a second phone call is received before the first call is ended. +* Fixed a watch crash when using the "Send Text" app caused by an animation not being cleaned up. + +### Changes to SDK + +* Added new ``gdraw_command_frame_get_command_list`` API. +* Fixed project build failures when the project path contained unicode. + +### Changes to Documentation + +* Added documentation of method arguments for ``smartstrap_set_timeout``, ``smartstrap_service_is_available``, ``smartstrap_attribute_get_service_id``, ``smartstrap_attribute_get_attribute_id``, and ``smartstrap_attribute_end_write``. +* Improved description of ``CompassHeadingData`` and how to convert to a heading using ``CompassHeadingData``. \ No newline at end of file diff --git a/devsite/source/_changelogs/3.12.md b/devsite/source/_changelogs/3.12.md new file mode 100644 index 00000000..72d382b2 --- /dev/null +++ b/devsite/source/_changelogs/3.12.md @@ -0,0 +1,45 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.12 and 3.12.1 - Changelog +date: 2016-05-10 +--- + +### Changes to Firmware + +* Added Smart Alarms, which are smarter than regular alarms because they try to avoid waking you from a deep sleep. +* Added support for the Send Text app on iOS with Time-series watches and supported carriers. +* iOS users with Time-series watches and supported carriers can now respond to phone calls with text messages. +* Added Pebble Health notifications for runs and long walks, and daily activity and sleep. +* Added notification icons for Amazon, LinkedIn, Slack, Google Maps, Google Photos and the iOS Photos app (only on Time-series watches). +* Restored the ability to control general vibration strength (lost in 3.11 on Time-series watches). +* Removed weekly activity and sleep charts from the health app (they're in the phone app now). +* The default watchface, TicToc, is now written in and running as JavaScript on Pebble Time and Pebble Time Steel. +* ``health_service_get_minute_history`` now correctly rounds the start time down to the nearest minute. +* Fixed an issue where snooze on reminders sometimes never reminded you again. +* Fixed popups still showing after entering low-power mode. +* Fixed a crash when a notification is dismissed on the phone while performing a voice reply to that notification. +* Fixed the watch briefly freezing after backing out of a progress window. +* Fixed some incorrect timezone data. +* Improved rendering of the sleep ring in the Health app after more than twelve hours of sleep. +* Improved rendering of adjacent deep sleep sessions in the Health app. + +### Changes to SDK + +* Each line of build output now indicates which platform it applies to. + +### Changes to Documentation + +* Improved description of ``MenuLayer``-related constants. diff --git a/devsite/source/_changelogs/3.13.1.md b/devsite/source/_changelogs/3.13.1.md new file mode 100644 index 00000000..293d9afc --- /dev/null +++ b/devsite/source/_changelogs/3.13.1.md @@ -0,0 +1,34 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.13 - Changelog +date: 2016-06-10 +--- + +This is a hotfix for [SDK 3.13](/sdk/changelogs/3.13/). This is an SDK-only +release; no corresponding firmware exists. + +### Changes to SDK + +* Use the value of package.json's `pebble.displayName` for the app's name + everywhere, instead of sometimes using `name`, which is subject to npm naming + restrictions. +* When using a block AppMessage key (e.g. `Elements[6]`), the name of the base + key is no longer usable directly and must be accessed numerically. This change + was made to ensure that it _could_ be accessed numerically, which is the + standard use-case for these array-like keys. +* Library capabilities (e.g. `location`) are now correctly merged with app + capabilities. + diff --git a/devsite/source/_changelogs/3.13.md b/devsite/source/_changelogs/3.13.md new file mode 100644 index 00000000..731cee0c --- /dev/null +++ b/devsite/source/_changelogs/3.13.md @@ -0,0 +1,56 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.13 - Changelog +date: 2016-06-07 +--- + +This firmware was released only for the Pebble Time series. + +### Changes to Firmware + +* Added the Weather app (requires mobile app 3.13). +* Reduced confusion between walking and sleeping in Pebble Health. +* Added U+1F525 FIRE (🔥) to the supported emoji. +* `layer_set_value()` functions now always mark the layer as dirty. +* Removed complaints about sleeping less than usual if you none-the-less slept + plenty. +* Improved Pebble Health summary notifications +* ``gcolor_equal()`` now considers all transparent functions to be equivalent. + This change only affects apps built with SDK 3.13 or later. + +### Changes to SDK + +* [We launched Pebble Packages](/blog/2016/06/07/pebble-packages/), our new, + npm-based package solution! + * Check out our [Pebble Package guides](/guides/pebble-packages/)! + * **appinfo.json** is deprecated; long live **package.json**! Use + `pebble convert-project` to update your app. + * We now include Pebble Packages that you have installed in your + `node_modules` folder for use in the C or PebbleKit JS code of your app. + * We also include standard node modules for use in PebbleKit JS (but + compatibility varies). + * We can now create packages as a new project type. Try + `pebble new-package`! + * Packages can also use ``AppMessage`` and resources without identifier + conflicts. +* `require` in PebbleKit JS now requires explicitly using relative paths when + loading files in your app (i.e. prefixing filenames with `./`). +* ``AppMessage`` keys are now included in your C code with a `MESSAGE_KEY_` + prefix. They are also included in your PebbleKit JS code as the return value + of `require("message_keys")`. +* ``AppMessage`` keys can be auto-assigned, so numbers don't have to be picked + explicitly for communication between C and PebbleKit JS. This is designed to + enable libraries to function without conflicts. diff --git a/devsite/source/_changelogs/3.14.md b/devsite/source/_changelogs/3.14.md new file mode 100644 index 00000000..5b0d83cc --- /dev/null +++ b/devsite/source/_changelogs/3.14.md @@ -0,0 +1,35 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.14 - Changelog +date: 2016-07-06 +--- + +This firmware was released only for the Pebble Time series. + +### Changes to Firmware + +* Adjusted the Health alerts to be more positive and less condescending. +* "Average" is now "Typical", to match the phone apps. +* Improved the smart alarm algorithm. +* Improved syncing of health data to the phone. +* Fixed a potential crash loop when receiving unexpected ANCS data on iOS. + + +### Changes to SDK + +* Added ``PBL_API_EXISTS`` macro, to test whether a function exists in a given + SDK at compile time. This macro will also be added to the 4.0-dp3 preview. +* Fixed a bug that caused messageKeys containing an underscore to be mangled. diff --git a/devsite/source/_changelogs/3.2.1.md b/devsite/source/_changelogs/3.2.1.md new file mode 100644 index 00000000..3711ae4a --- /dev/null +++ b/devsite/source/_changelogs/3.2.1.md @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.2.1 - Changelog +date: 2015-08-12 +--- + +This is a hotfix release for [SDK 3.2](/sdk/changelogs/3.2/). + +### Known issues + +* Taking screenshots from physical watches running firmware 3.2 or 3.2.1 usually doesn't work. + We expect to fix this in firmware 3.3. + +### Changes to Firmware + +* Apps compiled using SDK versions earlier than 3.2 which caused heap corruption but + did not crash will now emit warnings instead of crashing. Apps compiled using SDK + 3.2 or later will crash when a corrupt heap is detected. +* Resolved an issue causing emojis to display as boxes when replying to a notification using + the large notification font. +* Resolved multiple issues caused by having unsupported language packs installed. + +### Changes to the SDK + +None. + +### Changes to the Pebble Tool + +* Fixed a crash in the pebble tool when receiving a log containing a non-ASCII character. + +### Changes to the emulator and phone simulator + +* Fixed JavaScript apps crashing when attempting to log non-ASCII characters. + +### Changes to documentation + +None. diff --git a/devsite/source/_changelogs/3.2.md b/devsite/source/_changelogs/3.2.md new file mode 100644 index 00000000..4cc8f9d0 --- /dev/null +++ b/devsite/source/_changelogs/3.2.md @@ -0,0 +1,103 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.2 - Changelog +date: 2015-07-22 +--- + +### Known issues + +* Taking screenshots from physical watches running firmware 3.2 usually doesn't work. + We expect to fix this in firmware 3.3. + +### Changes to Firmware + +* Improved the reliability of app wakeup service when it is in use by multiple apps. +* Added generic icons to the Watchfaces app for watchfaces without one. +* Improved rendering of non-antialiased rounded rectangles. +* Improved rendering of timeline pins with long body text. +* Improved rendering of notifications. +* Improved rendering of timeline pins using the Reminder layout. +* Improved the music app on Android, in conjunction with the Pebble Time Android app + version 3.2. +* Added settings for backlight brightness and duration. +* Added settings for notification font size and vibration intensity. +* Restored the "dismiss all" action on notifications (long press or use the menu). +* Significantly improved music control reliability on iOS. +* Improved music control on Android, in conjunction with version 3.2 of the Android app. +* Fixed a crash when forcibly terminating the launcher. +* Improved the behavior of the app fetch UI after a bluetooth reconnection. + + +### Changes to SDK + +* ``ANIMATION_PLAY_COUNT_INFINITE`` got lost, but it's back now. +* Added ``gcolor_legible_over``, which returns a color legible over some background color. + +### Changes to Pebble Tool + +We scrapped everything and rewrote it from scratch! + +* Introducing [libpebble2]({{ site.links.libpebble }}), the successor to libpebble. + If you want to write tools that interact with a watch, this is the library to use. +* Also introducing [pebble-tool](https://github.com/pebble/pebble-tool), the successor to the + old 'pebble' command, which was also part of libpebble. + +The following user-visible changes were made: + +* You can now connect to your phone via the CloudPebble proxy, even if the network blocks + local connections. Pass `--cloudpebble` to do this. +* The pebble tool will now prompt you to opt in to analytics on first launch. While this is + optional, it would be very helpful if you said yes. + * If you had previously opted out, that will still be respected and the question will be + skipped. +* Analytics are no longer collected synchronously, which should reduce pauses while using the + pebble tool. +* Launching and connecting to the emulator should now be much more reliable. +* Both aplite and basalt emulators can now run concurrently. +* There is no longer a default action if no connection is specified. However, if exactly one + emulator is running, that will be used by default. +* Screenshots are now color-corrected by default. Use `--no-correction` to disable correction. +* You can now pass a filename to the screenshot command. +* On OS X, screenshots will automatically open once they are taken. This can be disabled by + passing `--no-open`. +* Screenshots now display a progress bar while the are taken. +* `--debug` and `--debug-phonesim` have both been removed. They have been replaced by `-v`. + * You can use up to four v's (`-vvvv`) for increased verbosity. At this level, all protocol + messages will be displayed in both deserialised and binary forms. +* `pebble build` now respects `-v` (up to three v's) to increase the verbosity of build output. +* `pebble build` can now pass arguments to your wscript; place them after a `--`. +* Log output from `pebble logs` and `pebble install --logs` is now in color. +* Added `pebble logout`. +* `pebble wipe` now only wipes data for the current SDK version, and will not log you out. + * To wipe *everything*, use `pebble wipe --everything`. +* `pebble app-config` has been renamed to `pebble emu-app-config`. +* The following commands have been removed: `list`, `rm`, `current`, `uuids`, `coredump`. +* The environment produced by `pebble repl` has changed: it now provides a libpebble2 + [`PebbleConnection`](http://libpebble2.readthedocs.org/en/v0.0.7/connection/#libpebble2.communication.PebbleConnection) + object named `pebble` and places the pebble protocol messages under `protocol`. +* Lightblue support was removed, but direct serial connections are still available using + `--serial`, e.g. `--serial /dev/cu.PebbleTimeDF7C-SerialPo`. + + +### Changes to Emulator/Phonesim + +* Updated the phone simulator to use libpebble2. + +### Changes to Documentation + +* [PebbleKit JS is now documented](/docs/pebblekit-js/). +* [A PebbleKit Android tutorial was added](/getting-started/android-tutorial/part1/). +* [A PebbleKit iOS tutorial was added](/getting-started/ios-tutorial/part1/). diff --git a/devsite/source/_changelogs/3.3.md b/devsite/source/_changelogs/3.3.md new file mode 100644 index 00000000..f145f9e4 --- /dev/null +++ b/devsite/source/_changelogs/3.3.md @@ -0,0 +1,51 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.3 - Changelog +date: 2015-08-19 +--- + +### Changes to Firmware + +* Screenshots once again work reliably. The display will now freeze briefly while taking a screenshot. +* Added "relationship bars" to the timeline, which indicate whether events overlap, are + back-to-back, or have time between them. +* Added the date to timeline day separators. +* Added groundwork for improved Android notification behavior (requires a future Android update). +* Further improved rendering of long paragraphs in timeline pins. +* Fixed MenuLayer animations in non-full-screen menu layers. +* Fixed a crash when passing ``data_logging_log`` a NULL pointer. +* Fixed timezone error in Chile. +* Resolved a privilege escalation exploit. + +### Changes to SDK + +* Added ``ActionMenu``, for easy hierarchical menus as seen in notifications, timeline, and + throughout the system UI. +* Added ``app_focus_service_subscribe_handlers``, which enables apps to better synchronize with + system animations. +* Exported ``property_animation_update_gcolor8``, for animating between colors. + +### Changes to Pebble Tool + +None. + +### Changes to Emulator/Phonesim + +None. + +### Changes to Documentation + +None. diff --git a/devsite/source/_changelogs/3.4.md b/devsite/source/_changelogs/3.4.md new file mode 100644 index 00000000..01388d09 --- /dev/null +++ b/devsite/source/_changelogs/3.4.md @@ -0,0 +1,46 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.4 - Changelog +date: 2015-09-09 +--- + +### Changes to Firmware + +* Improved calendar and reminder pin display. +* Added support for internationalization and localization. +* Added standby mode (disables bluetooth when the watch is not being worn). + * This can be toggled in Settings > System > Stand-by Mode. +* Renamed "Do Not Disturb" to "Quiet Time" and expanded functionality. + * Can now disable notifications during calendar events. + * Holding back from a watchface will now toggle Quiet Time. + * Quiet Time can be enabled from notifications. +* Increased timeline paragraph length limit. + +### Changes to SDK + +* Enabled [timeline HTTP actions](/guides/pebble-timeline/pin-structure/) (requires iOS app 3.2 or Android app 3.4). +* Added [Smartstrap APIs](/guides/smartstraps/). + + +### Changes to Pebble Tool + +* Added the [`pebble emu-control` command](/guides/tools-and-resources/pebble-tool/), + which enables sending real-time accelerometer and compass data from phones + to the pebble emulator. + +### Changes to Emulator/Phonesim + +None. diff --git a/devsite/source/_changelogs/3.6-dp2.md b/devsite/source/_changelogs/3.6-dp2.md new file mode 100644 index 00000000..815ede22 --- /dev/null +++ b/devsite/source/_changelogs/3.6-dp2.md @@ -0,0 +1,57 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6-dp2 - Changelog +date: 2015-09-23 +--- + +This changelog collects only changes visible to developers using the emulator. A complete +changelog will be provided when 3.6 ships on physical watches. + +### Changes to Firmware + +* Added support for [circular watches](/round/). +* Window stack animations were disabled on Chalk. +* Adjusted the display of [`ActionBarLayers`](``ActionBarLayer``) for circular watches. +* Adjusted the display of [`ActionMenus`](``ActionMenu``) for circular watches. +* Increased the height of the ``StatusBarLayer`` on Chalk (16 to 20 pixels). + +### Changes to SDK + +* Added the new "Chalk" platform for the Pebble Time Round. +* Added `PBL_ROUND`, `PBL_RECT` and `PBL_PLATFORM_CHALK` defines. +* Added ``PBL_IF_ROUND_ELSE``, ``PBL_IF_RECT_ELSE``, ``PBL_IF_COLOR_ELSE`` and + ``PBL_IF_BW_ELSE`` macros. ``COLOR_FALLBACK`` is now considered deprecated. +* Added ``graphics_fill_radial`` and ``graphics_draw_arc``, for drawing partial circles. +* Added ``gpoint_from_polar`` and ``grect_centered_from_polar`` for conversion from + polar to cartesian coordinates. +* Added ``ContentIndicator``, which provides a visual indicator of content above or + below the display. +* Added ``menu_layer_set_center_focused``, which forces the highlighted menu item to always + be in the center of the display. This is the preferred configuration for menus on Chalk. +* Added ``DEG_TO_TRIGANGLE``, the inverse of the pre-existing ``TRIGANGLE_TO_DEG``. +* Added ``GBitmapFormat8BitCircular``, the new framebuffer format used on Chalk. +* Added ``gbitmap_get_data_row_info``, because ``GBitmapFormat8BitCircular`` does not have + a constant number of bytes per row. This should now be used for all framebuffer + manipulation. + + +### Changes to Pebble Tool + +* Added support for Chalk and circular displays. + +### Changes to Emulator/Phonesim + +* Added support for Chalk and circular displays. diff --git a/devsite/source/_changelogs/3.6-dp5.md b/devsite/source/_changelogs/3.6-dp5.md new file mode 100644 index 00000000..cb5a0ea6 --- /dev/null +++ b/devsite/source/_changelogs/3.6-dp5.md @@ -0,0 +1,54 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6-dp5 - Changelog +date: 2015-10-02 +--- + +This changelog covers only changes since 3.6-dp2. Developer previews 3 and 4 were never shipped. + +### Changes to Firmware + +* Made the status bar on Chalk (Pebble Time Round) four pixels taller (from 20 to 24 pixels). +* Resolved an issue causing AppMessages to sometimes be dropped when sending both ways. +* Improved backlight behaviour when dictating text. +* Fixed a calculation error in ``grect_centered_from_polar``. +* Resolved multiple issues that occured when reusing a ``DictationSession``. + +### Changes to SDK + +* Added the ``GTextAttributes`` structure, with methods for defining text flow and paging on a + circular display. +* Added support for ``GTextAttributes`` to ``graphics_draw_text``. That `NULL` parameter that + you've been tacking on the end for two and a half years now actually does something. +* Added methods to ``TextLayer`` for flowing and paging text on a circular display. +* Added paging support to ``ScrollLayer``. +* Improved the return codes from the dictation API. + + +### Changes to Pebble Tool + +* Added the [`pebble transcribe` command](/guides/tools-and-resources/pebble-tool/), enabling + testing dictation in the emulator. +* Added the [`pebble data-logging` command](/guides/tools-and-resources/pebble-tool/), + to download stored datalogging from the + watch to the computer. + +### Changes to Emulator/Phonesim + +* Added a translucent ring around the inside of the emulator to remind you of masked pixels. +* Resolved an issue causing appmessage events to fire repeatedly. +* Resolved an issue causing app installs to frequently fail on Linux. +* Resolved an issue causing emulated Bluetooth to be broadly unreliable. diff --git a/devsite/source/_changelogs/3.6-dp6.md b/devsite/source/_changelogs/3.6-dp6.md new file mode 100644 index 00000000..03c56b0b --- /dev/null +++ b/devsite/source/_changelogs/3.6-dp6.md @@ -0,0 +1,45 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6-dp6 - Changelog +date: 2015-10-07 +--- + +This changelog covers only changes since 3.6-dp5. + +### Known Issues + +* Unfocused menu layer cells on Chalk may have their content inappropriately truncated. + This behavior will improve before the final 3.6 release. + +### Changes to Firmware + +* Added window stack transition animations on Chalk (subject to change). +* Correctly centred the loading bar for the voice API on Chalk. +* Removed an unexpected artifact at the end of ActionMenus on Chalk. + +### Changes to SDK + +* Added ``menu_layer_is_index_selected``. +* Added constants for the heights of menu cells on Chalk. + + +### Changes to Pebble Tool + +* None. + +### Changes to Emulator/Phonesim + +* Fixed a series of issues causing AppMessages larger than 2048 bytes to be lost. diff --git a/devsite/source/_changelogs/3.6-dp7.md b/devsite/source/_changelogs/3.6-dp7.md new file mode 100644 index 00000000..5b08c9f7 --- /dev/null +++ b/devsite/source/_changelogs/3.6-dp7.md @@ -0,0 +1,47 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6-dp7 - Changelog +date: 2015-10-09 +--- + +This changelog covers only changes since 3.6-dp6. + +### Known Issues + +* Unfocused menu layer cells on Chalk may have their content inappropriately truncated. + This behavior will improve before the final 3.6 release. + +### Changes to Firmware + +* MenuLayers will now correctly enable center focus by default on Chalk. +* Resolved an issue causing iOS notifications to cease after invoking the "call back" + action. +* Pebble Time Round no longer vibrates when charging is completed. +* The action menu nub is now slightly larger on Pebble Time Round. + + +### Changes to SDK + +* ``DEG_TO_TRIGANGLE`` no longer inappropriately normalizes angles. + + +### Changes to Pebble Tool + +* None. + +### Changes to Emulator/Phonesim + +* Fixed a JavaScript runtime crash when sending non-ASCII characters by AppMessage. diff --git a/devsite/source/_changelogs/3.6-dp8.md b/devsite/source/_changelogs/3.6-dp8.md new file mode 100644 index 00000000..8d134807 --- /dev/null +++ b/devsite/source/_changelogs/3.6-dp8.md @@ -0,0 +1,44 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6-dp8 - Changelog +date: 2015-10-09 +--- + +This changelog covers only changes since 3.6-dp7. + +### Known Issues + +* Unfocused menu layer cells on Chalk may have their content inappropriately truncated. + This behavior will improve before the final 3.6 release. + +### Changes to Firmware + +* Fixed an issue causing the voice API to not return a transcription when confirmation is + enabled. + + +### Changes to SDK + +* None. + + +### Changes to Pebble Tool + +* None. + +### Changes to Emulator/Phonesim + +* None. diff --git a/devsite/source/_changelogs/3.6.2.md b/devsite/source/_changelogs/3.6.2.md new file mode 100644 index 00000000..185e9326 --- /dev/null +++ b/devsite/source/_changelogs/3.6.2.md @@ -0,0 +1,45 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6.2 - Changelog +date: 2015-11-04 +--- + +This SDK corresponds with firmware 3.6.2, which only shipped on Pebble Time Round. + +### Changes to Firmware + +* Action bars are now less curved on Chalk. +* Added Chalk support to the sports API. +* Improved text rendering in unfocused menu layer cells on Chalk. +* Some missing animations were added on Chalk. +* Resolved an issue with alarms when DST starts or ends. + +### Changes to SDK + +* The recommended menu layer heights on Chalk have changed. + ``MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT``, + ``MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT``, ``MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT`` and + ``MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT`` were updated accordingly. + + +### Changes to Pebble Tool + +* Resolved an issue causing emulator launches that raise EINTR to fail. + + +### Changes to Emulator/Phonesim + +* None. diff --git a/devsite/source/_changelogs/3.6.md b/devsite/source/_changelogs/3.6.md new file mode 100644 index 00000000..30011631 --- /dev/null +++ b/devsite/source/_changelogs/3.6.md @@ -0,0 +1,83 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.6 - Changelog +date: 2015-10-14 +--- + +This changelog contains all changes since SDK 3.4. SDK 3.5 was never shipped. + +### Known Issues + +* Unfocused menu layer cells on Chalk may have their content inappropriately truncated. + This behavior will improve before Pebble Time Round is released. + +### Changes to Firmware + +* Added support for [circular watches](/round/). +* Window stack animations were disabled on Chalk. +* Adjusted the display of [`ActionBarLayers`](``ActionBarLayer``) for circular watches. +* Adjusted the display of [`ActionMenus`](``ActionMenu``) for circular watches. +* Increased the height of the ``StatusBarLayer`` on Chalk (16 to 24 pixels). +* Improved backlight behavior when dictating text. +* Added support for 8 KiB AppMessage buffers. + +### Changes to SDK + +* Added the new "Chalk" platform for the Pebble Time Round. +* Added the [Dictation API](/guides/pebble-apps/sensors/dictation/), enabling voice input on + the Basalt and Chalk platforms. +* Added `PBL_ROUND`, `PBL_RECT` and `PBL_PLATFORM_CHALK` defines. +* Added ``PBL_IF_ROUND_ELSE``, ``PBL_IF_RECT_ELSE``, ``PBL_IF_COLOR_ELSE`` and + ``PBL_IF_BW_ELSE`` macros. ``COLOR_FALLBACK`` is now considered deprecated. +* Added ``graphics_fill_radial`` and ``graphics_draw_arc``, for drawing partial circles. +* Added ``gpoint_from_polar`` and ``grect_centered_from_polar`` for conversion from + polar to cartesian coordinates. +* Added ``ContentIndicator``, which provides a visual indicator of content above or + below the display. +* Added ``menu_layer_set_center_focused``, which forces the highlighted menu item to always + be in the center of the display. This is the default configuration for menus on Chalk. +* Added ``menu_layer_is_index_selected``. +* Added constants for the heights of menu cells on Chalk: ``MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT``, + ``MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT``, ``MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT`` and + ``MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT``. +* Added ``DEG_TO_TRIGANGLE``, the inverse of the pre-existing ``TRIGANGLE_TO_DEG``. +* Added ``GBitmapFormat8BitCircular``, the new framebuffer format used on Chalk. +* Added ``gbitmap_get_data_row_info``, because ``GBitmapFormat8BitCircular`` does not have + a constant number of bytes per row. This should now be used for all framebuffer + manipulation. +* Added the ``GTextAttributes`` structure, with methods for defining text flow and paging on a + circular display. +* Added support for ``GTextAttributes`` to ``graphics_draw_text``. That `NULL` parameter that + you've been tacking on the end for two and a half years now actually does something. +* Added methods to ``TextLayer`` for flowing and paging text on a circular display. +* Added paging support to ``ScrollLayer``. + + +### Changes to Pebble Tool + +* Added support for Chalk and circular displays. +* Added the [`pebble transcribe` command](/guides/tools-and-resources/pebble-tool/), + enabling testing dictation in the emulator. +* Added the [`pebble data-logging` command](/guides/tools-and-resources/pebble-tool/), + to download stored datalogging from the watch to the computer. +* Added the [`pebble emu-time-format` commmand](/guides/tools-and-resources/pebble-tool/), + allowing switching of the emulator between 12-hour and 24-hour formats. + + +### Changes to Emulator/Phonesim + +* Added support for Chalk and circular displays. +* Fixed a JavaScript runtime crash when sending non-ASCII characters by AppMessage. diff --git a/devsite/source/_changelogs/3.7.md b/devsite/source/_changelogs/3.7.md new file mode 100644 index 00000000..d516c2e5 --- /dev/null +++ b/devsite/source/_changelogs/3.7.md @@ -0,0 +1,46 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.7 - Changelog +date: 2015-11-23 +--- + +Firmware 3.7 will not appear to be available unless you have the latest version of the +iOS or Android app. + +### Changes to Firmware + +* Added support for SMS notification replies on iOS, if you use AT&T. +* Added the date to the icon in calendar timeline pins. +* Restored the reminder icon to reminder notifications on Pebble Time Round. +* Fixed text clipping in the incoming call window in some cases. +* Improved rendering of clipped, antialiased GPaths. + +### Changes to SDK + +* Resolved an issue present since SDK 3.3 causing JavaScript changes to be ignored by + incremental builds. + + +### Changes to Pebble Tool + +* None. + + +### Changes to Emulator/Phonesim + +* Added an option to block access to local IP addresses to the phonesim. This blocking + is only active on CloudPebble. + diff --git a/devsite/source/_changelogs/3.8-beta10.md b/devsite/source/_changelogs/3.8-beta10.md new file mode 100644 index 00000000..37559630 --- /dev/null +++ b/devsite/source/_changelogs/3.8-beta10.md @@ -0,0 +1,43 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8-beta10 - Changelog +date: 2015-12-09 +--- + +This is a pre-release version of the Pebble SDK. Only changes since beta8 are included. + +### Changes to Firmware + +* Resolved an issue causing alarms to not vibrate in standby mode +* Added `shortSubtitle` property to all timeline layouts. + * This string, if provided, will be used as the subtitle in the timeline list view. +* Added a `displayTime` enum to the timeline weather layout. + * If `none`, it will not display a time. If `pin`, it will display the time of the + pin in the title. The default is `pin`. +* Timeline pins were no longer last updated in 1970. +* Resolved some minor layout issues. + + +### Changes to SDK + +* Added `PBL_MICROPHONE` and `PBL_SMARTSTRAP` macros, which will be defined when a + microphone or a smartstrap connector is available, respectively. Note that the + runtime availability of the services should still be tested, as the presence of + hardware is not sufficient. +* Removed spuriously exported `_EMOJI` fonts. Emoji symbols will display in system + fonts as before. +* Fixed an issue causing the generation of indexed PBIs containing non-black transparent + pixels to fail. diff --git a/devsite/source/_changelogs/3.8-beta12.md b/devsite/source/_changelogs/3.8-beta12.md new file mode 100644 index 00000000..473434fc --- /dev/null +++ b/devsite/source/_changelogs/3.8-beta12.md @@ -0,0 +1,35 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8-beta12 - Changelog +date: 2015-12-14 +--- + +This is a pre-release version of the Pebble SDK. Only changes since beta10 are included. + +### Changes to Firmware + +* ``graphics_fill_radial`` will now produce dithered 50% gray on Aplite where appropriate. +* Fixed an issue causing reduced stack availability after using dictation. +* Slightly reduced stack usage in general. +* Added support for 3.8 to 2.9 downgrades (but no data is retained when doing this). +* Restored the missing "Call back" button to iOS missed call pins. +* Improved stability. + + +### Changes to SDK + +* Fixed an issue causing SDK 3.8 apps to install on firmware 3.6 and 3.7, but then + potentially crash at runtime. They now display a compatibility error when launched. diff --git a/devsite/source/_changelogs/3.8-beta8.md b/devsite/source/_changelogs/3.8-beta8.md new file mode 100644 index 00000000..d2e5d598 --- /dev/null +++ b/devsite/source/_changelogs/3.8-beta8.md @@ -0,0 +1,52 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8-beta8 - Changelog +date: 2015-12-02 +--- + +This is a pre-release version of the Pebble SDK. + +### Changes to Firmware + +* Added support for Pebble Classic and Pebble Steel watches! + * You can read more [on our blog](/blog/2015/12/02/Bringing-the-Family-Back-Together/) + * This is a major change! Please test your aplite apps thoroughly after rebuilding with + SDK 3.8. +* Improved the rendering of ``NumberWindow``. +* Fixed a race when exiting an ``ActionMenuLevel``. +* Timeline pins no longer require icons, and will pick a sane default. +* Restored the double vibration when _disabling_ Quiet Time (enabling is still a single vibration). +* Resolved a memory leak in ``DictationSession``. +* Assorted minor design changes. + +### Changes to SDK + +* Deprecated `png`, `pbi` and `pbi8` in favor of a new `bitmap` resource type. + [Read more on our blog!](/blog/2015/12/02/Bitmap-Resources/). +* Font resources are now somewhat smaller than they used to be. + + +### Changes to Pebble Tool + +SDK 3.8 is the first version exclusively available via the +[new pebble tool](/blog/2015/12/01/A-New-Pebble-Tool/). + +No future SDK packages will contain the tool, or any changes to the tool. + + +### Changes to Emulator/Phonesim + +As with the pebble tool, the emulator and phone simulator are no longer coupled to the SDK. diff --git a/devsite/source/_changelogs/3.8.1.md b/devsite/source/_changelogs/3.8.1.md new file mode 100644 index 00000000..c73d064d --- /dev/null +++ b/devsite/source/_changelogs/3.8.1.md @@ -0,0 +1,33 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8.1 - Changelog +date: 2015-12-16 +--- + +This release is currently SDK-only. In particular, the timeline text color +fix will not be available on watches until we ship a new firmware build +~~later this~~ next week. + +### Changes to Firmware + +* Fixed a timeline crash that only occurred on Aplite when run in an SDK + emulator in a release build. +* Fixed timeline `foregroundColor` being used inappropriately in the + timeline list view, sometimes leading to white-on-white text. + +### Changes to SDK + +* None diff --git a/devsite/source/_changelogs/3.8.2.md b/devsite/source/_changelogs/3.8.2.md new file mode 100644 index 00000000..fe74d4ca --- /dev/null +++ b/devsite/source/_changelogs/3.8.2.md @@ -0,0 +1,31 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8.2 - Changelog +date: 2015-12-21 +--- + +This release also includes the changes from [3.8.1](/sdk/changelogs/3.8.1/), +which was not shipped on physical watches. + +### Changes to Firmware + +* Fixed a crash when trying to unload a cached app. +* Fixed a crash showing health pins when using a non-English language pack. + +### Changes to SDK + +* Fixed an issue causing font processing failures to lead to the build system + hanging until forcibly terminated. diff --git a/devsite/source/_changelogs/3.8.md b/devsite/source/_changelogs/3.8.md new file mode 100644 index 00000000..f0184c70 --- /dev/null +++ b/devsite/source/_changelogs/3.8.md @@ -0,0 +1,65 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.8 - Changelog +date: 2015-12-15 +--- + +### Changes to Firmware + +* Added support for Pebble Classic and Pebble Steel watches! + * You can read more [on our blog](/blog/2015/12/02/Bringing-the-Family-Back-Together/) + * This is a major change! Please test your aplite apps thoroughly after rebuilding with + SDK 3.8. +* Added Pebble Health! + * No API is yet available to access health information, but one will be added in + an upcoming release. +* Improved the rendering of ``NumberWindow``. +* Fixed a race when exiting an ``ActionMenuLevel``. +* Timeline pins no longer require icons, and will pick a sane default. +* Resolved an issue causing alarms to not vibrate in standby mode +* Added `shortSubtitle` property to all timeline layouts. + * This string, if provided, will be used as the subtitle in the timeline list view. +* Added a `displayTime` enum to the timeline weather layout. + * If `none`, it will not display a time. If `pin`, it will display the time of the + pin in the title. The default is `pin`. +* Timeline pins were no longer last updated in 1970. +* Resolved a memory leak in ``DictationSession``. +* Restored the double vibration when _disabling_ Quiet Time (enabling is still a single vibration). +* Restored the missing "Call back" button to iOS missed call pins. +* Assorted minor design changes. + +### Changes to SDK + +* Deprecated `png`, `pbi` and `pbi8` in favor of a new `bitmap` resource type. + [Read more on our blog!](/blog/2015/12/02/Bitmap-Resources/). +* Added `PBL_MICROPHONE` and `PBL_SMARTSTRAP` macros, which will be defined when a + microphone or a smartstrap connector is available, respectively. Note that the + runtime availability of the services should still be tested, as the presence of + hardware is not sufficient. +* Font resources are now somewhat smaller than they used to be. + + +### Changes to Pebble Tool + +SDK 3.8 is the first version exclusively available via the +[new pebble tool](/blog/2015/12/01/A-New-Pebble-Tool/). + +No future SDK packages will contain the tool, or any changes to the tool. + + +### Changes to Emulator/Phonesim + +As with the pebble tool, the emulator and phone simulator are no longer coupled to the SDK. diff --git a/devsite/source/_changelogs/3.9-beta5.md b/devsite/source/_changelogs/3.9-beta5.md new file mode 100644 index 00000000..a0153ee6 --- /dev/null +++ b/devsite/source/_changelogs/3.9-beta5.md @@ -0,0 +1,63 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9-beta5 - Changelog +date: 2016-01-19 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +### Known Issues + +* `HealthLightLevel` will be renamed to ``AmbientLightLevel`` before SDK 3.9 ships. +* Bluetooth may be more unreliable in this beta release. + +### Changes to Firmware + +* Resolved confusion caused by manipulating the window stack while it is animating. +* Fixed a crash when enabling text pagination on aplite. +* Improved the reliability of smartstrap communication when the system is busy. +* Fixed calendar pins sometimes omitting the location on aplite. +* Reduced the memory overhead for PNG decompression to 1152 bytes (plus space for both + the compressed and decompressed versions of the image). +* Fixed an issue causing "call back" on missed call pins on iOS to not actually + call back. +* Fixed an issue causing sleep graphs to not be drawn at all on the deep sleep + screen in the Health app unless there was deep sleep to display. +* Multiple timezone fixes, including Uruguay and Cancun. +* Made system watchface transitions snappier on Pebble Time. +* Redesigned the watch-only mode watchface. +* Changed the low-battery percentage warnings to time estimates. +* Added U+1F3B5 MUSICAL NOTE 🎵 and U+1F381 WRAPPED PRESENT 🎁 to the notification + font. +* Added an indicator of picture notifications on iOS. +* Redesigned Tic-Toc on the Rose Gold Pebble Time Round. +* Moved the hands on Tic-Toc on Pebble Time Round one pixel to the left. +* Assorted health tracking improvements. +* Assorted stability improvements. +* Assorted localization improvements. + +### Changes to SDK + +* Added the new [Health API](``HealthService``)! + * This enables you to read information from Pebble Health and include it in your + app or watchface. + * We are interested in feedback on this API! Let us know what you think + ~~on Slack or by contacting developer support~~ on [Discord]({{ site.links.discord_invite }})! +* Fixed an issue introduced in 3.8.2 causing some _successful_ font builds to hang + the build process indefinitely. +* Added ``PBL_IF_MICROPHONE_ELSE`` and ``PBL_IF_SMARTSTRAP_ELSE`` macros, for consistency + with ``PBL_IF_COLOR_ELSE`` and friends. diff --git a/devsite/source/_changelogs/3.9-beta7.md b/devsite/source/_changelogs/3.9-beta7.md new file mode 100644 index 00000000..fceaf6ed --- /dev/null +++ b/devsite/source/_changelogs/3.9-beta7.md @@ -0,0 +1,41 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9-beta7 - Changelog +date: 2016-01-29 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +The following release notes list only changes since [3.9-beta5](/sdk/changelogs/3.9-beta5/) + +### Changes to Firmware + +* Fixed a crash when calling ``health_service_any_activity_accessible``. +* Ensured that ``HealthEventSignificantUpdate`` is called immediately when subscribing + to the health service, as documented. +* Fixed a failure to correctly read the ambient light sensor. +* Further watchface transition tweaks. +* Improved firmware stability. +* Improved bluetooth reliability. + +### Changes to SDK + +* `HealthLightLevel` was renamed to ``AmbientLightLevel`` +* Added `PBL_HEALTH` and `PBL_IF_HEALTH_ELSE` macros. +* Added support for multiple JavaScript files! Check out + [the blog post](/blog/2016/01/29/Multiple-JavaScript-Files/) for more details. + diff --git a/devsite/source/_changelogs/3.9-beta8.md b/devsite/source/_changelogs/3.9-beta8.md new file mode 100644 index 00000000..f60171f7 --- /dev/null +++ b/devsite/source/_changelogs/3.9-beta8.md @@ -0,0 +1,45 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9-beta8 - Changelog +date: 2016-02-01 +--- + +This is a pre-release SDK, containing a preview of the new Pebble Health +API. + +**Important note**: We have changed the values of ``HealthServiceAccessibilityMask``. This is +not a binary compatible change, so you _must_ recompile watchfaces that use +``health_service_metric_accessible`` or ``health_service_any_activity_accessible``. +Additionally, ``HealthServiceAccessibilityMaskAvailable`` is no longer zero, so code that +tried to compare with zero will need to be updated. + +The following release notes list only changes since [3.9-beta7](/sdk/changelogs/3.9-beta7/). + +### Changes to Firmware + +* Fixed ``health_service_get_minute_history`` sometimes returning empty minutes. +* Fixed a bug causing the backlight to get stuck on at full intensity. +* Reduced watchface transition animation choppiness. +* Improved firmware stability. + +### Changes to SDK + +* Changed the values of ``HealthServiceAccessibilityMask` so that + ``HealthServiceAccessibilityMaskAvailable`` is nonzero. This is an ABI incompatible change, + and watchfaces must be recompiled with beta8 to run correctly on beta8 or later + firmware. +* Added support for `require`ing a JSON file from PebbleKit JS, if the wscript is adjusted + appropriately. diff --git a/devsite/source/_changelogs/3.9.1.md b/devsite/source/_changelogs/3.9.1.md new file mode 100644 index 00000000..e5109f19 --- /dev/null +++ b/devsite/source/_changelogs/3.9.1.md @@ -0,0 +1,26 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9.1 - Changelog +date: 2016-02-04 +--- + +### Changes to Firmware + +* Fixed a number of DST-related timezone errors introduced in 3.9. + +### Changes to SDK + +None; no SDK was shipped. diff --git a/devsite/source/_changelogs/3.9.2.md b/devsite/source/_changelogs/3.9.2.md new file mode 100644 index 00000000..18d86409 --- /dev/null +++ b/devsite/source/_changelogs/3.9.2.md @@ -0,0 +1,28 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9.2 - Changelog +date: 2016-02-09 +--- + +### Changes to Firmware + +* Resolved the backlight not activating on Kickstarter-era Pebbles on which the + backlight intensity had never been set. +* Fixed a potential crash after updating the firmware with a low battery. + +### Changes to SDK + +* Fixed an error when attempting to `require()` JSON files. diff --git a/devsite/source/_changelogs/3.9.md b/devsite/source/_changelogs/3.9.md new file mode 100644 index 00000000..7ed08c4d --- /dev/null +++ b/devsite/source/_changelogs/3.9.md @@ -0,0 +1,57 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 3.9 - Changelog +date: 2016-02-03 +--- + +### Changes to Firmware + +* Resolved confusion caused by manipulating the window stack while it is animating. +* Fixed a crash when enabling text pagination on aplite. +* Improved the reliability of smartstrap communication when the system is busy. +* Fixed calendar pins sometimes omitting the location on aplite. +* Reduced the memory overhead for PNG decompression to 1152 bytes (plus space for both + the compressed and decompressed versions of the image). +* Fixed an issue causing "call back" on missed call pins on iOS to not actually + call back. +* Fixed an issue causing sleep graphs to not be drawn at all on the deep sleep + screen in the Health app unless there was deep sleep to display. +* Multiple timezone fixes, including Uruguay and Cancun. +* Made system watchface transitions snappier on Pebble Time. +* Redesigned the watch-only mode watchface. +* Changed the low-battery percentage warnings to time estimates. +* Added U+1F3B5 MUSICAL NOTE 🎵 and U+1F381 WRAPPED PRESENT 🎁 to the notification + font. +* Added an indicator of picture notifications on iOS. +* Redesigned Tic-Toc on the Rose Gold Pebble Time Round. +* Moved the hands on Tic-Toc on Pebble Time Round one pixel to the left. +* Assorted health tracking improvements. +* Assorted stability improvements. +* Assorted localization improvements. + +### Changes to SDK + +* Added the new [Health API](/guides/events-and-services/health/)! + * This enables you to read information from Pebble Health and include it in your + app or watchface. +* Added support for multiple JavaScript files! Check out + [the blog post](/blog/2016/01/29/Multiple-JavaScript-Files/) for more details. +* Fixed an issue introduced in 3.8.2 causing some _successful_ font builds to hang + the build process indefinitely. +* Added ``PBL_IF_MICROPHONE_ELSE``, ``PBL_IF_SMARTSTRAP_ELSE``, and + ``PBL_IF_HEALTH_ELSE`` macros, for consistency with ``PBL_IF_COLOR_ELSE`` and + friends. This also includes new `PBL_MICROPHONE`, `PBL_SMARTSTRAP`, and + `PBL_HEALTH` defines. diff --git a/devsite/source/_changelogs/4.0-beta17.md b/devsite/source/_changelogs/4.0-beta17.md new file mode 100644 index 00000000..4aeac2e2 --- /dev/null +++ b/devsite/source/_changelogs/4.0-beta17.md @@ -0,0 +1,38 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0-beta17 - Changelog +date: 2016-08-16 +--- + +This is a developer preview release of SDK 4.0. No firmware is provided. + +### Changes to Firmware + +* Added support for [running developer apps written in JavaScript](/blog/2016/08/15/introducing-rockyjs-watchfaces/) + +### Changes to SDK + +* AppGlance icons must now be provided as references to `publishedMedia` + in package.json. Providing plain resource IDs will not work. +* Custom timeline icons can now be created via `publishedMedia`. +* Added support initial for building JavaScript apps. These apps won't + support aplite. + +### Known Issues + +* `publishedMedia` is completely undocumented. Updated documentation will + be available soon. In the interim, continue using 4.0-dp3 to develop + appglances. diff --git a/devsite/source/_changelogs/4.0-dp1.md b/devsite/source/_changelogs/4.0-dp1.md new file mode 100644 index 00000000..e4bfe037 --- /dev/null +++ b/devsite/source/_changelogs/4.0-dp1.md @@ -0,0 +1,80 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0-dp1 - Changelog +date: 2016-06-15 +--- + +This is the first Developer Preview release of the brand new Pebble SDK 4.0. +This changelog collects only changes visible to developers using the emulator. +A complete changelog will be provided when 4.0 ships on physical watches. + +### Changes to Firmware + +* Added support for [Pebble 2](https://www.kickstarter.com/projects/597507018/pebble-2-time-2-and-core-an-entirely-new-3g-ultra). +* Added [AppGlances](/guides/user-interfaces/appglance-c/) and Timeline +~~Peek~~ Quick View. + +### Changes to SDK + +* Added the new "Diorite" platform for the Pebble 2. +* Added `PBL_COMPASS`, `PBL_SMARTSTRAP_POWER`, and `PBL_PLATFORM_DIORITE` + defines. +* Added ``preferred_result_display_duration`` to get the recommended number of + milliseconds a result window should be visible before it should closed. +* Added ``AppExitReason`` and ``exit_reason_set`` +for an application to be able to notify the system of +[the reason it is exiting](/guides/user-interfaces/app-exit-reason/). +* Added ``AppGlanceSlice``, ``AppGlanceResult``, ``AppGlanceReloadSession``, +``app_glance_add_slice``, ``AppGlanceReloadCallback`` and ``app_glance_reload``. +to support [AppGlances](/guides/user-interfaces/appglance-c/). +* Added [Unobstructed Area APIs](/guides/user-interfaces/unobstructed-area/): +``UnobstructedAreaWillChangeHandler``, +``UnobstructedAreaChangeHandler``, +``UnobstructedAreaDidChangeHandler``, +``UnobstructedAreaHandlers``, +``layer_get_unobstructed_bounds``, +``unobstructed_area_service_subscribe`` +and ``unobstructed_area_service_unsubscribe`` +to enable a watchface to adapt to overlays partially obstructing it, such as +during a Timeline ~~Peek~~ Quick View. +* Added ``HealthMetricAlert``, ``HealthAggregation``, ``health_service_peek_current_value``, + ``health_service_peek_current_value``, ``health_service_aggregate_averaged``, + ``health_service_aggregate_averaged``, + ``health_service_metric_aggregate_averaged_accessible``, + ``health_service_register_metric_alert``, + ``health_service_register_metric_alert`` and + ``health_service_cancel_metric_alert`` in preparation for heart rate support. + Note that these are not yet implemented. +* Report memory usage for Pebble Packages at build time. + +### Changes to Documentation + +* Added [AppGlances Guide](/guides/user-interfaces/appglance-c/) +* Added [Unobstructed Area Guide](/guides/user-interfaces/unobstructed-area/) +* Added [AppExitReason Guide](/guides/user-interfaces/app-exit-reason) +* Added [One Click Action Guide](/guides/design-and-interaction/one-click-actions/) +* Added API documentation for new ``HealthService``, ``App Glance``, ``UnobstructedArea`` and ``AppExitReason`` APIs. + +### Known Issues + +* Creating an ``AppGlanceSlice`` with a .expiration_time of + `APP_GLANCE_SLICE_NO_EXPIRATION_TIME` results in an AppGlanceSlice that is + never displayed. +* The ``UnobstructedAreaHandlers`` object must be created before being passed into + the ``unobstructed_area_service_subscribe`` method. +* The Diorite emulator is still a little shy and will occassionally shake upon + starting. If your emulator doesn't stop shaking, try installing an app - that + usually brings it out of its shell. diff --git a/devsite/source/_changelogs/4.0-dp2.md b/devsite/source/_changelogs/4.0-dp2.md new file mode 100644 index 00000000..d30e587b --- /dev/null +++ b/devsite/source/_changelogs/4.0-dp2.md @@ -0,0 +1,39 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0-dp2 - Changelog +date: 2016-06-22 +--- + +This is a developer preview release of SDK 4.0. No firmware is provided. + +### Changes to Firmware + +* Fixed a bug causing ``APP_GLANCE_SLICE_NO_EXPIRATION`` to mean "expire immediately" + rather than the intended "expire never". +* Fixed a bug causing empty app glances (i.e. attempts to remove all glances) to be + ignored. + +### Changes to SDK + +* None. + +### Known Issues + +* The `UnobstructedAreaHandler` object must be created before being passed into + the `unobstructed_area_service_subscribe` method. +* The Diorite emulator is still a little shy and will occassionally shake upon + starting. If your emulator doesn't stop shaking, try installing an app - that + usually brings it out of its shell. diff --git a/devsite/source/_changelogs/4.0-dp3.md b/devsite/source/_changelogs/4.0-dp3.md new file mode 100644 index 00000000..bf845f58 --- /dev/null +++ b/devsite/source/_changelogs/4.0-dp3.md @@ -0,0 +1,43 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0-dp3 - Changelog +date: 2016-07-06 +--- + +This is a developer preview release of SDK 4.0. No firmware is provided. + +### Changes to Firmware + +* Fixed diorite vibrating on boot until cancelled. +* App icons can now be provided in PDC format. +* Changed the watchfaces icon! + +### Changes to SDK + +* Renamed AppGlanceSlice.layout's `template_string` to + `subtitle_template_string` and `icon_resource_id` to `icon`. You will need to + update your apps appropriately for them to compile. +* The compass service will now return ``CompassStatusUnavailable`` on diorite + watches, which do not have a compass. Apps built with SDK 2 or SDK 3 will + still see ``CompassStatusDataInvalid``. +* Added a ``PBL_API_EXISTS`` macro, for checking if an API is available in a + the current SDK at compile time. This macro was also added to SDK 3.14. +* Fixed a bug that caused messageKeys containing an underscore to be mangled. + +### Known Issues + +* The `UnobstructedAreaHandler` object must be created before being passed into + the `unobstructed_area_service_subscribe` method. diff --git a/devsite/source/_changelogs/4.0-rc20.md b/devsite/source/_changelogs/4.0-rc20.md new file mode 100644 index 00000000..9f656e20 --- /dev/null +++ b/devsite/source/_changelogs/4.0-rc20.md @@ -0,0 +1,38 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0-rc20 - Changelog +date: 2016-08-29 +--- + +This is a developer release candidate for SDK 4.0. No firmware is provided. + +### Changes to Firmware + +* Apps now correctly launch when installed in the SDK shell. +* Rocky bugfixes. +* Other bugfixes. + +### Changes to SDK + +* This SDK will not install in any pebble tool prior to 4.4-rc1. +* Corrected outdated AppGlance-related definitions in the aplite compatibility + header. + +### Known Issues + +* Using system icons for appglances is currently broken. Using custom icons for + them, however, works fine. +* `pebble gdb` is currently broken. diff --git a/devsite/source/_changelogs/4.0.1.md b/devsite/source/_changelogs/4.0.1.md new file mode 100644 index 00000000..57b81554 --- /dev/null +++ b/devsite/source/_changelogs/4.0.1.md @@ -0,0 +1,36 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0.1 - Changelog +date: 2016-09-06 +--- + +This is a hotfix for [SDK 4.0](/sdk/changelogs/4.0/). +The #pebblepast will be televized. + +### Changes to Firmware + +* Timeline Past can now be assigned to a Quick Launch button. +* Fixed an issue causing the Tylt Vü to not function. +* Fixed a number of issues with AppGlance icons. + +### Changes to SDK + +* Restored the functionality of `pebble gdb` (requires pebble tool 4.4.1) + +### Known issues + +* Timeline Past is still not available in the emulator; it will be restored in + a later update. diff --git a/devsite/source/_changelogs/4.0.md b/devsite/source/_changelogs/4.0.md new file mode 100644 index 00000000..efe7a47c --- /dev/null +++ b/devsite/source/_changelogs/4.0.md @@ -0,0 +1,75 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.0 - Changelog +date: 2016-08-30 +--- + +### Changes to Firmware + +* Added support for [Pebble 2]({{ site.links.kickstarter3 }}). +* Added [AppGlances](/guides/user-interfaces/appglance-c/) and [Timeline Quick View](/guides/user-interfaces/unobstructed-area/). +* Removed Timeline Past. + +### Changes to SDK + +* Added the new "Diorite" platform for the Pebble 2. +* Added support for writing watchfaces in JavaScript running directly on the watch using + [Rocky.js](/blog/2016/08/15/introducing-rockyjs-watchfaces/). +* Added `PBL_COMPASS`, `PBL_SMARTSTRAP_POWER`, and `PBL_PLATFORM_DIORITE` + defines. +* Added ``preferred_result_display_duration`` to get the recommended number of + milliseconds a result window should be visible before it should be closed. +* Added ``AppExitReason`` and ``exit_reason_set`` +for an application to be able to notify the system of +[the reason it is exiting](/guides/user-interfaces/app-exit-reason/). +* Added ``AppGlanceSlice``, ``AppGlanceResult``, ``AppGlanceReloadSession``, +``app_glance_add_slice``, ``AppGlanceReloadCallback`` and ``app_glance_reload``. +to support [AppGlances](/guides/user-interfaces/appglance-c/). +* Added [Unobstructed Area APIs](/guides/user-interfaces/unobstructed-area/): +``UnobstructedAreaWillChangeHandler``, +``UnobstructedAreaChangeHandler``, +``UnobstructedAreaDidChangeHandler``, +``UnobstructedAreaHandlers``, +``layer_get_unobstructed_bounds``, +``unobstructed_area_service_subscribe`` +and ``unobstructed_area_service_unsubscribe`` +to enable a watchface to adapt to overlays partially obstructing it, such as +during a Timeline Quick View. +* The compass service will now return ``CompassStatusUnavailable`` on diorite + watches, which do not have a compass. Apps built with SDK 2 or SDK 3 will + still see ``CompassStatusDataInvalid``. +* Report memory usage for Pebble Packages at build time. +* Fixed bug causing zero-length JS files to result in build failures. + +### Changes to Documentation + +* Added [AppGlances Guide](/guides/user-interfaces/appglance-c/) +* Added [Unobstructed Area Guide](/guides/user-interfaces/unobstructed-area/) +* Added [AppExitReason Guide](/guides/user-interfaces/app-exit-reason) +* Added [One Click Action Guide](/guides/design-and-interaction/one-click-actions/) +* Added API documentation for new ``App Glance``, ``UnobstructedArea`` and ``AppExitReason`` APIs. + +### Known Issues + +* `pebble gdb` is currently broken. +* AppGlances cannot currently use system icons. +* Custom AppGlance icons may not work if the assigned published media ID does not happen + to also be a standard resource ID. This can usually be worked around by assigning published + media IDs starting at 1 and counting up. +* For apps not using AppGlances, the system default icon may appear instead of the app-specified + menu icon if the resources defined for the app vary between platforms. This can be worked + around by defining the menu icon resource before defining any resources that use the + `targetPlatforms` attribute. diff --git a/devsite/source/_changelogs/4.1.1.md b/devsite/source/_changelogs/4.1.1.md new file mode 100644 index 00000000..6bc97b42 --- /dev/null +++ b/devsite/source/_changelogs/4.1.1.md @@ -0,0 +1,36 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.1.1 - Changelog +date: 2016-09-29 +--- + +This is a hotfix for [SDK 4.1](/sdk/changelogs/4.1/). No corresponding firmware exists. + +### Changes to Firmware + +None. + +### Changes to SDK + +* Fixed a build failure when using only tagged icons. +* Fixed a failure to validate the correct icon or published resource when using tagged resources. + +### Known issues + +* ``health_service_metric_accessible`` always returns `false` when + checking the accessibility of ``HealthMetricHeartRateBPM``. + Instead, use + `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` diff --git a/devsite/source/_changelogs/4.1.2.md b/devsite/source/_changelogs/4.1.2.md new file mode 100644 index 00000000..63112677 --- /dev/null +++ b/devsite/source/_changelogs/4.1.2.md @@ -0,0 +1,37 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.1.2 and 4.1.3 - Changelog +date: 2016-10-04 +--- + +4.1.2 is a hotfix for [firmware 4.1](/sdk/changelogs/4.1/). +4.1.3 removes some debug apps that were inadvertently included in 4.1.2. + +### Changes to Firmware + +* Fixed an issue where the Rocky.js `Date` constructor did not work for dates in October + on a leap year. This also fixes an issue where Tic-Toc would fail after one minute. + +### Changes to SDK + +None. + +### Known issues + +* ``health_service_metric_accessible`` always returns `false` when + checking the accessibility of ``HealthMetricHeartRateBPM``. + Instead, use + `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` diff --git a/devsite/source/_changelogs/4.1.4.md b/devsite/source/_changelogs/4.1.4.md new file mode 100644 index 00000000..3ff44fbe --- /dev/null +++ b/devsite/source/_changelogs/4.1.4.md @@ -0,0 +1,38 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.1.4 - Changelog +date: 2016-10-05 +--- + +This is a hotfix for [SDK 4.1](/sdk/changelogs/4.1/). No corresponding firmware exists. + +### Changes to Firmware + +None. + +### Changes to SDK + +* Fixed a build failure when using only tagged icons. +* Fixed a failure to validate the correct icon or published resource when using tagged resources. + +(This is the same as 4.1.1, but the fixes were inadvertently reverted in 4.1.2.) + +### Known issues + +* ``health_service_metric_accessible`` always returns `false` when + checking the accessibility of ``HealthMetricHeartRateBPM``. + Instead, use + `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` diff --git a/devsite/source/_changelogs/4.1.md b/devsite/source/_changelogs/4.1.md new file mode 100644 index 00000000..03c4e66d --- /dev/null +++ b/devsite/source/_changelogs/4.1.md @@ -0,0 +1,48 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.1 - Changelog +date: 2016-09-27 +--- + +### Changes to Firmware + +* Added support for Pebble 2 and the Diorite platform. +* New and improved launcher animations for rectangular watches. +* Fully implemented heartrate APIs. +* Fixed a crash when using the compass in the emulator. +* Fixed a _different_ crash when using the compass on a real watch. +* Fixed a crash when using templated AppGlance strings on Pebble 2 watches. +* Fixed Rocky apps crashing on Chalk. +* Improved launcher performance. +* Updated the timezone database. + +### Changes to SDK + +* Added some missing heartrate-related aplite compatibility macros. +* Added Pebble 2 color codes to ``WatchInfoModel``. +* Timeline past is now accessible in the SDK shell using the up button. +* Oversized menu icons will now fail at build time instead of runtime. +* `publishedMedia` can now reference resources inside Pebble Packages. + +### Known issues + +* If you use platform tags on your menu icon (e.g. `icon~bw.png` and `icon~color.png`, + but no `icon.png`), your build will fail. You can work around this by creating an + unused, un-tagged icon. This file will be validated, so it must be a valid icon. +* ``health_service_metric_accessible`` always returns `false` when + checking the accessibility of ``HealthMetricHeartRateBPM``. + Instead, use + `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` diff --git a/devsite/source/_changelogs/4.2-beta4.md b/devsite/source/_changelogs/4.2-beta4.md new file mode 100644 index 00000000..eb11af22 --- /dev/null +++ b/devsite/source/_changelogs/4.2-beta4.md @@ -0,0 +1,64 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.2-beta4 - Changelog +date: 2016-10-12 +--- + +This is a developer preview for SDK 4.2. No firmware is provided. + +### Changes to Firmware + +* Emery can now run apps compiled for Basalt (SDK 4.1 or earlier) or Aplite + (SDK 3.7 or earlier) in "bezel mode". +* Fixed ``health_service_metric_accessible`` for heartrate-related metrics. +* Rocky.js: The reliability of the ``postMessage`` API has been improved. +* Rocky.js: `postmessageconnected`, `postmessagedisconnected` and `postmessageerror` + events have been added. +* Rocky.js: Using regular expressions no longer results in substantial app log spam. +* Rocky.js: The default app template has been improved. +* Rocky.js: The coordinate system was adjusted by 0.5 pixels; (0, 0) now refers to + top left of the first pixel, rather than the center of the pixel. +* Rocky.js: The `memorypressure` event has been added. When fired with + `{level: 'high'}`, the app must free up sufficient memory or it will be + terminated. +* Rocky.js: Content size is exposed via ``UserPreferences``. +* Rocky.js: `watchInfo.`platform now works as expected in the emulator. +* Rocky.js: Removed the global `print()` function; use `console.log()`. +* Rocky.js: Fixed a crash when passing invalid values to `clearTimeout` or + `clearInterval`. +* Rocky.js: Provided constructors for `CanvasRenderingContext2D`, `RockyCanvasElement` + and `Event`. +* Rocky.js: Removed unimplemented methods that previously threw an exception when called, + allowing for polyfills of those methods. +* Rocky.js: Added the ability to unsubscribe from events using ``removeEventListener`` or + ``off``. +* Bug fixes and improvements. + + +### Changes to SDK + +* Added support for the Emery platform. +* Rocky.js is now considered stable, and can be used to submit apps to the appstore. +* Added ``preferred_content_size()`` to retrieve the user's preferred font size. +* All JS bundling is now performed using [webpack](https://webpack.github.io) + * Only JavaScript files that are actually used are now bundled. + + +### Known Issues + +* Clay does not work after building with SDK 4.2-beta4. +* Apps that use an external bundler and therefore expect `require` to exist at + runtime in order to `require('message_keys')` will not work. diff --git a/devsite/source/_changelogs/4.2-beta5.md b/devsite/source/_changelogs/4.2-beta5.md new file mode 100644 index 00000000..8ff53783 --- /dev/null +++ b/devsite/source/_changelogs/4.2-beta5.md @@ -0,0 +1,30 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.2-beta5 - Changelog +date: 2016-10-14 +--- + +This is a developer preview for SDK 4.2. No firmware is provided. + +### Changes to Firmware + +* Rocky.js: assorted postMessage fixes. + + +### Changes to SDK + +* Fixed apps that use an external bundler and expect `require` to exist at runtime. + * This also fixes Clay. diff --git a/devsite/source/_changelogs/4.2.1.md b/devsite/source/_changelogs/4.2.1.md new file mode 100644 index 00000000..4b404521 --- /dev/null +++ b/devsite/source/_changelogs/4.2.1.md @@ -0,0 +1,34 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.2.1 - Changelog +date: 2016-10-19 +--- + +This is a hotfix for [SDK 4.2](/sdk/changelogs/4.2/). No corresponding firmware exists. + +### Changes to Firmware + +None. + +### Changes to SDK + +* Removed the use of `eval` in JavaScript bundling that was causing incorrect behavior + when running in the emulator and on iOS. + +### Known issues + +* JavaScript errors do not have a useful filename or line number. You may find it helpful + to look up the line in build/pebble-js-app.js to determine what the error is referring to. diff --git a/devsite/source/_changelogs/4.2.2.md b/devsite/source/_changelogs/4.2.2.md new file mode 100644 index 00000000..1472c80d --- /dev/null +++ b/devsite/source/_changelogs/4.2.2.md @@ -0,0 +1,30 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.2.2 - Changelog +date: 2016-11-02 +--- + +This is a hotfix for [SDK 4.2](/sdk/changelogs/4.2/). No corresponding firmware exists. + +### Changes to Firmware + +* The glyph size limit for Emery has been increased. It will be increased for Basalt, + Chalk, and Diorite in a future update. + +### Changes to SDK + +* App builds now generate a sourcemap for PebbleKit JS code, which the pebble tool can + use to resolve line numbers in debug output. diff --git a/devsite/source/_changelogs/4.2.md b/devsite/source/_changelogs/4.2.md new file mode 100644 index 00000000..b1946432 --- /dev/null +++ b/devsite/source/_changelogs/4.2.md @@ -0,0 +1,57 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.2 - Changelog +date: 2016-10-18 +--- + +### Changes to Firmware + +* Emery can now run apps compiled for Basalt (SDK 4.1 or earlier) or Aplite + (SDK 3.7 or earlier) in "bezel mode". +* Fixed ``health_service_metric_accessible`` for heartrate-related metrics. + * Note that, as heartrate data is only retained for two hours, HRM accessibility + checks going back further than two hours will always return `false`. +* Rocky.js: The reliability of the ``postMessage`` API has been improved. +* Rocky.js: `postmessageconnected`, `postmessagedisconnected` and `postmessageerror` + events have been added. +* Rocky.js: Using regular expressions no longer results in substantial app log spam. +* Rocky.js: The coordinate system was adjusted by 0.5 pixels; (0, 0) now refers to + top left of the first pixel, rather than the center of the pixel. +* Rocky.js: The `memorypressure` event has been added. When fired with + `{level: 'high'}`, the app must free up sufficient memory or it will be + terminated. +* Rocky.js: Content size is exposed via ``UserPreferences``. +* Rocky.js: `watchInfo.platform` now works as expected in the emulator. +* Rocky.js: Removed the global `print()` function; use `console.log()`. +* Rocky.js: Fixed a crash when passing invalid values to `clearTimeout` or + `clearInterval`. +* Rocky.js: Provided constructors for `CanvasRenderingContext2D`, `RockyCanvasElement` + and `Event`. +* Rocky.js: Removed unimplemented methods that previously threw an exception when called, + allowing for polyfills of those methods. +* Rocky.js: Added the ability to unsubscribe from events using ``removeEventListener`` or + ``off``. +* Bug fixes and improvements. + + +### Changes to SDK + +* Added support for the Emery platform. +* Rocky.js is now considered stable, and can be used to submit apps to the appstore. +* Rocky.js: The default app template has been improved. +* Added ``preferred_content_size()`` to retrieve the user's preferred font size. +* All JS bundling is now performed using [webpack](https://webpack.github.io) + * Only JavaScript files that are actually used are now bundled. diff --git a/devsite/source/_changelogs/4.3.md b/devsite/source/_changelogs/4.3.md new file mode 100644 index 00000000..8b35ace8 --- /dev/null +++ b/devsite/source/_changelogs/4.3.md @@ -0,0 +1,69 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK 4.3 - Changelog +date: 2016-11-23 +--- + +### Changes to Firmware + +* Added heart rate zones to the Health app. +* Added a heart rate measurement characteristic to the Pebble GATT profile, enabling +mobile apps to retrieve live heart rate data from Pebble watches with a built-in heart +rate monitor. +* Added ``HealthMetricHeartRateRawBPM`` to allow access to raw heart rate measurements +from supported watches. +* Added Kickstart watchface. +* Added the display of heart rate measurements in the sports app when a 3rd party app +uses the Sports API. +* Added voice reminders on Pebble for iOS users. +* Improved measurements of heart rate on Pebble 2 devices. +* Bug fixes and improvements. + + +### Changes to SDK + +* Fixed a bug where the Rocky.js `minutechange` event was not emitted when the hour or +day changed. +* Added ``quiet_time_is_active`` to permit C apps to check if Quiet Time is active on a +watch. +* Added the alias `app_package.json` to the webpack.config.js file, to allow Pebble +Packages to `require('app_package.json')`, which will require an app's package.json or +appinfo.json file during JS bundling. +* Added support for specifying custom PebbleKit JS and Rocky.js entry points from +within a Rocky.js project's package.json file. + + +### Changes to PebbleKit Android (v4.0.1) + +* Added new APIs for sending custom HR data and a custom label/value from a mobile companion +app, using the Sports API. +* Added a helper object that will automatically manage state and message construction for +updates to the Sports app on a Pebble watch. +* Updated the Constant ``ICON_MAX_DIMENSIONS`` to be 25px, to be consistent with watches +running 4.0 firmware. If your provided icon exceeds 25px x 25px, you will encounter a runtime +error when calling ``customizeWatchApp``. + + +### Changes to PebbleKit iOS (v4.0.0) + +* Added support for Diorite and Emery watches. +* Added new APIs for sending custom HR data and a custom label/value from a mobile app. +* Added a helper object that will automatically manage state and message construction for +updates to the Sports app on a Pebble watch. +* Removed Bluetooth Classic support. Mobile apps compiled with this version of PebbleKit +will only communicate with watches running FW 3.8 and later, which use BLE for +communications. Mobile apps using PebbleKit iOS 4.0.0 and later will no longer need to be +whitelisted for use with Pebble. diff --git a/devsite/source/_data/authors.yml b/devsite/source/_data/authors.yml new file mode 100644 index 00000000..2c9de380 --- /dev/null +++ b/devsite/source/_data/authors.yml @@ -0,0 +1,78 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a list of all of the Pebble blog authors. +# If you're writing a blog post for the first time, add an entry for yourself! + +alex: + name: Alex Lin +alexey: + name: Alexey Komissarouk + photo: https://avatars1.githubusercontent.com/u/268126?v=1&s=100 +brad: + name: Brad Murray + photo: https://avatars2.githubusercontent.com/u/33967?v=3&s=100 +cat: + name: Cat Haines + photo: https://avatars1.githubusercontent.com/u/1187089?v=3&s=100 +cherie: + name: Cherie Williams + photo: https://avatars1.githubusercontent.com/u/318582?v=1&s=100 + job: Developer Evangelist +chrislewis: + name: Chris Lewis + photo: https://avatars3.githubusercontent.com/u/5732010?v=3&s=100 +jonb: + name: Jon Barlow + photo: https://avatars0.githubusercontent.com/u/876597?v=3&s=100 +meiguro: + name: Meiguro + photo: https://avatars2.githubusercontent.com/u/4428900?v=3&s=100 +katharine: + name: Katharine Berry + photo: https://avatars1.githubusercontent.com/u/110792?v=3&s=100 +katherine: + name: Katherine McAuliffe + photo: https://avatars1.githubusercontent.com/u/8052313?v=3&s=100 +keegan: + name: Keegan Lillo + photo: https://avatars1.githubusercontent.com/u/3537963?v=3&s=100 +kirby: + name: Kirby Kohlmorgen + photo: https://avatars1.githubusercontent.com/u/1156201?v=3&s=100 +pebble: + name: Team Pebble + photo: https://avatars0.githubusercontent.com/u/1048268?v=1&s=100 +rperryng: + name: Ryan Perry-Nguyen + photo: https://avatars3.githubusercontent.com/u/6402435?v=3&s=100 +ryan: + name: Ryan Case +thomas: + name: Thomas Sarlandie + photo: https://avatars1.githubusercontent.com/u/779005?v=3&s=100 +tom: + name: Tom Maremaa +niharika: + name: Niharika Bedekar +lukasz: + name: Łukasz Zalewski + photo: https://avatars3.githubusercontent.com/u/4211799?v=3&s=100 + +# To fix in posts: +# Chris Lewis +# team pebble +# cherie & thomas +# Matt Clark (SetPebble) +# tom@getpebble.com diff --git a/devsite/source/_data/docs.yaml b/devsite/source/_data/docs.yaml new file mode 100644 index 00000000..ec75425e --- /dev/null +++ b/devsite/source/_data/docs.yaml @@ -0,0 +1,20 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +c: PebbleSDK-4.3_docs.zip +# c_preview: PebbleSDK-4.2-beta4_docs.zip +pebblekit_ios: pebblekit_ios_4.0.0.zip +pebblekit_android: pebblekit_android_4.0.1.zip +pebblekit_js: jsdocs-pkjs +rocky_js: jsdocs-rocky diff --git a/devsite/source/_data/env.yaml b/devsite/source/_data/env.yaml new file mode 100644 index 00000000..f6d63105 --- /dev/null +++ b/devsite/source/_data/env.yaml @@ -0,0 +1,46 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- config: url + env: URL +- config: https_url + env: HTTPS_URL +- config: baseurl + env: BASEURL +- config: asset_path + env: ASSET_PATH +- config: external_server + env: EXTERNAL_SERVER +- config: algolia_app_id + env: ALGOLIA_APP_ID +- config: algolia_api_key + env: ALGOLIA_API_KEY +- config: algolia_search_key + env: ALGOLIA_SEARCH_KEY +- config: algolia_prefix + env: ALGOLIA_PREFIX +- config: docs_url + env: DOCS_URL +- config: google_analytics + env: GOOGLE_ANALYTICS +- config: debug + env: DEBUG +- config: sdk_bucket + env: SDK_BUCKET +- config: rollbar_client_token + env: ROLLBAR_CLIENT_TOKEN +- config: rack_env + env: RACK_ENV +- config: skip_docs + env: SKIP_DOCS diff --git a/devsite/source/_data/events.yml b/devsite/source/_data/events.yml new file mode 100644 index 00000000..6158032e --- /dev/null +++ b/devsite/source/_data/events.yml @@ -0,0 +1,134 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +upcoming: + - date: November 7-8th + month: November 2014 + name: Toyota Hackathon in Redwood City, CA + url: http://www.eventbrite.com/e/toyota-itc-connected-vehicle-hackathon-tickets-13303967525?aff=zvents + items: + - "Up to 5 Pebbles as prize for team with: Best use of Pebble API" + - $50 refurbished Pebbles for developers to purchase + - Mentors on-site throughout the event + + - date: November 7-9th + month: November 2014 + name: HackATL in Atlanta, GA + url: http://hackatl.org/ + items: + - Up to 10 Pebbles given to those who make Pebble hacks! + - Limited loaner Pebbles available at the event to develop on + + - date: November 8-9th + month: November 2014 + name: UnituHack in London, England + url: http://unituhack.com + items: + - "Up to 5 Pebbles as prize for team with: Best use of Pebble API" + - Limited loaner Pebbles available at the event to develop on + - $50 refurbished Pebbles for developers to purchase + - Mentors on-site throughout the event + + - date: November 15-16th + month: November 2014 + name: Hack Shanghai in Shanghai, China + url: http://www.hackshanghai.com/ + items: + - "5 Pebbles as prize for team with: Best use of Pebble API" + - 11 Pebbles as prize for various prize tiers + +past: + - date: October 11-12th + month: October 2014 + name: HackRU in New Brunswick, NJ + url: http://www.hackru.org/ + items: + - "Mystery # of Pebbles for team with: Best use of Pebble API" + - $75 refurbished Pebbles for developers to purchase + - Loaner Pebbles available through the MLH Hardware Lab at the event + - Pebble API Workshop + - Mentors on-site throughout the event + - T-shirts, stickers, other swag + + - date: October 17-19th + month: October 2014 + name: BoilerMake in West Lafayette, IN + url: http://www.boilermake.org/ + items: + - "Up to 4 Pebble Steels as prize for team with: Best use of Pebble API" + - $4500 in Pebbles as various prizes + - $50 refurbished Pebbles for developers to purchase + - $80 new Pebbles for purchase + - Loaner Pebbles available through the MLH Hardware Lab at the event + - 30 min Pebble API Workshop + - Mentors on-site throughout the event + - Stickers at table + + - date: October 17-18th + month: October 2014 + name: DubHacks in Seattle, WA + url: http://dubhacks.co/ + items: + - 5 Pebbles as a prize + - Loaner Pebbles available through the MLH Hardware Lab at the event + - Remote Pebble support + + - date: October 19-21st + month: October 2014 + name: Modev Wearables + Things in Washington, DC + url: http://wnt2014.gomodev.com/ + items: + - Hackathon + Conference presence + - 10 Pebbles as prize at hackathon + - $50 refurbished Pebbles for developers to purchase + - 30 min Pebble API Workshop on Oct 20th + - Mentor on-site throughout the event + + - date: October 20-21st + month: October 2014 + name: Internet of Things Conference in San Francisco, CA + url: http://iotaconf.com/index.html + items: + - $80 new Pebbles (all colors) for sale + - $180 gift box Pebble Steels (black/steel) for sale + - 30 min presentation on advanced graphics + - Stickers and other swag available + - Team Pebble on site to answer questions + + - date: October 25-26th + month: October 2014 + name: TeenHacks in Fullerton, CA + url: http://teenhacks.org/ + items: + - 4 Pebbles as a prize + - Remote Pebble support + - Stickers available + + - date: October 30th + month: October 2014 + name: The Perfect Trip DevCon 2014 in San Francisco, CA + url: https://developer.concur.com/devcon/PerfectTripFundAwards + items: + - 8 Pebbles as a prize + - Remote Pebble support + - Stickers available + + - date: November 5th + month: November 2014 + name: Toyota Pre-Hack Meetup in Redwood City, CA + url: http://www.eventbrite.com/e/nestgsv-connected-vehicle-meetup-tickets-13838032929?aff=NestGSV + items: + - $50 refurbished Pebbles for developers to purchase + - Pebble API Demo + - Mentors on-site throughout the event diff --git a/devsite/source/_data/examples.yaml b/devsite/source/_data/examples.yaml new file mode 100644 index 00000000..b0232934 --- /dev/null +++ b/devsite/source/_data/examples.yaml @@ -0,0 +1,458 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- title: Pebble Faces + repo: pebble-examples/pebble-faces + languages: + - c + - js + hardware_platforms: + - aplite + - basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/pebble-faces/11305757db5fa9ce5e448aa0ba4e7beb8a38b020/screenshots/screenshot.png + screenshot_platform: aplite + description: | + This Pebble application downloads, de-compresses and displays PNG images + from the Internet. + featured: true + tags: + - Images +- title: Weather Cards Example + repo: pebble-examples/cards-example + languages: + - c + hardware_platforms: + - basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/cards-example/master/screenshots/screenshot1.gif + screenshot_platform: basalt + description: | + This example shows how to implement a cards based weather app, demonstrating the Pebble Draw Commands API, which allows you to draw and animate vector graphics. + featured: true + tags: + - Graphics + - Color +- title: KS Clock Face + repo: pebble-examples/ks-clock-face + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/ks-clock-face/master/screenshots/screenshot-color.png + description: | + Animated color Pebble watchface from the Pebble Time Kickstarter video. + Expands from a dot, sweeping the clock arms as it does so. + tags: + - Color + - Animation +- title: Concentricity + repo: pebble-examples/concentricity + languages: + - c + hardware_platforms: + - chalk + - basalt + - aplite + featured: true + screenshot_platform: chalk + screenshot: https://raw.githubusercontent.com/pebble-examples/concentricity/master/screenshots/concentricity~chalk.png + description: | + Example watchface for aplite, basalt and chalk showing the hours, minutes and seconds with + concentric rings around the center of the display. + tags: + - Graphics + - Color + - Watchface +- title: Time Dots + repo: pebble-examples/time-dots + languages: + - c + hardware_platforms: + - chalk + featured: true + screenshot_platform: chalk + screenshot: https://raw.githubusercontent.com/pebble-examples/time-dots/master/screenshot.png + description: | + Example watchface for Pebble Time Round showing the hours and minutes as a + bar and dots. + tags: + - Color +- title: ContentIndicator Demo + repo: pebble-examples/content-indicator-demo + languages: + - c + hardware_platforms: + - chalk + featured: true + screenshot_platform: chalk + screenshot: https://raw.githubusercontent.com/pebble-examples/content-indicator-demo/master/screenshot.png + description: | + Example watchapp showing use of the ContentIndicator UI component. + tags: + - Color +- title: owm-weather + repo: pebble-hacks/owm-weather + languages: + - c + - js + hardware_platforms: + - aplite + - basalt + - chalk + featured: true + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-hacks/owm-weather/master/screenshots/basalt.png + no_cloudpebble: true + description: | + Library for easy fetching of weather data from OpenWeatherMap.org. Includes + a simple test app as a proof of concept usage of a weather C API. + tags: + - Color + - Libraries + - Watchface + - PebbleKit +- title: Tricorder + repo: pebble-examples/tricorder + no_cloudpebble: true + languages: + - c + - java + - objective-c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/tricorder/72e677141285b3ac2bf4659d6e88ba9c61dc500b/screenshot.png + description: | + Datalogging example app that communicates with PebbleKit Android and iOS + companion apps. Records many different stats such as connection state, + battery level, etc. + featured: true + tags: + - Color + - Datalogging + - PebbleKit +- title: Block World + repo: pebble-hacks/block-world + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://github.com/pebble-hacks/block-world/raw/master/screenshots/screenshot.png + description: | + Simple 'block-builder' game for Pebble SDK 3.0. Uses modified PGE and + isometric libraries to show a grid of 16 x 16 x 14 blocks that can be placed + by the user. + featured: true + tags: + - Color + - Graphics + - Animation + - Game +- title: Pandas and Bananas + repo: pebble-hacks/pandas-and-bananas + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://github.com/pebble-hacks/pandas-and-bananas/raw/master/screenshots/screenshot.png + description: | + Color game for Pebble SDK 3.0 that sees the user controlling a panda via + accelerometer tilting or buttons to catch fruit and avoid bombs. + featured: true + tags: + - Color + - Graphics + - Animation + - Game +- title: GBitmapSequence Example + repo: pebble-hacks/gbitmap-sequence-example + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://github.com/pebble-hacks/gbitmap-sequence-example/raw/master/screenshots/gbitmap-sequence.gif + description: | + Example app showing the usage of the GBItmapSequence API in Pebble SDK 3.0. + tags: + - Color + - Graphics + - Animation +- title: Isotime + repo: pebble-hacks/isotime + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://github.com/pebble-hacks/isotime/raw/master/screenshots/screenshot.png + description: | + Color watchface for Pebble SDK 3.0. Uses PGE plus PGE Isometric libraries to + show the time on digits built with individual segments. Staggered animation + of segments that change each minute. Sleeps completely between animations. + tags: + - Color + - Graphics + - Animation + - Watchface +- title: PDC Image + repo: pebble-examples/pdc-image + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/pdc-image/master/screenshots/screenshot.png + description: | + A simple example Pebble project demonstrating how to use the Draw Commands + API to load and display a vector file in the Pebble Drawing Commands format. + tags: + - Graphics + - Images +- title: PebbleKit JS Weather + repo: pebble-examples/pebblekit-js-weather + languages: + - c + - js + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/master/weather-screenshot~bw.png + description: | + This watchapp uses PebbleKit JS to fetch weather data from the + openweathermap.org and display it on the watch. It uses the location + provided by the phone to look up the nearest location, and sends temperature + data along with that location's name. A weather icon is also displayed. + featured: true +- title: Hello Timeline + repo: pebble-examples/hello-timeline + no_cloudpebble: true + languages: + - c + - js + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/hello-timeline/master/screenshots/screenshot.png + description: | + This example app shows how to use the Pebble timeline API with user tokens. + featured: true + tags: + - Timeline +- title: Timeline TV Tracker + repo: pebble-examples/timeline-tv-tracker + no_cloudpebble: true + languages: + - c + - js + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/timeline-tv-tracker/master/pebble/screenshots/screenshot1.png + description: | + This example demonstrates the usage of topics and shared pins in the Pebble + timeline API. + featured: true + tags: + - Timeline +- title: UI Patterns + repo: pebble-examples/ui-patterns + languages: + - c + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/ui-patterns/master/screenshots/dialog-choice.png + description: | + Example project showing implementations of recommended Pebble UI design + patterns. + featured: true + tags: + - Design + - Images +- title: Font Browser + repo: pebble-examples/app-font-browser + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/app-font-browser/master/app-font-browser-screenshot.png + description: | + This example show how to load and display all the Pebble built-in system + fonts. It also uses the Clicks API to allow the user to cycle through each + font in both directions. + tags: + - Fonts +- title: Tea Timer + repo: pebble-examples/feature-app-wakeup + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-app-wakeup/master/feature-app-wakeup-screenshot.png + description: | + This example shows how the Wakeup API works. + tags: + - Wakeup +- title: Timeline Push Pin + repo: pebble-examples/timeline-push-pin + no_cloudpebble: true + languages: + - c + - js + hardware_platforms: + - basalt + screenshot_platform: basalt + screenshot: https://raw.githubusercontent.com/pebble-examples/timeline-push-pin/master/screenshots/screenshot1.png + description: | + This is a Pebble SDK 3.0 example app that demonstrates how an app can use + PebbleKit JS to push a pin to itself through the public timeline API. It + includes a segment of JS code (marked 'timeline lib') that can be re-used in + any Pebble app that needs to push pins to its user. + featured: true + tags: + - Timeline +- title: Simple Analog + repo: pebble-examples/simple-analog + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/simple-analog/master/simple-analog-screenshot.png + description: | + Example analog watchface that uses hands instead of numbers. + featured: true + tags: + - Watchface + - Analog +- title: Classio Battery Connection + repo: pebble-examples/classio-battery-connection + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/classio-battery-connection/master/classio-battery-connection-screenshot.png + description: | + Example watchface showing the time, including seconds. This version also + displays the battery level and status of the Bluetooth connection. + tags: + - Watchface + - Battery Service + - Bluetooth Service +- title: Persist Counter + repo: pebble-examples/feature-persist-counter + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-persist-counter/master/feature-persist-counter-screenshot.png + description: | + This example shows how to use the Persistent Storage API. + tags: + - Persistent Storage + - ActionBarLayer +- title: Menu Layer + repo: pebble-examples/feature-menu-layer + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-menu-layer/master/feature-menu-layer-screenshot.png + description: | + This example shows how to use the MenuLayer. + tags: + - MenuLayer +- title: Transparent Images + repo: pebble-examples/feature-image-transparent + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-image-transparent/master/feature-image-transparent-screenshot.png + description: | + This example demonstrates how to display an image with black, white and + transparent sections. + tags: + - Images +- title: Frame Buffer + repo: pebble-examples/feature-frame-buffer + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-frame-buffer/master/feature-frame-buffer-screenshot.png + description: | + This example shows how to capture and manipulate the frame buffer. + tags: + - Graphics + - Frame Buffer +- title: Custom Font + repo: pebble-examples/feature-custom-font + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-custom-font/master/feature-custom-font-screenshot.png + description: | + This example shows how to use a custom font. + tags: + - Fonts +- title: Background Counter + repo: pebble-examples/feature-background-counter + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-background-counter/master/feature-background-counter-screenshot.png + description: | + This example shows how to use the Worker API (background worker). + tags: + - Background Worker +- title: Accel Discs + repo: pebble-examples/feature-accel-discs + languages: + - c + hardware_platforms: + - aplite + - basalt + screenshot_platform: aplite + screenshot: https://raw.githubusercontent.com/pebble-examples/feature-accel-discs/master/feature-accel-discs-screenshot.png + description: | + This example shows how to use the accelerometer. + tags: + - Accelerometer Service + - Graphics diff --git a/devsite/source/_data/faqs.json b/devsite/source/_data/faqs.json new file mode 100644 index 00000000..c93ebf76 --- /dev/null +++ b/devsite/source/_data/faqs.json @@ -0,0 +1,102 @@ +[ + { + "title": "Pebble SDK", + "questions": [ + { + "question": "What are the Aplite, Basalt, and Chalk platforms?", + "answer": "These are the platform names we use to describe our different generations of watches. Aplite refers to the original Pebble and Pebble Steel. Basalt refers to Pebble Time. Chalk refers to Pebble Time Round." + }, + { + "question": "I cannot find the \"Developer Mode\" or \"Developer Connection\" toggle in the iOS or Android application", + "answer": "Please make sure you have updated your mobile applications to at least 2.0. On iOS, you first need to enable Developer Mode in the global _Settings_ application on the phone. On Android, this can be enabled from the navigation drawer." + }, + { + "question": "`pebble` tool error: 'RuntimeError: Freetype library not found'", + "answer": "Please install the freetype library on your computer: \n\n* Mac OS users can use the [homebrew](http://brew.sh/) packaging system and run `brew install freetype`.\n\n* Linux users should not have to do anything." + }, + { + "question": "How do I save the state between app invocations, for example, if I want to store user selections etc and persist it across app invocations?", + "answer": "Use the [Persistent Storage API](/guides/events-and-services/persistent-storage), or [PebbleKit JS](/guides/communication/using-pebblekit-js) `localStorage`." + }, + { + "question": "How do I calculate the total size of a dictionary?", + "answer": "If you have a pointer to a `DictionaryIterator`, you can easily calculate the total size of this dictionary:\n\n```c\nint size = (int)received->end - (int)received->dictionary;\n```" + }, + { + "question": "Can I get realtime accelerometer data?", + "answer": "Yes. See [this community blog post (external link)](http://ninedof.wordpress.com/2014/03/28/streaming-pebble-accelerometer-data/) for more information." + } + ] + }, + { + "title": "App Communication", + "questions": [ + { + "question": "App message error: 'The app rejected the update'", + "answer": "This is likely caused by a mismatch between your watch application UUID and the UUID you registered for on the mobile application. Please make sure you use the same UUID on both ends." + } + ] + }, + { + "title": "Memory Management", + "questions": [ + { + "question": "How apps are loaded for execution? For example, are apps always in RAM, or loaded in RAM one at a time when the user starts that specific app?", + "answer": "Apps (code and static variables) are loaded entirely in RAM when they are started. Your resources are only loaded in RAM when you call `resource_load()` or one of the function that calls it (like `fonts_load_custom_font()` or `gbitmap_create_with_resource_id()`). Your app, and all memory it has allocated, will be removed from RAM when it is terminated." + }, + { + "question": "What is the maximum code segment size? The data segment size?", + "answer": "All segments share the same memory space. There is no limit on the segment sizes but the global limit of memory available to apps is currently around 24k bytes." + }, + { + "question": "Is there a separate Read-Only data segment vs a Read-Write data?", + "answer": "No. For apps, the data segment is all Read-Write." + }, + { + "question": "Can one app invoke another app to get around app size limits?", + "answer": "You can use the PebbleKit iOS or Android to launch any application on Pebble." + } + ] + }, + { + "title": "App Publishing", + "questions": [ + { + "question": "My companion app got rejected", + "answer": "It probably hasn't been accepted yet. You will get a confirmation email from pebbledev after it has been submitted to Apple. After this, it will be 2-3 days for it to be officially in the MFi program." + } + ], + "hidden-questions": [ + { + "question": "When is JS bundling?", + "answer": "Check tweets from @pebbledev and the bundling [forum thread](https://forums.getpebble.com/discussion/12451/pebble-js-bundling-updates)" + }, + { + "question": "Why does this app say 'coming soon'?", + "answer": "See 'When is JS bundling?'" + }, + { + "question": "I don't want this app to show 'coming soon'", + "answer": "Publish the release, but don't press 'Make Public'. The JS will still be included in the next bundling. Once it has been bundled, make it public." + } + ] + }, + { + "title": "Hardware Issues", + "questions": [ + { + "question": "My screen is artifacting/flashing/ customer support issues", + "answer": "Contact [support@getpebble.com](mailto:support@getpebble.com) for RMA issues." + } + ] + }, + { + "title": "Pebble SDK 1.x Migration", + "questions": [ + { + "question": "In my old app `PBL_APP_INFO` I was setting the flags `APP_INFO_STANDARD_APP | APP_INFO_VISIBILITY_SHOWN_ON_COMMUNICATION`. How do I do that with the new `appinfo.json` file?", + "answer": "You can add another flag to `package.json` under the `\"watchapp\"` block:\n\n```js\n\"watchapp\": {\n \"watchface\": false\n \"onlyShownOnCommunication\": true}\n```" + } + ] + } +] \ No newline at end of file diff --git a/devsite/source/_data/features.yaml b/devsite/source/_data/features.yaml new file mode 100644 index 00000000..659a7179 --- /dev/null +++ b/devsite/source/_data/features.yaml @@ -0,0 +1,21 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- title: SDK 4.0 Now Available! + url: /sdk4 + background_image: /images/landing-page/devblog.jpg + button_text: Find out more + button_fg: white + button_bg: red + duration: 4000 diff --git a/devsite/source/_data/guide-categories.yaml b/devsite/source/_data/guide-categories.yaml new file mode 100644 index 00000000..8fe2ed40 --- /dev/null +++ b/devsite/source/_data/guide-categories.yaml @@ -0,0 +1,22 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- id: beautiful-apps + title: Create beautiful and engaging apps +- id: interactive-apps + title: Create apps that interact with the world +- id: building-apps + title: Build, refine, and debug apps +- id: publishing-apps + title: Publish and share apps diff --git a/devsite/source/_data/guides.yaml b/devsite/source/_data/guides.yaml new file mode 100644 index 00000000..d2cc1a05 --- /dev/null +++ b/devsite/source/_data/guides.yaml @@ -0,0 +1,106 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is the listing of the various areas of the Guides. +# It is used to build the primary navigation and the Guides. + +app-resources: + title: App Resources + description: Information on the many kinds of files that can be used inside Pebble apps. + category: beautiful-apps + sort_by: title + +appstore-publishing: + title: Appstore Publishing + description: How to get your app ready for going live in the Pebble appstore. + category: publishing-apps + sort_by: order + +best-practices: + title: Best Practices + description: Information to help optimize apps and ensure a good user experience. + category: building-apps + sort_by: title + +communication: + title: Communication + description: How to talk to the phone via PebbleKit with JavaScript and on Android or iOS. + category: interactive-apps + sort_by: title + +debugging: + title: Debugging + description: How to find and fix common compilation and runtime problems in apps. + category: building-apps + sort_by: title + +design-and-interaction: + title: Design and Interaction + description: Information on creating the best app user experiences and layout designs. + category: beautiful-apps + sort_by: order + +events-and-services: + title: Events and Services + description: How to get data from the onboard sensors and services including the accelerometer, compass, and microphone. + category: interactive-apps + sort_by: title + +graphics-and-animations: + title: Graphics and Animations + description: Information on using animations and drawing shapes, text, and images, as well as more advanced techniques. + category: beautiful-apps + sort_by: title + +migration: + title: Migrating Older Apps + description: Information useful for updating old code from older apps to use newer SDKs. + category: publishing-apps + sort_by: title + +pebble-packages: + title: Pebble Packages + description: How to create and use Pebble Packages + category: building-apps + sort_by: title + +pebble-timeline: + title: Pebble Timeline + description: How to use Pebble timeline to bring timely information to app users outside the app itself via web services. + category: interactive-apps + sort_by: title + +rocky-js: + title: Rocky.js + description: Information on creating apps with JavaScript using Rocky.js + category: interactive-apps + sort_by: title + +smartstraps: + title: Smartstraps + description: Information on creating and talking to smartstraps. + category: interactive-apps + sort_by: title + +tools-and-resources: + title: Tools and Resources + description: Information on all the software tools available when writing Pebble apps, as well as other resources. + category: building-apps + sort_by: title + +user-interfaces: + title: User Interfaces + description: How to build effective user interfaces. Includes information on events, persistent storage, background worker, wakeups and app configuration. + category: beautiful-apps + sort_by: title diff --git a/devsite/source/_data/js.yaml b/devsite/source/_data/js.yaml new file mode 100644 index 00000000..ae2a159e --- /dev/null +++ b/devsite/source/_data/js.yaml @@ -0,0 +1,53 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +libs: + - name: jquery + path: /js/libs/jquery-1.11.2.js + - name: moment + path: /js/libs/moment.js + - name: responsive + path: /js/libs/responsive.js + - name: slick + path: /js/libs/slick.js + - name: jquery.fitvids + path: /js/libs/jquery.fitvids.js + - name: jquery.scrollTo + path: /js/libs/jquery.scrollTo.js + - name: jquery.vide + path: /js/libs/jquery.vide.js + - name: handlebars.runtime-v2.0.0 + path: /js/libs/handlebars.runtime-v2.0.0.js + - name: utils + path: /js/libs/utils.js + - name: tether + path: /js/libs/tether.js + - name: select + path: /js/libs/select.js + - name: algoliasearch + path: /js/libs/algoliasearch.js + - name: query-string + path: /js/libs/query-string.js + - name: mmenu + path: /js/libs/jquery.mmenu.oncanvas.js + - name: mmenu-offcanvas + path: /js/libs/jquery.mmenu.offcanvas.js + - name: mmenu-footer + path: /js/libs/jquery.mmenu.footer.js + - name: cookies + path: /js/libs/cookies.js + - name: heir + path: /js/libs/heir.js + - name: EventEmitter + path: /js/libs/EventEmitter.js diff --git a/devsite/source/_data/jsdocs-pkjs.json b/devsite/source/_data/jsdocs-pkjs.json new file mode 100644 index 00000000..36a141ca --- /dev/null +++ b/devsite/source/_data/jsdocs-pkjs.json @@ -0,0 +1,12192 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The Pebble namespace is where all of the Pebble specific methods and \nproperties exist. This class contains methods belonging to PebbleKit JS and \nallows bi-directional communication with a C or JavaScript watchapp, as well as managing \nthe user's timeline subscriptions, creating AppGlance slices and obtaining \ninformation about the currently connected watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 49, + "offset": 361 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 49, + "offset": 361 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 49, + "offset": 361 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "Pebble" + }, + { + "title": "desc", + "description": "The Pebble namespace is where all of the Pebble specific methods and \nproperties exist. This class contains methods belonging to PebbleKit JS and \nallows bi-directional communication with a C or JavaScript watchapp, as well as managing \nthe user's timeline subscriptions, creating AppGlance slices and obtaining \ninformation about the currently connected watch.", + "lineNumber": 3 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 24 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "namespace", + "name": "Pebble", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Adds a listener for PebbleKit JS events, such as when an ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 58, + "offset": 57 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 1, + "column": 58, + "offset": 57 + }, + "end": { + "line": 1, + "column": 72, + "offset": 71 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is\n received or the configuration view is opened or closed.", + "position": { + "start": { + "line": 1, + "column": 72, + "offset": 71 + }, + "end": { + "line": 2, + "column": 60, + "offset": 134 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 134 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Event Type Options", + "position": { + "start": { + "line": 4, + "column": 8, + "offset": 143 + }, + "end": { + "line": 4, + "column": 26, + "offset": 161 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 136 + }, + "end": { + "line": 4, + "column": 26, + "offset": 161 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 163 + }, + "end": { + "line": 6, + "column": 19, + "offset": 181 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 163 + }, + "end": { + "line": 6, + "column": 19, + "offset": 181 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ready", + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 187 + }, + "end": { + "line": 8, + "column": 12, + "offset": 194 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The watchapp has been launched and the PebbleKit JS component \nis now ready to receive events.", + "position": { + "start": { + "line": 8, + "column": 12, + "offset": 194 + }, + "end": { + "line": 9, + "column": 36, + "offset": 295 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 187 + }, + "end": { + "line": 9, + "column": 36, + "offset": 295 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 183 + }, + "end": { + "line": 9, + "column": 36, + "offset": 295 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "appmessage", + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 300 + }, + "end": { + "line": 10, + "column": 17, + "offset": 312 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The watch sent an ", + "position": { + "start": { + "line": 10, + "column": 17, + "offset": 312 + }, + "end": { + "line": 10, + "column": 38, + "offset": 333 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 10, + "column": 38, + "offset": 333 + }, + "end": { + "line": 10, + "column": 52, + "offset": 347 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " to PebbleKit JS. The \nAppMessage ", + "position": { + "start": { + "line": 10, + "column": 52, + "offset": 347 + }, + "end": { + "line": 11, + "column": 16, + "offset": 385 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "inlineCode", + "value": "Dictionary", + "position": { + "start": { + "line": 11, + "column": 16, + "offset": 385 + }, + "end": { + "line": 11, + "column": 30, + "offset": 399 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is contained in the payload property (i.e: \n", + "position": { + "start": { + "line": 11, + "column": 30, + "offset": 399 + }, + "end": { + "line": 12, + "column": 5, + "offset": 448 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "inlineCode", + "value": "event.payload", + "position": { + "start": { + "line": 12, + "column": 5, + "offset": 448 + }, + "end": { + "line": 12, + "column": 20, + "offset": 463 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "). The payload consists of key-value pairs, where the keys \nare strings containing integers (e.g: \"0\"), or aliases for keys defined \nin package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings \nor byte arrays (arrays of characters). This event is not available to \n", + "position": { + "start": { + "line": 12, + "column": 20, + "offset": 463 + }, + "end": { + "line": 16, + "column": 5, + "offset": 757 + }, + "indent": [ + 5, + 5, + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 757 + }, + "end": { + "line": 16, + "column": 36, + "offset": 788 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications, and attempting to register it will throw an exception.", + "position": { + "start": { + "line": 16, + "column": 36, + "offset": 788 + }, + "end": { + "line": 16, + "column": 105, + "offset": 857 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 300 + }, + "end": { + "line": 16, + "column": 105, + "offset": 857 + }, + "indent": [ + 5, + 5, + 5, + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 296 + }, + "end": { + "line": 16, + "column": 105, + "offset": 857 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "showConfiguration", + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 862 + }, + "end": { + "line": 17, + "column": 24, + "offset": 881 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The user has requested the app's configuration \nwebview to be displayed. This can occur either upon the app's initial \ninstall or when the user taps 'Settings' in the 'My Pebble' view within \nthe phone app.", + "position": { + "start": { + "line": 17, + "column": 24, + "offset": 881 + }, + "end": { + "line": 20, + "column": 19, + "offset": 1102 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 862 + }, + "end": { + "line": 20, + "column": 19, + "offset": 1102 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1, + "offset": 858 + }, + "end": { + "line": 20, + "column": 19, + "offset": 1102 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "webviewclosed", + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 1107 + }, + "end": { + "line": 21, + "column": 20, + "offset": 1122 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The configuration webview was closed by the user. If \nthe webview had a response, it will be contained in the response property \n(i.e: ", + "position": { + "start": { + "line": 21, + "column": 20, + "offset": 1122 + }, + "end": { + "line": 23, + "column": 11, + "offset": 1268 + }, + "indent": [ + 5, + 5 + ] + } + }, + { + "type": "inlineCode", + "value": "event.response", + "position": { + "start": { + "line": 23, + "column": 11, + "offset": 1268 + }, + "end": { + "line": 23, + "column": 27, + "offset": 1284 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "). This response can be used to feed back user \npreferences to the watchapp.", + "position": { + "start": { + "line": 23, + "column": 27, + "offset": 1284 + }, + "end": { + "line": 24, + "column": 33, + "offset": 1364 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 1107 + }, + "end": { + "line": 24, + "column": 33, + "offset": 1364 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1, + "offset": 1103 + }, + "end": { + "line": 24, + "column": 33, + "offset": 1364 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "message", + "position": { + "start": { + "line": 25, + "column": 5, + "offset": 1369 + }, + "end": { + "line": 25, + "column": 14, + "offset": 1378 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 25, + "column": 14, + "offset": 1378 + }, + "end": { + "line": 25, + "column": 27, + "offset": 1391 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#PostMessageCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "PostMessageCallback" + } + ], + "position": { + "start": { + "line": 25, + "column": 27, + "offset": 1391 + }, + "end": { + "line": 25, + "column": 75, + "offset": 1439 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \nas the callback. The message event is emitted every time PebbleKit JS \nreceives a ", + "position": { + "start": { + "line": 25, + "column": 75, + "offset": 1439 + }, + "end": { + "line": 27, + "column": 16, + "offset": 1531 + }, + "indent": [ + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "#postMessage", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "postMessage" + } + ], + "position": { + "start": { + "line": 27, + "column": 16, + "offset": 1531 + }, + "end": { + "line": 27, + "column": 48, + "offset": 1563 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " from the ", + "position": { + "start": { + "line": 27, + "column": 48, + "offset": 1563 + }, + "end": { + "line": 27, + "column": 58, + "offset": 1573 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 27, + "column": 58, + "offset": 1573 + }, + "end": { + "line": 27, + "column": 89, + "offset": 1604 + }, + "indent": [] + } + }, + { + "type": "break", + "position": { + "start": { + "line": 27, + "column": 89, + "offset": 1604 + }, + "end": { + "line": 28, + "column": 5, + "offset": 1611 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "text", + "value": " application. The payload contains a simple JavaScript object. (i.e. ", + "position": { + "start": { + "line": 28, + "column": 5, + "offset": 1611 + }, + "end": { + "line": 28, + "column": 74, + "offset": 1680 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "event.data", + "position": { + "start": { + "line": 28, + "column": 74, + "offset": 1680 + }, + "end": { + "line": 28, + "column": 86, + "offset": 1692 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "). \nThis event type can only be used with ", + "position": { + "start": { + "line": 28, + "column": 86, + "offset": 1692 + }, + "end": { + "line": 29, + "column": 43, + "offset": 1738 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 29, + "column": 43, + "offset": 1738 + }, + "end": { + "line": 29, + "column": 74, + "offset": 1769 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 29, + "column": 74, + "offset": 1769 + }, + "end": { + "line": 29, + "column": 88, + "offset": 1783 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 5, + "offset": 1369 + }, + "end": { + "line": 29, + "column": 88, + "offset": 1783 + }, + "indent": [ + 5, + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 1, + "offset": 1365 + }, + "end": { + "line": 29, + "column": 88, + "offset": 1783 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessageconnected", + "position": { + "start": { + "line": 30, + "column": 5, + "offset": 1788 + }, + "end": { + "line": 30, + "column": 27, + "offset": 1810 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 30, + "column": 27, + "offset": 1810 + }, + "end": { + "line": 30, + "column": 40, + "offset": 1823 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#PostMessageConnectedCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "PostMessageConnectedCallback" + } + ], + "position": { + "start": { + "line": 30, + "column": 40, + "offset": 1823 + }, + "end": { + "line": 30, + "column": 106, + "offset": 1889 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \nas the callback. The event may be emitted immediately upon subscription, \nif the subsystem is already connected. It is also emitted when connectivity is established. \nThis event type can only be used with ", + "position": { + "start": { + "line": 30, + "column": 106, + "offset": 1889 + }, + "end": { + "line": 33, + "column": 43, + "offset": 2108 + }, + "indent": [ + 5, + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 33, + "column": 43, + "offset": 2108 + }, + "end": { + "line": 33, + "column": 74, + "offset": 2139 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 33, + "column": 74, + "offset": 2139 + }, + "end": { + "line": 33, + "column": 88, + "offset": 2153 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 30, + "column": 5, + "offset": 1788 + }, + "end": { + "line": 33, + "column": 88, + "offset": 2153 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 30, + "column": 1, + "offset": 1784 + }, + "end": { + "line": 33, + "column": 88, + "offset": 2153 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessagedisconnected", + "position": { + "start": { + "line": 34, + "column": 5, + "offset": 2158 + }, + "end": { + "line": 34, + "column": 30, + "offset": 2183 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 34, + "column": 30, + "offset": 2183 + }, + "end": { + "line": 34, + "column": 43, + "offset": 2196 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#PostMessageDisconnectedCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "PostMessageDisconnectedCallback" + } + ], + "position": { + "start": { + "line": 34, + "column": 43, + "offset": 2196 + }, + "end": { + "line": 34, + "column": 115, + "offset": 2268 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \nas the callback. The event may be emitted immediately upon subscription, \nif the subsystem is already disconnected. It is also emitted when connectivity is lost. \nThis event type can only be used with ", + "position": { + "start": { + "line": 34, + "column": 115, + "offset": 2268 + }, + "end": { + "line": 37, + "column": 43, + "offset": 2483 + }, + "indent": [ + 5, + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 37, + "column": 43, + "offset": 2483 + }, + "end": { + "line": 37, + "column": 74, + "offset": 2514 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 37, + "column": 74, + "offset": 2514 + }, + "end": { + "line": 37, + "column": 88, + "offset": 2528 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 34, + "column": 5, + "offset": 2158 + }, + "end": { + "line": 37, + "column": 88, + "offset": 2528 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 34, + "column": 1, + "offset": 2154 + }, + "end": { + "line": 37, + "column": 88, + "offset": 2528 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 38, + "column": 5, + "offset": 2533 + }, + "end": { + "line": 38, + "column": 23, + "offset": 2551 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 38, + "column": 23, + "offset": 2551 + }, + "end": { + "line": 38, + "column": 36, + "offset": 2564 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#PostMessageErrorCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "PostMessageErrorCallback" + } + ], + "position": { + "start": { + "line": 38, + "column": 36, + "offset": 2564 + }, + "end": { + "line": 38, + "column": 94, + "offset": 2622 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \nas the callback. The event is emitted when a transmission error occurrs. \nYour message has not been delivered. The type of error is not provided. \nThis event type can only be used with ", + "position": { + "start": { + "line": 38, + "column": 94, + "offset": 2622 + }, + "end": { + "line": 41, + "column": 43, + "offset": 2821 + }, + "indent": [ + 5, + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 41, + "column": 43, + "offset": 2821 + }, + "end": { + "line": 41, + "column": 74, + "offset": 2852 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 41, + "column": 74, + "offset": 2852 + }, + "end": { + "line": 41, + "column": 88, + "offset": 2866 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 38, + "column": 5, + "offset": 2533 + }, + "end": { + "line": 41, + "column": 88, + "offset": 2866 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 38, + "column": 1, + "offset": 2529 + }, + "end": { + "line": 41, + "column": 88, + "offset": 2866 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 183 + }, + "end": { + "line": 41, + "column": 88, + "offset": 2866 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 41, + "column": 88, + "offset": 2866 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Adds a listener for PebbleKit JS events, such as when an ``AppMessage`` is\n received or the configuration view is opened or closed.\n\n #### Event Type Options\n\n Possible values:\n\n * `ready` - The watchapp has been launched and the PebbleKit JS component \n is now ready to receive events.\n * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The \n AppMessage ``Dictionary`` is contained in the payload property (i.e: \n `event.payload`). The payload consists of key-value pairs, where the keys \n are strings containing integers (e.g: \"0\"), or aliases for keys defined \n in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings \n or byte arrays (arrays of characters). This event is not available to \n {@link /docs/rockyjs/ Rocky.js} applications, and attempting to register it will throw an exception.\n * `showConfiguration` - The user has requested the app's configuration \n webview to be displayed. This can occur either upon the app's initial \n install or when the user taps 'Settings' in the 'My Pebble' view within \n the phone app.\n * `webviewclosed` - The configuration webview was closed by the user. If \n the webview had a response, it will be contained in the response property \n (i.e: `event.response`). This response can be used to feed back user \n preferences to the watchapp.\n * `message` - Provide a {@link #PostMessageCallback PostMessageCallback} \n as the callback. The message event is emitted every time PebbleKit JS \n receives a {@link #postMessage postMessage} from the {@link /docs/rockyjs/ Rocky.js} \n application. The payload contains a simple JavaScript object. (i.e. `event.data`). \n This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.\n * `postmessageconnected` - Provide a {@link #PostMessageConnectedCallback PostMessageConnectedCallback} \n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already connected. It is also emitted when connectivity is established. \n This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.\n * `postmessagedisconnected` - Provide a {@link #PostMessageDisconnectedCallback PostMessageDisconnectedCallback} \n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already disconnected. It is also emitted when connectivity is lost. \n This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.\n * `postmessageerror` - Provide a {@link #PostMessageErrorCallback PostMessageErrorCallback} \n as the callback. The event is emitted when a transmission error occurrs. \n Your message has not been delivered. The type of error is not provided. \n This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event, from the list described above.", + "lineNumber": 43, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The developer defined {@link #EventCallback EventCallback} \n to receive any events of the type specified that occur.", + "lineNumber": 44, + "type": { + "type": "NameExpression", + "name": "EventCallback" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 59, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 60, + "column": 0 + }, + "end": { + "line": 60, + "column": 55 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 43, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event, from the list described above.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 44, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#EventCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "EventCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 23, + "offset": 22 + }, + "end": { + "line": 1, + "column": 59, + "offset": 58 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n to receive any events of the type specified that occur.", + "position": { + "start": { + "line": 1, + "column": 59, + "offset": 58 + }, + "end": { + "line": 2, + "column": 58, + "offset": 117 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 117 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 117 + } + } + }, + "type": { + "type": "NameExpression", + "name": "EventCallback" + } + } + ], + "name": "addEventListener", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "addEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.addEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Attaches an event handler to the specified events. Synonymous with \n", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 1, + "offset": 68 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 2, + "offset": 69 + }, + "end": { + "line": 2, + "column": 27, + "offset": 94 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 68 + }, + "end": { + "line": 2, + "column": 47, + "offset": 114 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ". Only applicable to \n", + "position": { + "start": { + "line": 2, + "column": 47, + "offset": 114 + }, + "end": { + "line": 3, + "column": 1, + "offset": 136 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 136 + }, + "end": { + "line": 3, + "column": 32, + "offset": 167 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 3, + "column": 32, + "offset": 167 + }, + "end": { + "line": 3, + "column": 46, + "offset": 181 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 46, + "offset": 181 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "Pebble.on(type, callback);", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 183 + }, + "end": { + "line": 5, + "column": 29, + "offset": 211 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 183 + }, + "end": { + "line": 5, + "column": 29, + "offset": 211 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 29, + "offset": 211 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Attaches an event handler to the specified events. Synonymous with \n[Pebble.addEventListener()](#addEventListener). Only applicable to \n{@link /docs/rockyjs/ Rocky.js} applications.\n\n`Pebble.on(type, callback);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event, from the list described above.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The developer defined {@link #EventCallback EventCallback}\n to receive any events of the type specified that occur.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "EventCallback" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 62, + "column": 0 + }, + "end": { + "line": 72, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 73, + "column": 0 + }, + "end": { + "line": 73, + "column": 41 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event, from the list described above.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#EventCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "EventCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 23, + "offset": 22 + }, + "end": { + "line": 1, + "column": 59, + "offset": 58 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n to receive any events of the type specified that occur.", + "position": { + "start": { + "line": 1, + "column": 59, + "offset": 58 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + } + } + }, + "type": { + "type": "NameExpression", + "name": "EventCallback" + } + } + ], + "name": "on", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "on", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.on" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Remove an existing event listener previously registered with \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 64 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 65 + }, + "end": { + "line": 2, + "column": 29, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 64 + }, + "end": { + "line": 2, + "column": 49, + "offset": 110 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 2, + "column": 49, + "offset": 110 + }, + "end": { + "line": 2, + "column": 53, + "offset": 114 + }, + "indent": [] + } + }, + { + "type": "link", + "title": null, + "url": "#on", + "children": [ + { + "type": "text", + "value": "Pebble.on()", + "position": { + "start": { + "line": 2, + "column": 54, + "offset": 115 + }, + "end": { + "line": 2, + "column": 65, + "offset": 126 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 53, + "offset": 114 + }, + "end": { + "line": 2, + "column": 71, + "offset": 132 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 71, + "offset": 132 + }, + "end": { + "line": 2, + "column": 72, + "offset": 133 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 72, + "offset": 133 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 72, + "offset": 133 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Remove an existing event listener previously registered with \n [Pebble.addEventListener()](#addEventListener) or [Pebble.on()](#on).", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event listener to be removed. See \n [Pebble.addEventListener()](#addEventListener) for a list of available event types.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The existing developer-defined function that was \n previously registered.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 75, + "column": 0 + }, + "end": { + "line": 83, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 84, + "column": 0 + }, + "end": { + "line": 84, + "column": 58 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event listener to be removed. See \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 53 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 54 + }, + "end": { + "line": 2, + "column": 29, + "offset": 79 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 53 + }, + "end": { + "line": 2, + "column": 49, + "offset": 99 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for a list of available event types.", + "position": { + "start": { + "line": 2, + "column": 49, + "offset": 99 + }, + "end": { + "line": 2, + "column": 86, + "offset": 136 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 86, + "offset": 136 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 86, + "offset": 136 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The existing developer-defined function that was \n previously registered.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "removeEventListener", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "removeEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.removeEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Remove an existing event handler from the specified events. Synonymous \n with ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 84 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#removeEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.removeEventListener()", + "position": { + "start": { + "line": 2, + "column": 14, + "offset": 85 + }, + "end": { + "line": 2, + "column": 42, + "offset": 113 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 13, + "offset": 84 + }, + "end": { + "line": 2, + "column": 65, + "offset": 136 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ". Only applicable to \n ", + "position": { + "start": { + "line": 2, + "column": 65, + "offset": 136 + }, + "end": { + "line": 3, + "column": 7, + "offset": 164 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 3, + "column": 7, + "offset": 164 + }, + "end": { + "line": 3, + "column": 38, + "offset": 195 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications.", + "position": { + "start": { + "line": 3, + "column": 38, + "offset": 195 + }, + "end": { + "line": 3, + "column": 52, + "offset": 209 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 52, + "offset": 209 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "Pebble.off(type, callback);", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 211 + }, + "end": { + "line": 5, + "column": 30, + "offset": 240 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 211 + }, + "end": { + "line": 5, + "column": 30, + "offset": 240 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 30, + "offset": 240 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Remove an existing event handler from the specified events. Synonymous \n with [Pebble.removeEventListener()](#removeEventListener). Only applicable to \n {@link /docs/rockyjs/ Rocky.js} applications.\n\n`Pebble.off(type, callback);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event listener to be removed. See \n [Pebble.addEventListener()](#addEventListener) for a list of available types.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The existing developer-defined function that was \n previously registered.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 86, + "column": 0 + }, + "end": { + "line": 97, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 98, + "column": 0 + }, + "end": { + "line": 98, + "column": 42 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event listener to be removed. See \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 53 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 54 + }, + "end": { + "line": 2, + "column": 29, + "offset": 79 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 53 + }, + "end": { + "line": 2, + "column": 49, + "offset": 99 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for a list of available types.", + "position": { + "start": { + "line": 2, + "column": 49, + "offset": 99 + }, + "end": { + "line": 2, + "column": 80, + "offset": 130 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 80, + "offset": 130 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 80, + "offset": 130 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The existing developer-defined function that was \n previously registered.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "off", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "off", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.off" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Show a simple modal notification on the connected watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Show a simple modal notification on the connected watch.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The title of the notification", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "title" + }, + { + "title": "param", + "description": "The main content of the notification", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "body" + } + ], + "loc": { + "start": { + "line": 100, + "column": 0 + }, + "end": { + "line": 105, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 106, + "column": 0 + }, + "end": { + "line": 106, + "column": 66 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "title", + "lineNumber": 3, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The title of the notification", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "body", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The main content of the notification", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "showSimpleNotificationOnPebble", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "showSimpleNotificationOnPebble", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.showSimpleNotificationOnPebble" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Send an AppMessage to the app running on the watch. Messages should be \n in the form of JSON objects containing key-value pairs. See \n Pebble.sendAppMessage() for valid key and value data types. \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 5, + "offset": 206 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };", + "position": { + "start": { + "line": 4, + "column": 5, + "offset": 206 + }, + "end": { + "line": 4, + "column": 72, + "offset": 273 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n Please note that ", + "position": { + "start": { + "line": 4, + "column": 72, + "offset": 273 + }, + "end": { + "line": 5, + "column": 22, + "offset": 296 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "sendAppMessage", + "position": { + "start": { + "line": 5, + "column": 22, + "offset": 296 + }, + "end": { + "line": 5, + "column": 38, + "offset": 312 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is ", + "position": { + "start": { + "line": 5, + "column": 38, + "offset": 312 + }, + "end": { + "line": 5, + "column": 42, + "offset": 316 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "undefined", + "position": { + "start": { + "line": 5, + "column": 42, + "offset": 316 + }, + "end": { + "line": 5, + "column": 53, + "offset": 327 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " in \n ", + "position": { + "start": { + "line": 5, + "column": 53, + "offset": 327 + }, + "end": { + "line": 6, + "column": 5, + "offset": 336 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 6, + "column": 5, + "offset": 336 + }, + "end": { + "line": 6, + "column": 36, + "offset": 367 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " applications, see ", + "position": { + "start": { + "line": 6, + "column": 36, + "offset": 367 + }, + "end": { + "line": 6, + "column": 55, + "offset": 386 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#postMessage", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "postMessage" + } + ], + "position": { + "start": { + "line": 6, + "column": 55, + "offset": 386 + }, + "end": { + "line": 6, + "column": 87, + "offset": 418 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " instead.", + "position": { + "start": { + "line": 6, + "column": 87, + "offset": 418 + }, + "end": { + "line": 6, + "column": 96, + "offset": 427 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 96, + "offset": 427 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 96, + "offset": 427 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Send an AppMessage to the app running on the watch. Messages should be \n in the form of JSON objects containing key-value pairs. See \n Pebble.sendAppMessage() for valid key and value data types. \n `Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };` \n Please note that `sendAppMessage` is `undefined` in \n {@link /docs/rockyjs/ Rocky.js} applications, see {@link #postMessage postMessage} instead.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "The transaction id for this message", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "A JSON object containing key-value pairs to send to \n the watch. Values in arrays that are greater then 255 will be mod 255 \n before sending.", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + }, + { + "title": "param", + "description": "A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback} \n callback to run if the watch acknowledges (ACK) this message.", + "lineNumber": 13, + "type": { + "type": "NameExpression", + "name": "AppMessageAckCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback} \n callback to run if the watch does NOT acknowledge (NACK) this message.", + "lineNumber": 15, + "type": { + "type": "NameExpression", + "name": "AppMessageOnFailure" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 108, + "column": 0 + }, + "end": { + "line": 125, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 126, + "column": 0 + }, + "end": { + "line": 126, + "column": 65 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The transaction id for this message", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "data", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A JSON object containing key-value pairs to send to \n the watch. Values in arrays that are greater then 255 will be mod 255 \n before sending.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 147 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 147 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 147 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + }, + { + "name": "onSuccess", + "lineNumber": 13, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppMessageAckCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppMessageAckCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 73, + "offset": 72 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n callback to run if the watch acknowledges (ACK) this message.", + "position": { + "start": { + "line": 1, + "column": 73, + "offset": 72 + }, + "end": { + "line": 2, + "column": 66, + "offset": 139 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 139 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 139 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppMessageAckCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 15, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppMessageNackCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppMessageNackCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n callback to run if the watch does NOT acknowledge (NACK) this message.", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 2, + "column": 75, + "offset": 150 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 150 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 150 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppMessageOnFailure" + } + } + ], + "name": "sendAppMessage", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "sendAppMessage", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.sendAppMessage" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sends a message to the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Rocky.js" + } + ], + "position": { + "start": { + "line": 1, + "column": 24, + "offset": 23 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " component. Please be aware \n that messages should be kept concise. Each message is queued, so \n ", + "position": { + "start": { + "line": 1, + "column": 55, + "offset": 54 + }, + "end": { + "line": 3, + "column": 4, + "offset": 155 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "postMessage()", + "position": { + "start": { + "line": 3, + "column": 4, + "offset": 155 + }, + "end": { + "line": 3, + "column": 19, + "offset": 170 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " can be called multiple times immediately. If there is a momentary loss of connectivity, queued \n messages may still be delivered, or automatically removed from the queue \n after a few seconds of failed connectivity. Any transmission failures, or \n out of memory errors will be raised via the ", + "position": { + "start": { + "line": 3, + "column": 19, + "offset": 170 + }, + "end": { + "line": 6, + "column": 48, + "offset": 469 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 6, + "column": 48, + "offset": 469 + }, + "end": { + "line": 6, + "column": 66, + "offset": 487 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " event.", + "position": { + "start": { + "line": 6, + "column": 66, + "offset": 487 + }, + "end": { + "line": 6, + "column": 73, + "offset": 494 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 73, + "offset": 494 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "Pebble.postMessage({temperature: 30, conditions: 'Sunny'});", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 496 + }, + "end": { + "line": 8, + "column": 62, + "offset": 557 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 496 + }, + "end": { + "line": 8, + "column": 62, + "offset": 557 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 8, + "column": 62, + "offset": 557 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Sends a message to the {@link /docs/rockyjs/ Rocky.js} component. Please be aware \n that messages should be kept concise. Each message is queued, so \n `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued \n messages may still be delivered, or automatically removed from the queue \n after a few seconds of failed connectivity. Any transmission failures, or \n out of memory errors will be raised via the `postmessageerror` event.\n\n`Pebble.postMessage({temperature: 30, conditions: 'Sunny'});`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "A {@link #PostMessageCallback PostMessageCallback} containing \n the data to deliver to the watch.\n This will be received in the `data` field of the `type` delivered to \n the `on('message', ...)` handler.", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + } + ], + "loc": { + "start": { + "line": 128, + "column": 0 + }, + "end": { + "line": 142, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 143, + "column": 0 + }, + "end": { + "line": 143, + "column": 40 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "data", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#PostMessageCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "PostMessageCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " containing \n the data to deliver to the watch.\n This will be received in the ", + "position": { + "start": { + "line": 1, + "column": 51, + "offset": 50 + }, + "end": { + "line": 3, + "column": 38, + "offset": 142 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 3, + "column": 38, + "offset": 142 + }, + "end": { + "line": 3, + "column": 44, + "offset": 148 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " field of the ", + "position": { + "start": { + "line": 3, + "column": 44, + "offset": 148 + }, + "end": { + "line": 3, + "column": 58, + "offset": 162 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 3, + "column": 58, + "offset": 162 + }, + "end": { + "line": 3, + "column": 64, + "offset": 168 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " delivered to \n the ", + "position": { + "start": { + "line": 3, + "column": 64, + "offset": 168 + }, + "end": { + "line": 4, + "column": 13, + "offset": 195 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "on('message', ...)", + "position": { + "start": { + "line": 4, + "column": 13, + "offset": 195 + }, + "end": { + "line": 4, + "column": 33, + "offset": 215 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " handler.", + "position": { + "start": { + "line": 4, + "column": 33, + "offset": 215 + }, + "end": { + "line": 4, + "column": 42, + "offset": 224 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 42, + "offset": 224 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 42, + "offset": 224 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "name": "postMessage", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "postMessage", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.postMessage" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Get the user's timeline token for this app. This is a string and is \n unique per user per app. Note: In order for timeline tokens to be \n available, the app must be submitted to the Pebble appstore, but does not \n need to be public. Read more in the \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 5, + "offset": 264 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "link", + "url": "/guides/pebble-timeline/timeline-js/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "timeline guides" + } + ], + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 264 + }, + "end": { + "line": 5, + "column": 65, + "offset": 324 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 5, + "column": 65, + "offset": 324 + }, + "end": { + "line": 5, + "column": 66, + "offset": 325 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 66, + "offset": 325 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 66, + "offset": 325 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Get the user's timeline token for this app. This is a string and is \n unique per user per app. Note: In order for timeline tokens to be \n available, the app must be submitted to the Pebble appstore, but does not \n need to be public. Read more in the \n {@link /guides/pebble-timeline/timeline-js/ timeline guides}.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback} \n callback to handle a successful attempt to get the timeline token.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "TimelineTokenCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a \n failed attempt to get the timeline token.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 145, + "column": 0 + }, + "end": { + "line": 156, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 157, + "column": 1 + }, + "end": { + "line": 157, + "column": 62 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "onSuccess", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#TimelineTokenCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "TimelineTokenCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 73, + "offset": 72 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n callback to handle a successful attempt to get the timeline token.", + "position": { + "start": { + "line": 1, + "column": 73, + "offset": 72 + }, + "end": { + "line": 2, + "column": 71, + "offset": 144 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 71, + "offset": 144 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 71, + "offset": 144 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TimelineTokenCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a \n failed attempt to get the timeline token.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 87 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "getTimelineToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getTimelineToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getTimelineToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Subscribe the user to a timeline topic for your app. This can be used \n to filter the different pins a user could receive according to their \n preferences, as well as maintain groups of users.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 198 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 198 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 198 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Subscribe the user to a timeline topic for your app. This can be used \n to filter the different pins a user could receive according to their \n preferences, as well as maintain groups of users.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The desired topic to be subscribed to. Users will \n receive all pins pushed to this topic.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "topic" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a \n successful subscription attempt.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a \n failed subscription attempt.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 159, + "column": 0 + }, + "end": { + "line": 170, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 171, + "column": 0 + }, + "end": { + "line": 171, + "column": 69 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "topic", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The desired topic to be subscribed to. Users will \n receive all pins pushed to this topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 93 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "onSuccess", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a \n successful subscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 78 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 78 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 78 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "onFailure", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a \n failed subscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 74 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineSubscribe", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineSubscribe", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineSubscribe" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Unsubscribe the user from a timeline topic for your app. Once \n unsubscribed, the user will no longer receive any pins pushed to this \n topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 148 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 148 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 148 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Unsubscribe the user from a timeline topic for your app. Once \n unsubscribed, the user will no longer receive any pins pushed to this \n topic.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The desired topic to be unsubscribed from.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "topic" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a \n successful unsubscription attempt.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a \n failed unsubscription attempt.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 173, + "column": 0 + }, + "end": { + "line": 183, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 184, + "column": 0 + }, + "end": { + "line": 184, + "column": 71 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "topic", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The desired topic to be unsubscribed from.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "onSuccess", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a \n successful unsubscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 80 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 80 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 80 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "onFailure", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a \n failed unsubscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 76 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 76 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 76 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineUnsubscribe", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineUnsubscribe", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineUnsubscribe" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Obtain a list of topics that the user is currently subscribed to. The \n length of the list should be checked to determine whether the user is \n subscribed to at least one topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 38, + "offset": 183 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 38, + "offset": 183 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 185 + }, + "end": { + "line": 5, + "column": 4, + "offset": 188 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Pebble.timelineSubscriptions(function(topics) { console.log(topics); }, function() { console.log('error'); } );", + "position": { + "start": { + "line": 5, + "column": 4, + "offset": 188 + }, + "end": { + "line": 5, + "column": 117, + "offset": 301 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 185 + }, + "end": { + "line": 5, + "column": 117, + "offset": 301 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 117, + "offset": 301 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Obtain a list of topics that the user is currently subscribed to. The \n length of the list should be checked to determine whether the user is \n subscribed to at least one topic.\n\n `Pebble.timelineSubscriptions(function(topics) { console.log(topics); }, function() { console.log('error'); } );`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The developer-defined function to process the \n retuned list of topic strings.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "TimelineTopicsCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "The developer-defined function to gracefully \n handle any errors in obtaining the user's subscriptions.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 186, + "column": 0 + }, + "end": { + "line": 197, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 198, + "column": 0 + }, + "end": { + "line": 198, + "column": 66 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "onSuccess", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined function to process the \n retuned list of topic strings.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 81 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 81 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 81 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TimelineTopicsCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined function to gracefully \n handle any errors in obtaining the user's subscriptions.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 106 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 106 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 106 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineSubscriptions", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineSubscriptions", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineSubscriptions" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Obtain an object containing information on the currently connected \n Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 23, + "offset": 90 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 23, + "offset": 90 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "code", + "lang": null, + "value": "**Note:** This function is only available when using the Pebble Time \nsmartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information} \nfor details on how to use this function.", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 92 + }, + "end": { + "line": 6, + "column": 45, + "offset": 329 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 45, + "offset": 329 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Obtain an object containing information on the currently connected \n Pebble smartwatch.\n\n **Note:** This function is only available when using the Pebble Time \n smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information} \n for details on how to use this function.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A {@link #WatchInfo WatchInfo} object detailing the \n currently connected Pebble watch.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "WatchInfo" + } + } + ], + "loc": { + "start": { + "line": 201, + "column": 0 + }, + "end": { + "line": 211, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 212, + "column": 0 + }, + "end": { + "line": 212, + "column": 43 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#WatchInfo", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "WatchInfo" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object detailing the \n currently connected Pebble watch.", + "position": { + "start": { + "line": 1, + "column": 31, + "offset": 30 + }, + "end": { + "line": 2, + "column": 38, + "offset": 90 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 90 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 90 + } + } + }, + "type": { + "type": "NameExpression", + "name": "WatchInfo" + } + } + ], + "name": "getActiveWatchInfo", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getActiveWatchInfo", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getActiveWatchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns a unique account token that is associated with the Pebble \n account of the current user.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 99 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 99 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "code", + "lang": null, + "value": "**Note:** The behavior of this changed slightly in SDK 3.0. Read the \n{@link /guides/migration/migration-guide-3/ Migration Guide} to learn the \ndetails and how to adapt older tokens.", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 101 + }, + "end": { + "line": 6, + "column": 43, + "offset": 296 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 43, + "offset": 296 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Returns a unique account token that is associated with the Pebble \n account of the current user.\n\n **Note:** The behavior of this changed slightly in SDK 3.0. Read the \n {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the \n details and how to adapt older tokens.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A string that is guaranteed to be identical across devices \n if the user owns several Pebble or several mobile devices. From the \n developer's perspective, the account token of a user is identical across \n platforms and across all the developer's watchapps. If the user is not \n logged in, this function will return an empty string ('').", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "loc": { + "start": { + "line": 214, + "column": 0 + }, + "end": { + "line": 227, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 229, + "column": 0 + }, + "end": { + "line": 229, + "column": 40 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A string that is guaranteed to be identical across devices \n if the user owns several Pebble or several mobile devices. From the \n developer's perspective, the account token of a user is identical across \n platforms and across all the developer's watchapps. If the user is not \n logged in, this function will return an empty string ('').", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 349 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 349 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 349 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "getAccountToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getAccountToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getAccountToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns a a unique token that can be used to identify a Pebble device.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Returns a a unique token that can be used to identify a Pebble device.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A string that is is guaranteed to be identical for each \n Pebble device for the same app across different mobile devices. The token \n is unique to your app and cannot be used to track Pebble devices across \n applications.", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "loc": { + "start": { + "line": 231, + "column": 0 + }, + "end": { + "line": 238, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 239, + "column": 0 + }, + "end": { + "line": 239, + "column": 38 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A string that is is guaranteed to be identical for each \n Pebble device for the same app across different mobile devices. The token \n is unique to your app and cannot be used to track Pebble devices across \n applications.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 230 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 230 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 230 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "getWatchToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getWatchToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getWatchToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Triggers a reload of the app glance which first clears any existing \n slices and then adds the provided slices.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Triggers a reload of the app glance which first clears any existing \n slices and then adds the provided slices.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "{@link #AppGlanceSlice AppGlanceSlice} \n JSON objects to add to the app glance.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "appGlanceSlices" + }, + { + "title": "param", + "description": "The developer-defined \n callback which is called if the reload operation succeeds.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadSuccessCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "The developer-defined \n callback which is called if the reload operation fails.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadFailureCallback" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 242, + "column": 0 + }, + "end": { + "line": 252, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 253, + "column": 0 + }, + "end": { + "line": 253, + "column": 77 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "appGlanceSlices", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n JSON objects to add to the app glance.", + "position": { + "start": { + "line": 1, + "column": 39, + "offset": 38 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + }, + { + "name": "onSuccess", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined \n callback which is called if the reload operation succeeds.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadSuccessCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined \n callback which is called if the reload operation fails.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadFailureCallback" + } + } + ], + "name": "appGlanceReload", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "appGlanceReload", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.appGlanceReload" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when the user's list of subscriptions is available for processing by the developer.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "TimelineTopicsCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when the user's list of subscriptions is available for processing by the developer.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "of topic strings that the user is subscribed to", + "lineNumber": 5, + "type": { + "type": "ArrayType", + "elements": [ + { + "type": "NameExpression", + "name": "String" + } + ] + }, + "name": "List" + } + ], + "loc": { + "start": { + "line": 328, + "column": 0 + }, + "end": { + "line": 334, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "TimelineTopicsCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "List", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "of topic strings that the user is subscribed to", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + } + } + }, + "type": { + "type": "ArrayType", + "elements": [ + { + "type": "NameExpression", + "name": "String" + } + ] + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "TimelineTopicsCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleTimelineTopicsCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "When an app is marked as configurable, the PebbleKit JS component must \n implement ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 12, + "offset": 83 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "Pebble.openURL()", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 83 + }, + "end": { + "line": 2, + "column": 30, + "offset": 101 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " in the ", + "position": { + "start": { + "line": 2, + "column": 30, + "offset": 101 + }, + "end": { + "line": 2, + "column": 38, + "offset": 109 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "showConfiguration", + "position": { + "start": { + "line": 2, + "column": 38, + "offset": 109 + }, + "end": { + "line": 2, + "column": 57, + "offset": 128 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " event handler. The \n Pebble mobile app will launch the supplied URL to allow the user to configure \n the watchapp or watchface. See the \n ", + "position": { + "start": { + "line": 2, + "column": 57, + "offset": 128 + }, + "end": { + "line": 5, + "column": 2, + "offset": 267 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "link", + "url": "/guides/user-interfaces/app-configuration-static/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "App Configuration guide" + } + ], + "position": { + "start": { + "line": 5, + "column": 2, + "offset": 267 + }, + "end": { + "line": 5, + "column": 83, + "offset": 348 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 5, + "column": 83, + "offset": 348 + }, + "end": { + "line": 5, + "column": 84, + "offset": 349 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 84, + "offset": 349 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 84, + "offset": 349 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "When an app is marked as configurable, the PebbleKit JS component must \n implement `Pebble.openURL()` in the `showConfiguration` event handler. The \n Pebble mobile app will launch the supplied URL to allow the user to configure \n the watchapp or watchface. See the \n {@link /guides/user-interfaces/app-configuration-static/ App Configuration guide}.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The URL of the static configuration page.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "url" + } + ], + "loc": { + "start": { + "line": 255, + "column": 0 + }, + "end": { + "line": 263, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "params": [ + { + "name": "url", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The URL of the static configuration page.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "openURL", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "openURL", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.openURL" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when AppGlanceReload has failed.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppGlanceReloadFailureCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when AppGlanceReload has failed.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An {@link #AppGlanceSlice AppGlanceSlice} object \n containing the app glance slices.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "AppGlanceSlices" + } + ], + "loc": { + "start": { + "line": 275, + "column": 1 + }, + "end": { + "line": 282, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceReloadFailureCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "AppGlanceSlices", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 4, + "offset": 3 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 4, + "offset": 3 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object \n containing the app glance slices.", + "position": { + "start": { + "line": 1, + "column": 42, + "offset": 41 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceReloadFailureCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceReloadFailureCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an AppMessage is acknowledged by the watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppMessageAckCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an AppMessage is acknowledged by the watch.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the callback data. This contains \n the `transactionId` which is the transaction ID of the message.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + } + ], + "loc": { + "start": { + "line": 284, + "column": 0 + }, + "end": { + "line": 291, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppMessageAckCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "data", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the callback data. This contains \n the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 7, + "offset": 61 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transactionId", + "position": { + "start": { + "line": 2, + "column": 7, + "offset": 61 + }, + "end": { + "line": 2, + "column": 22, + "offset": 76 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " which is the transaction ID of the message.", + "position": { + "start": { + "line": 2, + "column": 22, + "offset": 76 + }, + "end": { + "line": 2, + "column": 66, + "offset": 120 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 120 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 120 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppMessageAckCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppMessageAckCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an AppMessage is not acknowledged by the watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppMessageNackCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an AppMessage is not acknowledged by the watch.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the callback data. This contains \n the `transactionId` which is the transaction ID of the message", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + }, + { + "title": "param", + "description": "The error message", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "error" + } + ], + "loc": { + "start": { + "line": 293, + "column": 0 + }, + "end": { + "line": 301, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppMessageNackCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "data", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the callback data. This contains \n the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 7, + "offset": 61 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transactionId", + "position": { + "start": { + "line": 2, + "column": 7, + "offset": 61 + }, + "end": { + "line": 2, + "column": 22, + "offset": 76 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " which is the transaction ID of the message", + "position": { + "start": { + "line": 2, + "column": 22, + "offset": 76 + }, + "end": { + "line": 2, + "column": 65, + "offset": 119 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 119 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 119 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + }, + { + "name": "error", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The error message", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppMessageNackCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppMessageNackCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an event of any type previously registered occurs. The \n parameters are different depending on the type of event, shown in \n brackets for each parameter listed here.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 183 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 183 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 183 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "EventCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an event of any type previously registered occurs. The \n parameters are different depending on the type of event, shown in \n brackets for each parameter listed here.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the event information, including:\n * `type` - The type of event fired, from the list in the description of [Pebble.addEventListener()](#addEventListener).\n * `payload` - The dictionary sent over ``AppMessage`` consisting of \n key-value pairs. *This field only exists for `appmessage` events.*\n * `response` - The contents of the URL navigated to when the \n configuration page was closed, after the anchor. This may be encoded, \n which will require use of decodeURIComponent() before reading as an \n object. *This field only exists for for `webviewclosed` events.*", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 303, + "column": 0 + }, + "end": { + "line": 318, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "EventCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the event information, including:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 60 + }, + "end": { + "line": 2, + "column": 12, + "offset": 66 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event fired, from the list in the description of ", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 66 + }, + "end": { + "line": 2, + "column": 76, + "offset": 130 + }, + "indent": [] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 77, + "offset": 131 + }, + "end": { + "line": 2, + "column": 102, + "offset": 156 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 76, + "offset": 130 + }, + "end": { + "line": 2, + "column": 122, + "offset": 176 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 122, + "offset": 176 + }, + "end": { + "line": 2, + "column": 123, + "offset": 177 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 60 + }, + "end": { + "line": 2, + "column": 123, + "offset": 177 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 55 + }, + "end": { + "line": 2, + "column": 123, + "offset": 177 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "payload", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 183 + }, + "end": { + "line": 3, + "column": 15, + "offset": 192 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The dictionary sent over ", + "position": { + "start": { + "line": 3, + "column": 15, + "offset": 192 + }, + "end": { + "line": 3, + "column": 43, + "offset": 220 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 3, + "column": 43, + "offset": 220 + }, + "end": { + "line": 3, + "column": 57, + "offset": 234 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " consisting of \nkey-value pairs. ", + "position": { + "start": { + "line": 3, + "column": 57, + "offset": 234 + }, + "end": { + "line": 4, + "column": 23, + "offset": 272 + }, + "indent": [ + 6 + ] + } + }, + { + "type": "emphasis", + "children": [ + { + "type": "text", + "value": "This field only exists for ", + "position": { + "start": { + "line": 4, + "column": 24, + "offset": 273 + }, + "end": { + "line": 4, + "column": 51, + "offset": 300 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "appmessage", + "position": { + "start": { + "line": 4, + "column": 51, + "offset": 300 + }, + "end": { + "line": 4, + "column": 63, + "offset": 312 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " events.", + "position": { + "start": { + "line": 4, + "column": 63, + "offset": 312 + }, + "end": { + "line": 4, + "column": 71, + "offset": 320 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 23, + "offset": 272 + }, + "end": { + "line": 4, + "column": 72, + "offset": 321 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 183 + }, + "end": { + "line": 4, + "column": 72, + "offset": 321 + }, + "indent": [ + 6 + ] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 178 + }, + "end": { + "line": 4, + "column": 72, + "offset": 321 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "response", + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 327 + }, + "end": { + "line": 5, + "column": 16, + "offset": 337 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The contents of the URL navigated to when the \nconfiguration page was closed, after the anchor. This may be encoded, \nwhich will require use of decodeURIComponent() before reading as an \nobject. ", + "position": { + "start": { + "line": 5, + "column": 16, + "offset": 337 + }, + "end": { + "line": 8, + "column": 14, + "offset": 550 + }, + "indent": [ + 6, + 6, + 6 + ] + } + }, + { + "type": "emphasis", + "children": [ + { + "type": "text", + "value": "This field only exists for for ", + "position": { + "start": { + "line": 8, + "column": 15, + "offset": 551 + }, + "end": { + "line": 8, + "column": 46, + "offset": 582 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "webviewclosed", + "position": { + "start": { + "line": 8, + "column": 46, + "offset": 582 + }, + "end": { + "line": 8, + "column": 61, + "offset": 597 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " events.", + "position": { + "start": { + "line": 8, + "column": 61, + "offset": 597 + }, + "end": { + "line": 8, + "column": 69, + "offset": 605 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 14, + "offset": 550 + }, + "end": { + "line": 8, + "column": 70, + "offset": 606 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 327 + }, + "end": { + "line": 8, + "column": 70, + "offset": 606 + }, + "indent": [ + 6, + 6, + 6 + ] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 322 + }, + "end": { + "line": 8, + "column": 70, + "offset": 606 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 55 + }, + "end": { + "line": 8, + "column": 70, + "offset": 606 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 8, + "column": 70, + "offset": 606 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "EventCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleEventCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when the user's timeline token is available.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "TimelineTokenCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when the user's timeline token is available.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "The user's token.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "token" + } + ], + "loc": { + "start": { + "line": 320, + "column": 0 + }, + "end": { + "line": 326, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "TimelineTokenCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "token", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The user's token.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "TimelineTokenCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleTimelineTokenCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when AppGlanceReload is successful.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppGlanceReloadSuccessCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when AppGlanceReload is successful.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An {@link #AppGlanceSlice AppGlanceSlice} object \n containing the app glance slices.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "AppGlanceSlices" + } + ], + "loc": { + "start": { + "line": 266, + "column": 0 + }, + "end": { + "line": 273, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceReloadSuccessCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "AppGlanceSlices", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 4, + "offset": 3 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 4, + "offset": 3 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object \n containing the app glance slices.", + "position": { + "start": { + "line": 1, + "column": 42, + "offset": 41 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceReloadSuccessCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceReloadSuccessCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "message", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 62, + "offset": 61 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 62, + "offset": 61 + }, + "end": { + "line": 2, + "column": 4, + "offset": 65 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 65 + }, + "end": { + "line": 2, + "column": 21, + "offset": 82 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 82 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "PostMessageCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `message`\n {@link #on event}.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.\n * `data` - The data sent within the message.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 336, + "column": 0 + }, + "end": { + "line": 346, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "PostMessageCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 11, + "offset": 112 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The data sent within the message.", + "position": { + "start": { + "line": 3, + "column": 11, + "offset": 112 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 102 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "PostMessageCallback", + "kind": "typedef" + } + ], + "namespace": "PebblePostMessageCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 71, + "offset": 70 + }, + "end": { + "line": 2, + "column": 4, + "offset": 74 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 74 + }, + "end": { + "line": 2, + "column": 21, + "offset": 91 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 91 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "PostMessageErrorCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessageerror`\n {@link #on event}.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.\n * `data` - The data failed to send within the message.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 348, + "column": 0 + }, + "end": { + "line": 358, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "PostMessageErrorCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 11, + "offset": 112 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The data failed to send within the message.", + "position": { + "start": { + "line": 3, + "column": 11, + "offset": 112 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 102 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "PostMessageErrorCallback", + "kind": "typedef" + } + ], + "namespace": "PebblePostMessageErrorCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessageconnected", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 2, + "column": 4, + "offset": 78 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 78 + }, + "end": { + "line": 2, + "column": 21, + "offset": 95 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 95 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "PostMessageConnectedCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessageconnected`\n {@link #on event}.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 360, + "column": 0 + }, + "end": { + "line": 369, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "PostMessageConnectedCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "PostMessageConnectedCallback", + "kind": "typedef" + } + ], + "namespace": "PebblePostMessageConnectedCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessagedisconnected", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 78, + "offset": 77 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 78, + "offset": 77 + }, + "end": { + "line": 2, + "column": 4, + "offset": 81 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 81 + }, + "end": { + "line": 2, + "column": 21, + "offset": 98 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 98 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "PostMessageDisconnectedCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessagedisconnected`\n {@link #on event}.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 371, + "column": 0 + }, + "end": { + "line": 380, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "PostMessageDisconnectedCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "PostMessageDisconnectedCallback", + "kind": "typedef" + } + ], + "namespace": "PebblePostMessageDisconnectedCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides information about the connected Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "WatchInfo" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Provides information about the connected Pebble smartwatch.", + "lineNumber": 4 + }, + { + "title": "property", + "description": "The hardware platform, such as `basalt` or `emery`.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "platform" + }, + { + "title": "property", + "description": "The watch model, such as `pebble_black`", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "model" + }, + { + "title": "property", + "description": "The user's currently selected language on\n this watch.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "language" + }, + { + "title": "property", + "description": "An object containing information about the\n watch's firmware version, including:\n * `major` - The major version\n * `minor` - The minor version\n * `patch` - The patch version\n * `suffix` - Any additional version information, such as `beta3`", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "firmware" + } + ], + "loc": { + "start": { + "line": 383, + "column": 0 + }, + "end": { + "line": 399, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "WatchInfo", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "memberof": "Pebble", + "properties": [ + { + "name": "platform", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The hardware platform, such as ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "basalt", + "position": { + "start": { + "line": 1, + "column": 32, + "offset": 31 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 1, + "column": 40, + "offset": 39 + }, + "end": { + "line": 1, + "column": 44, + "offset": 43 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "emery", + "position": { + "start": { + "line": 1, + "column": 44, + "offset": 43 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 51, + "offset": 50 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "model", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The watch model, such as ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 26, + "offset": 25 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "pebble_black", + "position": { + "start": { + "line": 1, + "column": 26, + "offset": 25 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "language", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The user's currently selected language on\n this watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "firmware", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the\n watch's firmware version, including:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 83 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "major", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 89 + }, + "end": { + "line": 3, + "column": 13, + "offset": 96 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The major version", + "position": { + "start": { + "line": 3, + "column": 13, + "offset": 96 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 89 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 84 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "minor", + "position": { + "start": { + "line": 4, + "column": 6, + "offset": 122 + }, + "end": { + "line": 4, + "column": 13, + "offset": 129 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The minor version", + "position": { + "start": { + "line": 4, + "column": 13, + "offset": 129 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 6, + "offset": 122 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 117 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "patch", + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 155 + }, + "end": { + "line": 5, + "column": 13, + "offset": 162 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The patch version", + "position": { + "start": { + "line": 5, + "column": 13, + "offset": 162 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 155 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 150 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "suffix", + "position": { + "start": { + "line": 6, + "column": 6, + "offset": 188 + }, + "end": { + "line": 6, + "column": 14, + "offset": 196 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Any additional version information, such as ", + "position": { + "start": { + "line": 6, + "column": 14, + "offset": 196 + }, + "end": { + "line": 6, + "column": 61, + "offset": 243 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "beta3", + "position": { + "start": { + "line": 6, + "column": 61, + "offset": 243 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 6, + "offset": 188 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 183 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 84 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "WatchInfo", + "kind": "typedef" + } + ], + "namespace": "PebbleWatchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The structure of an app glance.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "AppGlanceSlice" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The structure of an app glance.", + "lineNumber": 4 + }, + { + "title": "property", + "description": "Optional ISO date-time string of when \nthe entry should expire and no longer be shown in the app glance.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "expirationTime" + }, + { + "title": "property", + "description": "An object containing:\n * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "layout" + } + ], + "loc": { + "start": { + "line": 401, + "column": 0 + }, + "end": { + "line": 412, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 264, + "column": 0 + }, + "end": { + "line": 264, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceSlice", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "memberof": "Pebble", + "properties": [ + { + "name": "expirationTime", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Optional ISO date-time string of when \nthe entry should expire and no longer be shown in the app glance.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "layout", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "icon", + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 27 + }, + "end": { + "line": 2, + "column": 12, + "offset": 33 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 33 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 27 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 22 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "subtitleTemplateString", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 128 + }, + "end": { + "line": 3, + "column": 30, + "offset": 152 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Template string that will be displayed in the subtitle of the app glance.", + "position": { + "start": { + "line": 3, + "column": 30, + "offset": 152 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 128 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 123 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 22 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceSlice", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceSlice" + } + ] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + } + ], + "namespace": "Pebble" + } +] \ No newline at end of file diff --git a/devsite/source/_data/jsdocs-rocky.json b/devsite/source/_data/jsdocs-rocky.json new file mode 100644 index 00000000..c77a9298 --- /dev/null +++ b/devsite/source/_data/jsdocs-rocky.json @@ -0,0 +1,25825 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The CanvasRenderingContext2D interface is used for drawing\n rectangles, text, images and other objects onto the canvas element. It\n provides the 2D rendering context for the drawing on the device's display.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 76, + "offset": 206 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 76, + "offset": 206 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The canvas uses a standard x and y \n ", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 208 + }, + "end": { + "line": 6, + "column": 2, + "offset": 245 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "coordinate system" + } + ], + "position": { + "start": { + "line": 6, + "column": 2, + "offset": 245 + }, + "end": { + "line": 6, + "column": 111, + "offset": 354 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ". ", + "position": { + "start": { + "line": 6, + "column": 111, + "offset": 354 + }, + "end": { + "line": 6, + "column": 113, + "offset": 356 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 208 + }, + "end": { + "line": 6, + "column": 113, + "offset": 356 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The ", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 358 + }, + "end": { + "line": 8, + "column": 5, + "offset": 362 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "CanvasRenderingContext2D", + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 362 + }, + "end": { + "line": 8, + "column": 31, + "offset": 388 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object is obtained as a parameter in the\n ", + "position": { + "start": { + "line": 8, + "column": 31, + "offset": 388 + }, + "end": { + "line": 9, + "column": 2, + "offset": 431 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/rocky#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "rocky.on('draw', ...)" + } + ], + "position": { + "start": { + "line": 9, + "column": 2, + "offset": 431 + }, + "end": { + "line": 9, + "column": 54, + "offset": 483 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " event.", + "position": { + "start": { + "line": 9, + "column": 54, + "offset": 483 + }, + "end": { + "line": 9, + "column": 61, + "offset": 490 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 358 + }, + "end": { + "line": 9, + "column": 61, + "offset": 490 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
});", + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 492 + }, + "end": { + "line": 11, + "column": 92, + "offset": 583 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 492 + }, + "end": { + "line": 11, + "column": 92, + "offset": 583 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The state of pixels is maintained between each draw, so developers are \n responsible for clearing an area, before drawing again. ", + "position": { + "start": { + "line": 13, + "column": 1, + "offset": 585 + }, + "end": { + "line": 14, + "column": 58, + "offset": 714 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1, + "offset": 585 + }, + "end": { + "line": 14, + "column": 58, + "offset": 714 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Please note that this API is still in development and there may be some \n limitations, which are documented below. We will also be adding support \n for more common APIs in future releases.", + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 716 + }, + "end": { + "line": 18, + "column": 42, + "offset": 904 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 716 + }, + "end": { + "line": 18, + "column": 42, + "offset": 904 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 18, + "column": 42, + "offset": 904 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "CanvasRenderingContext2D" + }, + { + "title": "desc", + "description": "The CanvasRenderingContext2D interface is used for drawing\n rectangles, text, images and other objects onto the canvas element. It\n provides the 2D rendering context for the drawing on the device's display.\n\nThe canvas uses a standard x and y \n {@link https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes coordinate system}. \n\nThe `CanvasRenderingContext2D` object is obtained as a parameter in the\n {@link /docs/rockyjs/rocky#on rocky.on('draw', ...)} event.\n\n`rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
});`\n\nThe state of pixels is maintained between each draw, so developers are \n responsible for clearing an area, before drawing again. \n\nPlease note that this API is still in development and there may be some \n limitations, which are documented below. We will also be adding support \n for more common APIs in future releases.", + "lineNumber": 2 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 21, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 22, + "column": 0 + }, + "end": { + "line": 163, + "column": 2 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "kind": "namespace", + "name": "CanvasRenderingContext2D", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The TextMetrics interface represents the dimensions of a text\n in the canvas (display), as created by ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 44, + "offset": 105 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#measureText", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "measureText" + } + ], + "position": { + "start": { + "line": 2, + "column": 44, + "offset": 105 + }, + "end": { + "line": 2, + "column": 76, + "offset": 137 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 76, + "offset": 137 + }, + "end": { + "line": 2, + "column": 77, + "offset": 138 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 77, + "offset": 138 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 77, + "offset": 138 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "TextMetrics" + }, + { + "title": "desc", + "description": "The TextMetrics interface represents the dimensions of a text\n in the canvas (display), as created by {@link #measureText measureText}.", + "lineNumber": 2 + }, + { + "title": "property", + "description": "Calculated width of text in pixels.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "property", + "description": "Calculated height of text in pixels.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 23, + "column": 2 + }, + "end": { + "line": 30, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 66, + "column": 2 + }, + "end": { + "line": 66, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "kind": "typedef", + "name": "TextMetrics", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "properties": [ + { + "name": "width", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calculated width of text in pixels.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calculated height of text in pixels.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "TextMetrics", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.TextMetrics" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Specifies the color to use inside shapes. The default is\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 2, + "offset": 58 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "#000", + "position": { + "start": { + "line": 2, + "column": 2, + "offset": 58 + }, + "end": { + "line": 2, + "column": 8, + "offset": 64 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " (black).", + "position": { + "start": { + "line": 2, + "column": 8, + "offset": 64 + }, + "end": { + "line": 2, + "column": 17, + "offset": 73 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 17, + "offset": 73 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Options", + "position": { + "start": { + "line": 4, + "column": 8, + "offset": 82 + }, + "end": { + "line": 4, + "column": 15, + "offset": 89 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 75 + }, + "end": { + "line": 4, + "column": 15, + "offset": 89 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 91 + }, + "end": { + "line": 6, + "column": 19, + "offset": 109 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 91 + }, + "end": { + "line": 6, + "column": 19, + "offset": 109 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Most (but not all) CSS color names. e.g. ", + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 115 + }, + "end": { + "line": 8, + "column": 46, + "offset": 156 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "blanchedalmond", + "position": { + "start": { + "line": 8, + "column": 46, + "offset": 156 + }, + "end": { + "line": 8, + "column": 62, + "offset": 172 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 115 + }, + "end": { + "line": 8, + "column": 62, + "offset": 172 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 111 + }, + "end": { + "line": 8, + "column": 62, + "offset": 172 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pebble color names. e.g. ", + "position": { + "start": { + "line": 9, + "column": 5, + "offset": 177 + }, + "end": { + "line": 9, + "column": 30, + "offset": 202 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "shockingpink", + "position": { + "start": { + "line": 9, + "column": 30, + "offset": 202 + }, + "end": { + "line": 9, + "column": 44, + "offset": 216 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 5, + "offset": 177 + }, + "end": { + "line": 9, + "column": 44, + "offset": 216 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 173 + }, + "end": { + "line": 9, + "column": 44, + "offset": 216 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Hex color codes, short and long. e.g. ", + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 221 + }, + "end": { + "line": 10, + "column": 43, + "offset": 259 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "#FFFFFF", + "position": { + "start": { + "line": 10, + "column": 43, + "offset": 259 + }, + "end": { + "line": 10, + "column": 52, + "offset": 268 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 10, + "column": 52, + "offset": 268 + }, + "end": { + "line": 10, + "column": 56, + "offset": 272 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "#FFF", + "position": { + "start": { + "line": 10, + "column": 56, + "offset": 272 + }, + "end": { + "line": 10, + "column": 62, + "offset": 278 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 221 + }, + "end": { + "line": 10, + "column": 62, + "offset": 278 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 217 + }, + "end": { + "line": 10, + "column": 62, + "offset": 278 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 111 + }, + "end": { + "line": 10, + "column": 62, + "offset": 278 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Please note that we currently only support solid colors. You may specifiy\n ", + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 280 + }, + "end": { + "line": 13, + "column": 2, + "offset": 355 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transparent", + "position": { + "start": { + "line": 13, + "column": 2, + "offset": 355 + }, + "end": { + "line": 13, + "column": 15, + "offset": 368 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 13, + "column": 15, + "offset": 368 + }, + "end": { + "line": 13, + "column": 19, + "offset": 372 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "clear", + "position": { + "start": { + "line": 13, + "column": 19, + "offset": 372 + }, + "end": { + "line": 13, + "column": 26, + "offset": 379 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for transparency, but we do do not support \n partial transparency or the ", + "position": { + "start": { + "line": 13, + "column": 26, + "offset": 379 + }, + "end": { + "line": 14, + "column": 30, + "offset": 453 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "#RRGGBBAA", + "position": { + "start": { + "line": 14, + "column": 30, + "offset": 453 + }, + "end": { + "line": 14, + "column": 41, + "offset": 464 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " notation yet.", + "position": { + "start": { + "line": 14, + "column": 41, + "offset": 464 + }, + "end": { + "line": 14, + "column": 55, + "offset": 478 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 280 + }, + "end": { + "line": 14, + "column": 55, + "offset": 478 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.fillStyle = 'white';", + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 480 + }, + "end": { + "line": 16, + "column": 27, + "offset": 506 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 480 + }, + "end": { + "line": 16, + "column": 27, + "offset": 506 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 16, + "column": 27, + "offset": 506 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Specifies the color to use inside shapes. The default is\n `#000` (black).\n\n #### Options\n\n Possible values:\n\n * Most (but not all) CSS color names. e.g. `blanchedalmond`\n * Pebble color names. e.g. `shockingpink`\n * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF`\n\nPlease note that we currently only support solid colors. You may specifiy\n `transparent` or `clear` for transparency, but we do do not support \n partial transparency or the `#RRGGBBAA` notation yet.\n\n`ctx.fillStyle = 'white';`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 47, + "column": 2 + }, + "end": { + "line": 65, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 66, + "column": 2 + }, + "end": { + "line": 66, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "fillStyle", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "fillStyle", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.fillStyle" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides information about the device's canvas (display). This is not\n actually a DOM element, it is provided for standards compliance only. ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 72, + "offset": 141 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 72, + "offset": 141 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
  var h = ctx.canvas.unobstructedHeight;
});", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 143 + }, + "end": { + "line": 4, + "column": 146, + "offset": 288 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 143 + }, + "end": { + "line": 4, + "column": 146, + "offset": 288 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 146, + "offset": 288 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "Canvas" + }, + { + "title": "desc", + "description": "Provides information about the device's canvas (display). This is not\n actually a DOM element, it is provided for standards compliance only. \n\n`rocky.on('draw', function(drawEvent) {
  var ctx = drawEvent.context;
  var h = ctx.canvas.unobstructedHeight;
});`", + "lineNumber": 2 + }, + { + "title": "property", + "description": "The full width of the canvas.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "clientWidth" + }, + { + "title": "property", + "description": "The full height of the canvas.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "clientHeight" + }, + { + "title": "property", + "description": "The width of the canvas that is not\n obstructed by system overlays (Timeline Quick View).", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "unobstructedWidth" + }, + { + "title": "property", + "description": "The height of the canvas that is\n not obstructed by system overlays (Timeline Quick View).", + "lineNumber": 11, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "unobstructedHeight" + } + ], + "loc": { + "start": { + "line": 32, + "column": 2 + }, + "end": { + "line": 45, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 66, + "column": 2 + }, + "end": { + "line": 66, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "kind": "typedef", + "name": "Canvas", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "properties": [ + { + "name": "clientWidth", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The full width of the canvas.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "clientHeight", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The full height of the canvas.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "unobstructedWidth", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The width of the canvas that is not\n obstructed by system overlays (Timeline Quick View).", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 92 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "unobstructedHeight", + "lineNumber": 11, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The height of the canvas that is\n not obstructed by system overlays (Timeline Quick View).", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 93 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "Canvas", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.Canvas" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Specifies the color to use for lines around shapes. The\n default is ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 68 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "#000", + "position": { + "start": { + "line": 2, + "column": 13, + "offset": 68 + }, + "end": { + "line": 2, + "column": 19, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " (black).", + "position": { + "start": { + "line": 2, + "column": 19, + "offset": 74 + }, + "end": { + "line": 2, + "column": 28, + "offset": 83 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 28, + "offset": 83 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Options", + "position": { + "start": { + "line": 4, + "column": 8, + "offset": 92 + }, + "end": { + "line": 4, + "column": 15, + "offset": 99 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 85 + }, + "end": { + "line": 4, + "column": 15, + "offset": 99 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 101 + }, + "end": { + "line": 6, + "column": 19, + "offset": 119 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 101 + }, + "end": { + "line": 6, + "column": 19, + "offset": 119 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Most (but not all) CSS color names. e.g. ", + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 125 + }, + "end": { + "line": 8, + "column": 46, + "offset": 166 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "blanchedalmond", + "position": { + "start": { + "line": 8, + "column": 46, + "offset": 166 + }, + "end": { + "line": 8, + "column": 62, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 125 + }, + "end": { + "line": 8, + "column": 62, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 121 + }, + "end": { + "line": 8, + "column": 62, + "offset": 182 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pebble color names. e.g. ", + "position": { + "start": { + "line": 9, + "column": 5, + "offset": 187 + }, + "end": { + "line": 9, + "column": 30, + "offset": 212 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "shockingpink", + "position": { + "start": { + "line": 9, + "column": 30, + "offset": 212 + }, + "end": { + "line": 9, + "column": 44, + "offset": 226 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 5, + "offset": 187 + }, + "end": { + "line": 9, + "column": 44, + "offset": 226 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 183 + }, + "end": { + "line": 9, + "column": 44, + "offset": 226 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Hex color codes, short and long. e.g. ", + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 231 + }, + "end": { + "line": 10, + "column": 43, + "offset": 269 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "#FFFFFF", + "position": { + "start": { + "line": 10, + "column": 43, + "offset": 269 + }, + "end": { + "line": 10, + "column": 52, + "offset": 278 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 10, + "column": 52, + "offset": 278 + }, + "end": { + "line": 10, + "column": 56, + "offset": 282 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "#FFF", + "position": { + "start": { + "line": 10, + "column": 56, + "offset": 282 + }, + "end": { + "line": 10, + "column": 62, + "offset": 288 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 231 + }, + "end": { + "line": 10, + "column": 62, + "offset": 288 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 227 + }, + "end": { + "line": 10, + "column": 62, + "offset": 288 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 121 + }, + "end": { + "line": 10, + "column": 62, + "offset": 288 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Please note that we currently only support solid colors. You may specifiy\n ", + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 290 + }, + "end": { + "line": 13, + "column": 2, + "offset": 365 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transparent", + "position": { + "start": { + "line": 13, + "column": 2, + "offset": 365 + }, + "end": { + "line": 13, + "column": 15, + "offset": 378 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 13, + "column": 15, + "offset": 378 + }, + "end": { + "line": 13, + "column": 19, + "offset": 382 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "clear", + "position": { + "start": { + "line": 13, + "column": 19, + "offset": 382 + }, + "end": { + "line": 13, + "column": 26, + "offset": 389 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for transparency, but we do do not support \n partial transparency or the ", + "position": { + "start": { + "line": 13, + "column": 26, + "offset": 389 + }, + "end": { + "line": 14, + "column": 30, + "offset": 463 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "#RRGGBBAA", + "position": { + "start": { + "line": 14, + "column": 30, + "offset": 463 + }, + "end": { + "line": 14, + "column": 41, + "offset": 474 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " notation yet.", + "position": { + "start": { + "line": 14, + "column": 41, + "offset": 474 + }, + "end": { + "line": 14, + "column": 55, + "offset": 488 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 290 + }, + "end": { + "line": 14, + "column": 55, + "offset": 488 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.strokeStyle = 'red';", + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 490 + }, + "end": { + "line": 16, + "column": 27, + "offset": 516 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 490 + }, + "end": { + "line": 16, + "column": 27, + "offset": 516 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 16, + "column": 27, + "offset": 516 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Specifies the color to use for lines around shapes. The\n default is `#000` (black).\n\n #### Options\n\n Possible values:\n\n * Most (but not all) CSS color names. e.g. `blanchedalmond`\n * Pebble color names. e.g. `shockingpink`\n * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF`\n\nPlease note that we currently only support solid colors. You may specifiy\n `transparent` or `clear` for transparency, but we do do not support \n partial transparency or the `#RRGGBBAA` notation yet.\n\n`ctx.strokeStyle = 'red';`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 68, + "column": 2 + }, + "end": { + "line": 86, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 87, + "column": 2 + }, + "end": { + "line": 87, + "column": 13 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "strokeStyle", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "strokeStyle", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.strokeStyle" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#Canvas", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Canvas" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 25, + "offset": 24 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object containing information about\n the system's canvas (display).", + "position": { + "start": { + "line": 1, + "column": 25, + "offset": 24 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "A {@link #Canvas Canvas} object containing information about\n the system's canvas (display).", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 89, + "column": 2 + }, + "end": { + "line": 92, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 93, + "column": 2 + }, + "end": { + "line": 93, + "column": 8 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "canvas", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "canvas", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.canvas" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The width of lines drawn (to the nearest integer) with the\n context (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 11, + "offset": 69 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "1.0", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 69 + }, + "end": { + "line": 2, + "column": 16, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " by default).", + "position": { + "start": { + "line": 2, + "column": 16, + "offset": 74 + }, + "end": { + "line": 2, + "column": 29, + "offset": 87 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 29, + "offset": 87 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.lineWidth = 8;", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 89 + }, + "end": { + "line": 4, + "column": 21, + "offset": 109 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 89 + }, + "end": { + "line": 4, + "column": 21, + "offset": 109 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 21, + "offset": 109 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "The width of lines drawn (to the nearest integer) with the\n context (`1.0` by default).\n\n`ctx.lineWidth = 8;`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 95, + "column": 2 + }, + "end": { + "line": 101, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 102, + "column": 2 + }, + "end": { + "line": 102, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "lineWidth", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "lineWidth", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.lineWidth" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Specifies the current text style being used when drawing text.\n Although this string uses the same syntax as a CSS font specifier, you \n cannot specifiy arbitrary values and you must only use one of the values below.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 81, + "offset": 217 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 81, + "offset": 217 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " The default font is ", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 219 + }, + "end": { + "line": 5, + "column": 22, + "offset": 240 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "14px bold Gothic", + "position": { + "start": { + "line": 5, + "column": 22, + "offset": 240 + }, + "end": { + "line": 5, + "column": 40, + "offset": 258 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 5, + "column": 40, + "offset": 258 + }, + "end": { + "line": 5, + "column": 41, + "offset": 259 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 219 + }, + "end": { + "line": 5, + "column": 41, + "offset": 259 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.font = '28px bold Droid-serif';", + "position": { + "start": { + "line": 7, + "column": 1, + "offset": 261 + }, + "end": { + "line": 7, + "column": 38, + "offset": 298 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1, + "offset": 261 + }, + "end": { + "line": 7, + "column": 38, + "offset": 298 + }, + "indent": [] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Options", + "position": { + "start": { + "line": 9, + "column": 8, + "offset": 307 + }, + "end": { + "line": 9, + "column": 15, + "offset": 314 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 300 + }, + "end": { + "line": 9, + "column": 15, + "offset": 314 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 316 + }, + "end": { + "line": 11, + "column": 19, + "offset": 334 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 316 + }, + "end": { + "line": 11, + "column": 19, + "offset": 334 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "18px bold Gothic", + "position": { + "start": { + "line": 13, + "column": 5, + "offset": 340 + }, + "end": { + "line": 13, + "column": 23, + "offset": 358 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 5, + "offset": 340 + }, + "end": { + "line": 13, + "column": 23, + "offset": 358 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1, + "offset": 336 + }, + "end": { + "line": 13, + "column": 23, + "offset": 358 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "14px Gothic", + "position": { + "start": { + "line": 14, + "column": 5, + "offset": 363 + }, + "end": { + "line": 14, + "column": 18, + "offset": 376 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 5, + "offset": 363 + }, + "end": { + "line": 14, + "column": 18, + "offset": 376 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 1, + "offset": 359 + }, + "end": { + "line": 14, + "column": 18, + "offset": 376 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "14px bold Gothic", + "position": { + "start": { + "line": 15, + "column": 5, + "offset": 381 + }, + "end": { + "line": 15, + "column": 23, + "offset": 399 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 5, + "offset": 381 + }, + "end": { + "line": 15, + "column": 23, + "offset": 399 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1, + "offset": 377 + }, + "end": { + "line": 15, + "column": 23, + "offset": 399 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "18px Gothic", + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 404 + }, + "end": { + "line": 16, + "column": 18, + "offset": 417 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 404 + }, + "end": { + "line": 16, + "column": 18, + "offset": 417 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 400 + }, + "end": { + "line": 16, + "column": 18, + "offset": 417 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "24px Gothic", + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 422 + }, + "end": { + "line": 17, + "column": 18, + "offset": 435 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 422 + }, + "end": { + "line": 17, + "column": 18, + "offset": 435 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1, + "offset": 418 + }, + "end": { + "line": 17, + "column": 18, + "offset": 435 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "24px bold Gothic", + "position": { + "start": { + "line": 18, + "column": 5, + "offset": 440 + }, + "end": { + "line": 18, + "column": 23, + "offset": 458 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 18, + "column": 5, + "offset": 440 + }, + "end": { + "line": 18, + "column": 23, + "offset": 458 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 18, + "column": 1, + "offset": 436 + }, + "end": { + "line": 18, + "column": 23, + "offset": 458 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "28px Gothic", + "position": { + "start": { + "line": 19, + "column": 5, + "offset": 463 + }, + "end": { + "line": 19, + "column": 18, + "offset": 476 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 5, + "offset": 463 + }, + "end": { + "line": 19, + "column": 18, + "offset": 476 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1, + "offset": 459 + }, + "end": { + "line": 19, + "column": 18, + "offset": 476 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "28px bold Gothic", + "position": { + "start": { + "line": 20, + "column": 5, + "offset": 481 + }, + "end": { + "line": 20, + "column": 23, + "offset": 499 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 20, + "column": 5, + "offset": 481 + }, + "end": { + "line": 20, + "column": 23, + "offset": 499 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 20, + "column": 1, + "offset": 477 + }, + "end": { + "line": 20, + "column": 23, + "offset": 499 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "30px bolder Bitham", + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 504 + }, + "end": { + "line": 21, + "column": 25, + "offset": 524 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 504 + }, + "end": { + "line": 21, + "column": 25, + "offset": 524 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1, + "offset": 500 + }, + "end": { + "line": 21, + "column": 25, + "offset": 524 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "42px bold Bitham", + "position": { + "start": { + "line": 22, + "column": 5, + "offset": 529 + }, + "end": { + "line": 22, + "column": 23, + "offset": 547 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 22, + "column": 5, + "offset": 529 + }, + "end": { + "line": 22, + "column": 23, + "offset": 547 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 22, + "column": 1, + "offset": 525 + }, + "end": { + "line": 22, + "column": 23, + "offset": 547 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "42px light Bitham", + "position": { + "start": { + "line": 23, + "column": 5, + "offset": 552 + }, + "end": { + "line": 23, + "column": 24, + "offset": 571 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 5, + "offset": 552 + }, + "end": { + "line": 23, + "column": 24, + "offset": 571 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1, + "offset": 548 + }, + "end": { + "line": 23, + "column": 24, + "offset": 571 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "42px Bitham-numeric", + "position": { + "start": { + "line": 24, + "column": 5, + "offset": 576 + }, + "end": { + "line": 24, + "column": 26, + "offset": 597 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 24, + "column": 5, + "offset": 576 + }, + "end": { + "line": 24, + "column": 26, + "offset": 597 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 24, + "column": 1, + "offset": 572 + }, + "end": { + "line": 24, + "column": 26, + "offset": 597 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "34px Bitham-numeric", + "position": { + "start": { + "line": 25, + "column": 5, + "offset": 602 + }, + "end": { + "line": 25, + "column": 26, + "offset": 623 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 5, + "offset": 602 + }, + "end": { + "line": 25, + "column": 26, + "offset": 623 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 1, + "offset": 598 + }, + "end": { + "line": 25, + "column": 26, + "offset": 623 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "21px Roboto", + "position": { + "start": { + "line": 26, + "column": 5, + "offset": 628 + }, + "end": { + "line": 26, + "column": 18, + "offset": 641 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 26, + "column": 5, + "offset": 628 + }, + "end": { + "line": 26, + "column": 18, + "offset": 641 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 26, + "column": 1, + "offset": 624 + }, + "end": { + "line": 26, + "column": 18, + "offset": 641 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "49px Roboto-subset", + "position": { + "start": { + "line": 27, + "column": 5, + "offset": 646 + }, + "end": { + "line": 27, + "column": 25, + "offset": 666 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 27, + "column": 5, + "offset": 646 + }, + "end": { + "line": 27, + "column": 25, + "offset": 666 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 27, + "column": 1, + "offset": 642 + }, + "end": { + "line": 27, + "column": 25, + "offset": 666 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "28px bold Droid-serif", + "position": { + "start": { + "line": 28, + "column": 5, + "offset": 671 + }, + "end": { + "line": 28, + "column": 28, + "offset": 694 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 28, + "column": 5, + "offset": 671 + }, + "end": { + "line": 28, + "column": 28, + "offset": 694 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 28, + "column": 1, + "offset": 667 + }, + "end": { + "line": 28, + "column": 28, + "offset": 694 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "20px bold Leco-numbers", + "position": { + "start": { + "line": 29, + "column": 5, + "offset": 699 + }, + "end": { + "line": 29, + "column": 29, + "offset": 723 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 5, + "offset": 699 + }, + "end": { + "line": 29, + "column": 29, + "offset": 723 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 1, + "offset": 695 + }, + "end": { + "line": 29, + "column": 29, + "offset": 723 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "26px bold Leco-numbers-am-pm", + "position": { + "start": { + "line": 30, + "column": 5, + "offset": 728 + }, + "end": { + "line": 30, + "column": 35, + "offset": 758 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 30, + "column": 5, + "offset": 728 + }, + "end": { + "line": 30, + "column": 35, + "offset": 758 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 30, + "column": 1, + "offset": 724 + }, + "end": { + "line": 30, + "column": 35, + "offset": 758 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "32px bold numbers Leco-numbers", + "position": { + "start": { + "line": 31, + "column": 5, + "offset": 763 + }, + "end": { + "line": 31, + "column": 37, + "offset": 795 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 31, + "column": 5, + "offset": 763 + }, + "end": { + "line": 31, + "column": 37, + "offset": 795 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 31, + "column": 1, + "offset": 759 + }, + "end": { + "line": 31, + "column": 37, + "offset": 795 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "36px bold numbers Leco-numbers", + "position": { + "start": { + "line": 32, + "column": 5, + "offset": 800 + }, + "end": { + "line": 32, + "column": 37, + "offset": 832 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 32, + "column": 5, + "offset": 800 + }, + "end": { + "line": 32, + "column": 37, + "offset": 832 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 32, + "column": 1, + "offset": 796 + }, + "end": { + "line": 32, + "column": 37, + "offset": 832 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "38px bold numbers Leco-numbers", + "position": { + "start": { + "line": 33, + "column": 5, + "offset": 837 + }, + "end": { + "line": 33, + "column": 37, + "offset": 869 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 33, + "column": 5, + "offset": 837 + }, + "end": { + "line": 33, + "column": 37, + "offset": 869 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 33, + "column": 1, + "offset": 833 + }, + "end": { + "line": 33, + "column": 37, + "offset": 869 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "42px bold numbers Leco-numbers", + "position": { + "start": { + "line": 34, + "column": 5, + "offset": 874 + }, + "end": { + "line": 34, + "column": 37, + "offset": 906 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 34, + "column": 5, + "offset": 874 + }, + "end": { + "line": 34, + "column": 37, + "offset": 906 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 34, + "column": 1, + "offset": 870 + }, + "end": { + "line": 34, + "column": 37, + "offset": 906 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "28px light numbers Leco-numbers", + "position": { + "start": { + "line": 35, + "column": 5, + "offset": 911 + }, + "end": { + "line": 35, + "column": 38, + "offset": 944 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 35, + "column": 5, + "offset": 911 + }, + "end": { + "line": 35, + "column": 38, + "offset": 944 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 35, + "column": 1, + "offset": 907 + }, + "end": { + "line": 35, + "column": 38, + "offset": 944 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1, + "offset": 336 + }, + "end": { + "line": 35, + "column": 38, + "offset": 944 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 35, + "column": 38, + "offset": 944 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Specifies the current text style being used when drawing text.\n Although this string uses the same syntax as a CSS font specifier, you \n cannot specifiy arbitrary values and you must only use one of the values below.\n\n The default font is `14px bold Gothic`.\n\n`ctx.font = '28px bold Droid-serif';`\n\n #### Options\n\n Possible values:\n\n * `18px bold Gothic`\n * `14px Gothic`\n * `14px bold Gothic`\n * `18px Gothic`\n * `24px Gothic`\n * `24px bold Gothic`\n * `28px Gothic`\n * `28px bold Gothic`\n * `30px bolder Bitham`\n * `42px bold Bitham`\n * `42px light Bitham`\n * `42px Bitham-numeric`\n * `34px Bitham-numeric`\n * `21px Roboto`\n * `49px Roboto-subset`\n * `28px bold Droid-serif`\n * `20px bold Leco-numbers`\n * `26px bold Leco-numbers-am-pm`\n * `32px bold numbers Leco-numbers`\n * `36px bold numbers Leco-numbers`\n * `38px bold numbers Leco-numbers`\n * `42px bold numbers Leco-numbers`\n * `28px light numbers Leco-numbers`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 104, + "column": 2 + }, + "end": { + "line": 140, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 141, + "column": 2 + }, + "end": { + "line": 141, + "column": 6 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "font", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "font", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.font" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Specifies the current text alignment being used when drawing\n text. Beware that the alignment is based on the x-axis coordinate value of \n the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 7, + "offset": 145 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "link", + "url": "#fillText", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "CanvasRenderingContext2D.fillText" + } + ], + "position": { + "start": { + "line": 3, + "column": 7, + "offset": 145 + }, + "end": { + "line": 3, + "column": 58, + "offset": 196 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " method.", + "position": { + "start": { + "line": 3, + "column": 58, + "offset": 196 + }, + "end": { + "line": 3, + "column": 66, + "offset": 204 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 66, + "offset": 204 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.textAlign = 'center';", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 206 + }, + "end": { + "line": 5, + "column": 28, + "offset": 233 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 206 + }, + "end": { + "line": 5, + "column": 28, + "offset": 233 + }, + "indent": [] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Options", + "position": { + "start": { + "line": 7, + "column": 8, + "offset": 242 + }, + "end": { + "line": 7, + "column": 15, + "offset": 249 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1, + "offset": 235 + }, + "end": { + "line": 7, + "column": 15, + "offset": 249 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 251 + }, + "end": { + "line": 9, + "column": 19, + "offset": 269 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 251 + }, + "end": { + "line": 9, + "column": 19, + "offset": 269 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "left", + "position": { + "start": { + "line": 11, + "column": 5, + "offset": 275 + }, + "end": { + "line": 11, + "column": 11, + "offset": 281 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The text is left-aligned", + "position": { + "start": { + "line": 11, + "column": 11, + "offset": 281 + }, + "end": { + "line": 11, + "column": 38, + "offset": 308 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 5, + "offset": 275 + }, + "end": { + "line": 11, + "column": 38, + "offset": 308 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 271 + }, + "end": { + "line": 11, + "column": 38, + "offset": 308 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "right", + "position": { + "start": { + "line": 12, + "column": 5, + "offset": 313 + }, + "end": { + "line": 12, + "column": 12, + "offset": 320 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The text is right-aligned", + "position": { + "start": { + "line": 12, + "column": 12, + "offset": 320 + }, + "end": { + "line": 12, + "column": 40, + "offset": 348 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 12, + "column": 5, + "offset": 313 + }, + "end": { + "line": 12, + "column": 40, + "offset": 348 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 309 + }, + "end": { + "line": 12, + "column": 40, + "offset": 348 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "center", + "position": { + "start": { + "line": 13, + "column": 5, + "offset": 353 + }, + "end": { + "line": 13, + "column": 13, + "offset": 361 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The text is center-aligned", + "position": { + "start": { + "line": 13, + "column": 13, + "offset": 361 + }, + "end": { + "line": 13, + "column": 42, + "offset": 390 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 5, + "offset": 353 + }, + "end": { + "line": 13, + "column": 42, + "offset": 390 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1, + "offset": 349 + }, + "end": { + "line": 13, + "column": 42, + "offset": 390 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "start", + "position": { + "start": { + "line": 14, + "column": 5, + "offset": 395 + }, + "end": { + "line": 14, + "column": 12, + "offset": 402 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " (default) - The text is aligned left, unless using a \n right-to-left language. Currently only left-to-right is supported.", + "position": { + "start": { + "line": 14, + "column": 12, + "offset": 402 + }, + "end": { + "line": 15, + "column": 73, + "offset": 529 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 5, + "offset": 395 + }, + "end": { + "line": 15, + "column": 73, + "offset": 529 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 1, + "offset": 391 + }, + "end": { + "line": 15, + "column": 73, + "offset": 529 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "end", + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 534 + }, + "end": { + "line": 16, + "column": 10, + "offset": 539 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The text is aligned right, unless using a right-to-left \n language. Currently only left-to-right is supported.", + "position": { + "start": { + "line": 16, + "column": 10, + "offset": 539 + }, + "end": { + "line": 17, + "column": 58, + "offset": 656 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 534 + }, + "end": { + "line": 17, + "column": 58, + "offset": 656 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 530 + }, + "end": { + "line": 17, + "column": 58, + "offset": 656 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 271 + }, + "end": { + "line": 17, + "column": 58, + "offset": 656 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 17, + "column": 58, + "offset": 656 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Specifies the current text alignment being used when drawing\n text. Beware that the alignment is based on the x-axis coordinate value of \n the {@link #fillText CanvasRenderingContext2D.fillText} method.\n\n`ctx.textAlign = 'center';`\n\n #### Options\n\n Possible values:\n\n * `left` - The text is left-aligned\n * `right` - The text is right-aligned\n * `center` - The text is center-aligned\n * `start` (default) - The text is aligned left, unless using a \n right-to-left language. Currently only left-to-right is supported.\n * `end` - The text is aligned right, unless using a right-to-left \n language. Currently only left-to-right is supported.", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 143, + "column": 2 + }, + "end": { + "line": 161, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 162, + "column": 2 + }, + "end": { + "line": 162, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "textAlign", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "textAlign", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.textAlign" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sets all pixels in the rectangle at (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 38, + "offset": 37 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 38, + "offset": 37 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 41, + "offset": 40 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 42, + "offset": 41 + }, + "end": { + "line": 1, + "column": 45, + "offset": 44 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") with size\n (", + "position": { + "start": { + "line": 1, + "column": 45, + "offset": 44 + }, + "end": { + "line": 2, + "column": 3, + "offset": 58 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "width", + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 58 + }, + "end": { + "line": 2, + "column": 10, + "offset": 65 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 2, + "column": 10, + "offset": 65 + }, + "end": { + "line": 2, + "column": 12, + "offset": 67 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "height", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 67 + }, + "end": { + "line": 2, + "column": 20, + "offset": 75 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") to black, erasing any previously drawn content.", + "position": { + "start": { + "line": 2, + "column": 20, + "offset": 75 + }, + "end": { + "line": 2, + "column": 69, + "offset": 124 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 69, + "offset": 124 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.clearRect(0, 0, 144, 168);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 126 + }, + "end": { + "line": 4, + "column": 33, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 126 + }, + "end": { + "line": 4, + "column": 33, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 33, + "offset": 158 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Sets all pixels in the rectangle at (`x`,`y`) with size\n (`width`, `height`) to black, erasing any previously drawn content.\n\n`ctx.clearRect(0, 0, 144, 168);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 165, + "column": 0 + }, + "end": { + "line": 175, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 176, + "column": 0 + }, + "end": { + "line": 176, + "column": 71 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "clearRect", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "clearRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.clearRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Draws a filled rectangle at (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 30, + "offset": 29 + }, + "end": { + "line": 1, + "column": 33, + "offset": 32 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 33, + "offset": 32 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 34, + "offset": 33 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") with size (", + "position": { + "start": { + "line": 1, + "column": 37, + "offset": 36 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "width", + "position": { + "start": { + "line": 1, + "column": 50, + "offset": 49 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 1, + "column": 57, + "offset": 56 + }, + "end": { + "line": 1, + "column": 59, + "offset": 58 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "height", + "position": { + "start": { + "line": 1, + "column": 59, + "offset": 58 + }, + "end": { + "line": 1, + "column": 67, + "offset": 66 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "), \n using the current fill style.", + "position": { + "start": { + "line": 1, + "column": 67, + "offset": 66 + }, + "end": { + "line": 2, + "column": 31, + "offset": 100 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 31, + "offset": 100 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.fillRect(0, 30, 144, 30);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 102 + }, + "end": { + "line": 4, + "column": 32, + "offset": 133 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 102 + }, + "end": { + "line": 4, + "column": 32, + "offset": 133 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 32, + "offset": 133 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Draws a filled rectangle at (`x`,`y`) with size (`width`, `height`), \n using the current fill style.\n\n`ctx.fillRect(0, 30, 144, 30);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 178, + "column": 0 + }, + "end": { + "line": 188, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 189, + "column": 0 + }, + "end": { + "line": 189, + "column": 70 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "fillRect", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "fillRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.fillRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Paints a rectangle at (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 24, + "offset": 23 + }, + "end": { + "line": 1, + "column": 27, + "offset": 26 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 27, + "offset": 26 + }, + "end": { + "line": 1, + "column": 28, + "offset": 27 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 28, + "offset": 27 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") with size (", + "position": { + "start": { + "line": 1, + "column": 31, + "offset": 30 + }, + "end": { + "line": 1, + "column": 44, + "offset": 43 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "width", + "position": { + "start": { + "line": 1, + "column": 44, + "offset": 43 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 1, + "column": 51, + "offset": 50 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "height", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 61, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "),\n using the current stroke style.", + "position": { + "start": { + "line": 1, + "column": 61, + "offset": 60 + }, + "end": { + "line": 2, + "column": 33, + "offset": 95 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 95 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.strokeRect(0, 30, 144, 30);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 97 + }, + "end": { + "line": 4, + "column": 34, + "offset": 130 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 97 + }, + "end": { + "line": 4, + "column": 34, + "offset": 130 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 34, + "offset": 130 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Paints a rectangle at (`x`,`y`) with size (`width`, `height`),\n using the current stroke style.\n\n`ctx.strokeRect(0, 30, 144, 30);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 191, + "column": 0 + }, + "end": { + "line": 201, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 202, + "column": 0 + }, + "end": { + "line": 202, + "column": 72 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "strokeRect", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "strokeRect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.strokeRect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Draws (fills) ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "text", + "position": { + "start": { + "line": 1, + "column": 15, + "offset": 14 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " at the given (", + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 36, + "offset": 35 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 39, + "offset": 38 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 40, + "offset": 39 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") position.", + "position": { + "start": { + "line": 1, + "column": 43, + "offset": 42 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.fillText('Hello World', 0, 30, 144);", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 55 + }, + "end": { + "line": 3, + "column": 43, + "offset": 97 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 55 + }, + "end": { + "line": 3, + "column": 43, + "offset": 97 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 43, + "offset": 97 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Draws (fills) `text` at the given (`x`,`y`) position.\n\n`ctx.fillText('Hello World', 0, 30, 144);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The text to draw", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "text" + }, + { + "title": "param", + "description": "The x-axis coordinate of the text's starting point", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the text's starting point", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "(Optional) Maximum width to draw. If specified, \n and the string is wider than the width, the font is adjusted to use a \n smaller font.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "maxWidth" + } + ], + "loc": { + "start": { + "line": 204, + "column": 0 + }, + "end": { + "line": 215, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 216, + "column": 0 + }, + "end": { + "line": 216, + "column": 71 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "text", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The text to draw", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the text's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "maxWidth", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) Maximum width to draw. If specified, \n and the string is wider than the width, the font is adjusted to use a \n smaller font.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 17, + "offset": 139 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 17, + "offset": 139 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 17, + "offset": 139 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "fillText", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "fillText", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.fillText" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns a ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 11, + "offset": 10 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#TextMetrics", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "TextMetrics" + } + ], + "position": { + "start": { + "line": 1, + "column": 11, + "offset": 10 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object containing\n information about ", + "position": { + "start": { + "line": 1, + "column": 43, + "offset": 42 + }, + "end": { + "line": 2, + "column": 23, + "offset": 83 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "text", + "position": { + "start": { + "line": 2, + "column": 23, + "offset": 83 + }, + "end": { + "line": 2, + "column": 29, + "offset": 89 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 29, + "offset": 89 + }, + "end": { + "line": 2, + "column": 30, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 30, + "offset": 90 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "var dimensions = ctx.measureText('Hello World');", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 92 + }, + "end": { + "line": 4, + "column": 51, + "offset": 142 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 92 + }, + "end": { + "line": 4, + "column": 51, + "offset": 142 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 51, + "offset": 142 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Returns a {@link #TextMetrics TextMetrics} object containing\n information about `text`.\n\n`var dimensions = ctx.measureText('Hello World');`", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A ``TextMetrics`` object with information about\n the measured text", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "TextMetrics" + } + }, + { + "title": "param", + "description": "The text to measure", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "text" + } + ], + "loc": { + "start": { + "line": 218, + "column": 0 + }, + "end": { + "line": 228, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 229, + "column": 0 + }, + "end": { + "line": 229, + "column": 58 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "TextMetrics", + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object with information about\n the measured text", + "position": { + "start": { + "line": 1, + "column": 18, + "offset": 17 + }, + "end": { + "line": 2, + "column": 19, + "offset": 66 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 19, + "offset": 66 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 19, + "offset": 66 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TextMetrics" + } + } + ], + "params": [ + { + "name": "text", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The text to measure", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 20, + "offset": 19 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "measureText", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "measureText", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.measureText" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Starts a new path by emptying the list of sub-paths. Call this\n method when you want to create a new path.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 44, + "offset": 106 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 44, + "offset": 106 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.beginPath();", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 108 + }, + "end": { + "line": 4, + "column": 19, + "offset": 126 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 108 + }, + "end": { + "line": 4, + "column": 19, + "offset": 126 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 19, + "offset": 126 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Starts a new path by emptying the list of sub-paths. Call this\n method when you want to create a new path.\n\n`ctx.beginPath();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 231, + "column": 0 + }, + "end": { + "line": 236, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 237, + "column": 0 + }, + "end": { + "line": 237, + "column": 52 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "beginPath", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "beginPath", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.beginPath" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Causes the point of the pen to move back to the start of the\n current sub-path. It tries to add a straight line (but does not\n actually draw it) from the current point to the start. If the shape has\n already been closed or has only one point, this function does nothing.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 75, + "offset": 279 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 75, + "offset": 279 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.closePath();", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 281 + }, + "end": { + "line": 6, + "column": 19, + "offset": 299 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 281 + }, + "end": { + "line": 6, + "column": 19, + "offset": 299 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 19, + "offset": 299 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Causes the point of the pen to move back to the start of the\n current sub-path. It tries to add a straight line (but does not\n actually draw it) from the current point to the start. If the shape has\n already been closed or has only one point, this function does nothing.\n\n`ctx.closePath();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 239, + "column": 0 + }, + "end": { + "line": 246, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 247, + "column": 0 + }, + "end": { + "line": 247, + "column": 52 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "closePath", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "closePath", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.closePath" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Moves the starting point of a new sub-path to the (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 52, + "offset": 51 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 55, + "offset": 54 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 56, + "offset": 55 + }, + "end": { + "line": 1, + "column": 59, + "offset": 58 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ")\n coordinates.", + "position": { + "start": { + "line": 1, + "column": 59, + "offset": 58 + }, + "end": { + "line": 2, + "column": 14, + "offset": 73 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 14, + "offset": 73 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.moveTo(10, 20);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 75 + }, + "end": { + "line": 4, + "column": 22, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 75 + }, + "end": { + "line": 4, + "column": 22, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 22, + "offset": 96 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Moves the starting point of a new sub-path to the (`x`,`y`)\n coordinates.\n\n`ctx.moveTo(10, 20);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The destination point on the x-axis", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The destination point on the y-axis", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + } + ], + "loc": { + "start": { + "line": 249, + "column": 0 + }, + "end": { + "line": 257, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 258, + "column": 0 + }, + "end": { + "line": 258, + "column": 53 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The destination point on the x-axis", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The destination point on the y-axis", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "moveTo", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "moveTo", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.moveTo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Connects the last point of the sub-path to the (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 49, + "offset": 48 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 49, + "offset": 48 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 52, + "offset": 51 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ")\n coordinates with a straight line.", + "position": { + "start": { + "line": 1, + "column": 56, + "offset": 55 + }, + "end": { + "line": 2, + "column": 35, + "offset": 91 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 91 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.lineTo(10, 20);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 93 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 93 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Connects the last point of the sub-path to the (`x`,`y`)\n coordinates with a straight line.\n\n`ctx.lineTo(10, 20);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The destination point on the x-axis", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The destination point on the y-axis", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + } + ], + "loc": { + "start": { + "line": 260, + "column": 0 + }, + "end": { + "line": 268, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 269, + "column": 0 + }, + "end": { + "line": 269, + "column": 53 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The destination point on the x-axis", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The destination point on the y-axis", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "lineTo", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "lineTo", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.lineTo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Adds an arc to the path which is centered at (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 47, + "offset": 46 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 47, + "offset": 46 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 50, + "offset": 49 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 51, + "offset": 50 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ")\n position with radius ", + "position": { + "start": { + "line": 1, + "column": 54, + "offset": 53 + }, + "end": { + "line": 2, + "column": 26, + "offset": 80 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "r", + "position": { + "start": { + "line": 2, + "column": 26, + "offset": 80 + }, + "end": { + "line": 2, + "column": 29, + "offset": 83 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " starting at ", + "position": { + "start": { + "line": 2, + "column": 29, + "offset": 83 + }, + "end": { + "line": 2, + "column": 42, + "offset": 96 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 2, + "column": 42, + "offset": 96 + }, + "end": { + "line": 2, + "column": 54, + "offset": 108 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ending at\n ", + "position": { + "start": { + "line": 2, + "column": 54, + "offset": 108 + }, + "end": { + "line": 3, + "column": 5, + "offset": 127 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "endAngle", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 127 + }, + "end": { + "line": 3, + "column": 15, + "offset": 137 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " going in the direction determined by the ", + "position": { + "start": { + "line": 3, + "column": 15, + "offset": 137 + }, + "end": { + "line": 3, + "column": 57, + "offset": 179 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "anticlockwise", + "position": { + "start": { + "line": 3, + "column": 57, + "offset": 179 + }, + "end": { + "line": 3, + "column": 72, + "offset": 194 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n parameter (defaulting to clockwise).", + "position": { + "start": { + "line": 3, + "column": 72, + "offset": 194 + }, + "end": { + "line": 4, + "column": 41, + "offset": 236 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 41, + "offset": 236 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "If ", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 238 + }, + "end": { + "line": 6, + "column": 4, + "offset": 241 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 6, + "column": 4, + "offset": 241 + }, + "end": { + "line": 6, + "column": 16, + "offset": 253 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " > ", + "position": { + "start": { + "line": 6, + "column": 16, + "offset": 253 + }, + "end": { + "line": 6, + "column": 19, + "offset": 256 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "endAngle", + "position": { + "start": { + "line": 6, + "column": 19, + "offset": 256 + }, + "end": { + "line": 6, + "column": 29, + "offset": 266 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " nothing will be drawn, and if the difference \n between ", + "position": { + "start": { + "line": 6, + "column": 29, + "offset": 266 + }, + "end": { + "line": 7, + "column": 11, + "offset": 323 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 7, + "column": 11, + "offset": 323 + }, + "end": { + "line": 7, + "column": 23, + "offset": 335 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ", + "position": { + "start": { + "line": 7, + "column": 23, + "offset": 335 + }, + "end": { + "line": 7, + "column": 28, + "offset": 340 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "endAngle", + "position": { + "start": { + "line": 7, + "column": 28, + "offset": 340 + }, + "end": { + "line": 7, + "column": 38, + "offset": 350 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " exceeds 2π, a full circle will be drawn.", + "position": { + "start": { + "line": 7, + "column": 38, + "offset": 350 + }, + "end": { + "line": 7, + "column": 79, + "offset": 391 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 238 + }, + "end": { + "line": 7, + "column": 79, + "offset": 391 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "// Draw a full circle outline
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.arc(72, 84, 40, 0, 2 * Math.PI, false);
ctx.stroke();", + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 393 + }, + "end": { + "line": 9, + "column": 146, + "offset": 538 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 393 + }, + "end": { + "line": 9, + "column": 146, + "offset": 538 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Please note this function does not work with ", + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 540 + }, + "end": { + "line": 11, + "column": 46, + "offset": 585 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": ".fill", + "position": { + "start": { + "line": 11, + "column": 46, + "offset": 585 + }, + "end": { + "line": 11, + "column": 53, + "offset": 592 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", you must use \n ", + "position": { + "start": { + "line": 11, + "column": 53, + "offset": 592 + }, + "end": { + "line": 12, + "column": 2, + "offset": 609 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#rockyFillRadial", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "CanvasRenderingContext2D.rockyFillRadial" + } + ], + "position": { + "start": { + "line": 12, + "column": 2, + "offset": 609 + }, + "end": { + "line": 12, + "column": 67, + "offset": 674 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " instead.", + "position": { + "start": { + "line": 12, + "column": 67, + "offset": 674 + }, + "end": { + "line": 12, + "column": 76, + "offset": 683 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1, + "offset": 540 + }, + "end": { + "line": 12, + "column": 76, + "offset": 683 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 12, + "column": 76, + "offset": 683 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Adds an arc to the path which is centered at (`x`,`y`)\n position with radius `r` starting at `startAngle` and ending at\n `endAngle` going in the direction determined by the `anticlockwise` \n parameter (defaulting to clockwise).\n\nIf `startAngle` > `endAngle` nothing will be drawn, and if the difference \n between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn.\n\n`// Draw a full circle outline
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.arc(72, 84, 40, 0, 2 * Math.PI, false);
ctx.stroke();`\n\nPlease note this function does not work with `.fill`, you must use \n {@link #rockyFillRadial CanvasRenderingContext2D.rockyFillRadial} instead.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the arc's center", + "lineNumber": 14, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the arc's center", + "lineNumber": 15, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The radius of the arc", + "lineNumber": 16, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "r" + }, + { + "title": "param", + "description": "The angle at which the arc starts, measured\n clockwise from the positive x axis and expressed in radians.", + "lineNumber": 17, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "startAngle" + }, + { + "title": "param", + "description": "The angle at which the arc ends, measured\n clockwise from the positive x axis and expressed in radians.", + "lineNumber": 19, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "endAngle" + }, + { + "title": "param", + "description": "(Optional) `Boolean` which, if `true`,\n causes the arc to be drawn counter-clockwise between the two angles\n (`false` by default)", + "lineNumber": 21, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Bool" + } + }, + "name": "anticlockwise" + } + ], + "loc": { + "start": { + "line": 271, + "column": 0 + }, + "end": { + "line": 295, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 296, + "column": 0 + }, + "end": { + "line": 296, + "column": 90 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 14, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the arc's center", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 15, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the arc's center", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "r", + "lineNumber": 16, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The radius of the arc", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "startAngle", + "lineNumber": 17, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The angle at which the arc starts, measured\n clockwise from the positive x axis and expressed in radians.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 108 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 108 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 108 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "endAngle", + "lineNumber": 19, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The angle at which the arc ends, measured\n clockwise from the positive x axis and expressed in radians.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 106 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 106 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 106 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "anticlockwise", + "lineNumber": 21, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 12, + "offset": 11 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Boolean", + "position": { + "start": { + "line": 1, + "column": 12, + "offset": 11 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " which, if ", + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "true", + "position": { + "start": { + "line": 1, + "column": 32, + "offset": 31 + }, + "end": { + "line": 1, + "column": 38, + "offset": 37 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",\n causes the arc to be drawn counter-clockwise between the two angles\n (", + "position": { + "start": { + "line": 1, + "column": 38, + "offset": 37 + }, + "end": { + "line": 3, + "column": 6, + "offset": 116 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "false", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 116 + }, + "end": { + "line": 3, + "column": 13, + "offset": 123 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " by default)", + "position": { + "start": { + "line": 3, + "column": 13, + "offset": 123 + }, + "end": { + "line": 3, + "column": 25, + "offset": 135 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 25, + "offset": 135 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 25, + "offset": 135 + } + } + }, + "type": { + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Bool" + } + } + } + ], + "name": "arc", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "arc", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.arc" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Creates a path for a rectangle at position (", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 45, + "offset": 44 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "x", + "position": { + "start": { + "line": 1, + "column": 45, + "offset": 44 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",", + "position": { + "start": { + "line": 1, + "column": 48, + "offset": 47 + }, + "end": { + "line": 1, + "column": 49, + "offset": 48 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "y", + "position": { + "start": { + "line": 1, + "column": 49, + "offset": 48 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") with a\n size that is determined by ", + "position": { + "start": { + "line": 1, + "column": 52, + "offset": 51 + }, + "end": { + "line": 2, + "column": 32, + "offset": 91 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "width", + "position": { + "start": { + "line": 2, + "column": 32, + "offset": 91 + }, + "end": { + "line": 2, + "column": 39, + "offset": 98 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ", + "position": { + "start": { + "line": 2, + "column": 39, + "offset": 98 + }, + "end": { + "line": 2, + "column": 44, + "offset": 103 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "height", + "position": { + "start": { + "line": 2, + "column": 44, + "offset": 103 + }, + "end": { + "line": 2, + "column": 52, + "offset": 111 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ". Those four points are\n connected by straight lines and the sub-path is marked as closed, so\n that you can fill or stroke this rectangle.", + "position": { + "start": { + "line": 2, + "column": 52, + "offset": 111 + }, + "end": { + "line": 4, + "column": 48, + "offset": 255 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 48, + "offset": 255 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.rect(0, 30, 144, 50);", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 257 + }, + "end": { + "line": 6, + "column": 28, + "offset": 284 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 257 + }, + "end": { + "line": 6, + "column": 28, + "offset": 284 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 28, + "offset": 284 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Creates a path for a rectangle at position (`x`,`y`) with a\n size that is determined by `width` and `height`. Those four points are\n connected by straight lines and the sub-path is marked as closed, so\n that you can fill or stroke this rectangle.\n\n`ctx.rect(0, 30, 144, 50);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the rectangle's starting point", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the rectangle's starting point", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The rectangle's width", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "width" + }, + { + "title": "param", + "description": "The rectangle's height", + "lineNumber": 11, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "height" + } + ], + "loc": { + "start": { + "line": 298, + "column": 0 + }, + "end": { + "line": 310, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 311, + "column": 0 + }, + "end": { + "line": 311, + "column": 66 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the rectangle's starting point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "width", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's width", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "height", + "lineNumber": 11, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The rectangle's height", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "rect", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "rect", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.rect" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Fills the current path with the current ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#fillStyle", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "fillStyle" + } + ], + "position": { + "start": { + "line": 1, + "column": 41, + "offset": 40 + }, + "end": { + "line": 1, + "column": 69, + "offset": 68 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 69, + "offset": 68 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.fill();", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 71 + }, + "end": { + "line": 3, + "column": 14, + "offset": 84 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 71 + }, + "end": { + "line": 3, + "column": 14, + "offset": 84 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 14, + "offset": 84 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Fills the current path with the current {@link #fillStyle fillStyle}.\n\n`ctx.fill();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 313, + "column": 0 + }, + "end": { + "line": 317, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 318, + "column": 0 + }, + "end": { + "line": 318, + "column": 47 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "fill", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "fill", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.fill" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Strokes the current path with the current ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#strokeStyle", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "strokeStyle" + } + ], + "position": { + "start": { + "line": 1, + "column": 43, + "offset": 42 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 1, + "column": 76, + "offset": 75 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 76, + "offset": 75 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.stroke();", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 77 + }, + "end": { + "line": 3, + "column": 16, + "offset": 92 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 77 + }, + "end": { + "line": 3, + "column": 16, + "offset": 92 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 16, + "offset": 92 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Strokes the current path with the current {@link #strokeStyle strokeStyle}.\n\n`ctx.stroke();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 320, + "column": 0 + }, + "end": { + "line": 324, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 325, + "column": 0 + }, + "end": { + "line": 325, + "column": 49 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "stroke", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "stroke", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.stroke" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Saves the entire state of the canvas by pushing the current\n state onto a stack.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 81 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 81 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.save();", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 83 + }, + "end": { + "line": 4, + "column": 14, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 83 + }, + "end": { + "line": 4, + "column": 14, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 14, + "offset": 96 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Saves the entire state of the canvas by pushing the current\n state onto a stack.\n\n`ctx.save();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 327, + "column": 0 + }, + "end": { + "line": 332, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 333, + "column": 0 + }, + "end": { + "line": 333, + "column": 47 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "save", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "save", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.save" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Restores the most recently saved canvas state by popping the\n top entry in the drawing state stack. If there is no saved state, this\n method does nothing.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 23, + "offset": 156 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 23, + "offset": 156 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ctx.restore();", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 158 + }, + "end": { + "line": 5, + "column": 17, + "offset": 174 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 158 + }, + "end": { + "line": 5, + "column": 17, + "offset": 174 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 17, + "offset": 174 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Restores the most recently saved canvas state by popping the\n top entry in the drawing state stack. If there is no saved state, this\n method does nothing.\n\n`ctx.restore();`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 335, + "column": 0 + }, + "end": { + "line": 341, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 342, + "column": 0 + }, + "end": { + "line": 342, + "column": 50 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "name": "restore", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "restore", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.restore" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Fills a circle clockwise between startAngle and endAngle where\n 0 is the start of the circle beginning at the 3 o'clock position on a\n watchface.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 14, + "offset": 149 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 14, + "offset": 149 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " If ", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 151 + }, + "end": { + "line": 5, + "column": 6, + "offset": 156 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 156 + }, + "end": { + "line": 5, + "column": 18, + "offset": 168 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " > ", + "position": { + "start": { + "line": 5, + "column": 18, + "offset": 168 + }, + "end": { + "line": 5, + "column": 21, + "offset": 171 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "endAngle", + "position": { + "start": { + "line": 5, + "column": 21, + "offset": 171 + }, + "end": { + "line": 5, + "column": 31, + "offset": 181 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " nothing will be drawn, and if the difference\n between ", + "position": { + "start": { + "line": 5, + "column": 31, + "offset": 181 + }, + "end": { + "line": 6, + "column": 12, + "offset": 238 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 6, + "column": 12, + "offset": 238 + }, + "end": { + "line": 6, + "column": 24, + "offset": 250 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ", + "position": { + "start": { + "line": 6, + "column": 24, + "offset": 250 + }, + "end": { + "line": 6, + "column": 29, + "offset": 255 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "endAngle", + "position": { + "start": { + "line": 6, + "column": 29, + "offset": 255 + }, + "end": { + "line": 6, + "column": 39, + "offset": 265 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " exceeds 2π, a full circle will be drawn.", + "position": { + "start": { + "line": 6, + "column": 39, + "offset": 265 + }, + "end": { + "line": 6, + "column": 80, + "offset": 306 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 151 + }, + "end": { + "line": 6, + "column": 80, + "offset": 306 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "// Draw a filled circle
ctx.fillStyle = 'white';
ctx.rockyFillRadial(72, 84, 0, 30, 0, 2 * Math.PI);", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 308 + }, + "end": { + "line": 8, + "column": 109, + "offset": 416 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 308 + }, + "end": { + "line": 8, + "column": 109, + "offset": 416 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 8, + "column": 109, + "offset": 416 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Fills a circle clockwise between startAngle and endAngle where\n 0 is the start of the circle beginning at the 3 o'clock position on a\n watchface.\n\n If `startAngle` > `endAngle` nothing will be drawn, and if the difference\n between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn.\n\n`// Draw a filled circle
ctx.fillStyle = 'white';
ctx.rockyFillRadial(72, 84, 0, 30, 0, 2 * Math.PI);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The x-axis coordinate of the radial's center point", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "x" + }, + { + "title": "param", + "description": "The y-axis coordinate of the radial's center point", + "lineNumber": 11, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "y" + }, + { + "title": "param", + "description": "The inner radius of the circle. Use 0 for a full circle", + "lineNumber": 12, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "innerRadius" + }, + { + "title": "param", + "description": "The outer radius of the circle", + "lineNumber": 13, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "outerRadius" + }, + { + "title": "param", + "description": "Radial starting angle", + "lineNumber": 14, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "startAngle" + }, + { + "title": "param", + "description": "Radial finishing angle. If smaller than `startAngle` nothing is drawn", + "lineNumber": 15, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "endAngle" + } + ], + "loc": { + "start": { + "line": 344, + "column": 0 + }, + "end": { + "line": 360, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 361, + "column": 0 + }, + "end": { + "line": 361, + "column": 110 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/CanvasRenderingContext2D.js" + }, + "params": [ + { + "name": "x", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The x-axis coordinate of the radial's center point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "y", + "lineNumber": 11, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The y-axis coordinate of the radial's center point", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "innerRadius", + "lineNumber": 12, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The inner radius of the circle. Use 0 for a full circle", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "outerRadius", + "lineNumber": 13, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The outer radius of the circle", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "startAngle", + "lineNumber": 14, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Radial starting angle", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "name": "endAngle", + "lineNumber": 15, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Radial finishing angle. If smaller than ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "startAngle", + "position": { + "start": { + "line": 1, + "column": 41, + "offset": 40 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " nothing is drawn", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "rockyFillRadial", + "kind": "function", + "memberof": "CanvasRenderingContext2D", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + }, + { + "name": "rockyFillRadial", + "kind": "function", + "scope": "static" + } + ], + "namespace": "CanvasRenderingContext2D.rockyFillRadial" + } + ] + }, + "path": [ + { + "name": "CanvasRenderingContext2D", + "kind": "namespace" + } + ], + "namespace": "CanvasRenderingContext2D" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This provides an interface to the app's debugging console. ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " If you're using ", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 61 + }, + "end": { + "line": 3, + "column": 19, + "offset": 79 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "https://cloudpebble.net", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "CloudPebble" + } + ], + "position": { + "start": { + "line": 3, + "column": 19, + "offset": 79 + }, + "end": { + "line": 3, + "column": 62, + "offset": 122 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", these logs \n will appear when you press 'View Logs' after launching your application.", + "position": { + "start": { + "line": 3, + "column": 62, + "offset": 122 + }, + "end": { + "line": 4, + "column": 75, + "offset": 210 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 61 + }, + "end": { + "line": 4, + "column": 75, + "offset": 210 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " If you're using the local SDK, you can use the ", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 212 + }, + "end": { + "line": 6, + "column": 50, + "offset": 261 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "$ pebble logs", + "position": { + "start": { + "line": 6, + "column": 50, + "offset": 261 + }, + "end": { + "line": 6, + "column": 65, + "offset": 276 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " command or: ", + "position": { + "start": { + "line": 6, + "column": 65, + "offset": 276 + }, + "end": { + "line": 6, + "column": 78, + "offset": 289 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 212 + }, + "end": { + "line": 6, + "column": 78, + "offset": 289 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 291 + }, + "end": { + "line": 8, + "column": 3, + "offset": 293 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "$ pebble install --emulator basalt --logs", + "position": { + "start": { + "line": 8, + "column": 3, + "offset": 293 + }, + "end": { + "line": 8, + "column": 46, + "offset": 336 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 291 + }, + "end": { + "line": 8, + "column": 46, + "offset": 336 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " You can find out more about logging in our \n ", + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 338 + }, + "end": { + "line": 11, + "column": 3, + "offset": 386 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "/guides/debugging/debugging-with-app-logs/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "Debugging with App Logs" + } + ], + "position": { + "start": { + "line": 11, + "column": 3, + "offset": 386 + }, + "end": { + "line": 11, + "column": 77, + "offset": 460 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " guide.", + "position": { + "start": { + "line": 11, + "column": 77, + "offset": 460 + }, + "end": { + "line": 11, + "column": 84, + "offset": 467 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 338 + }, + "end": { + "line": 11, + "column": 84, + "offset": 467 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 11, + "column": 84, + "offset": 467 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "console" + }, + { + "title": "desc", + "description": "This provides an interface to the app's debugging console. \n\n If you're using {@link https://cloudpebble.net CloudPebble}, these logs \n will appear when you press 'View Logs' after launching your application.\n\n If you're using the local SDK, you can use the `$ pebble logs` command or: \n\n `$ pebble install --emulator basalt --logs`\n\n You can find out more about logging in our \n {@link /guides/debugging/debugging-with-app-logs/ Debugging with App Logs} guide.", + "lineNumber": 2 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 15, + "column": 0 + }, + "end": { + "line": 15, + "column": 27 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Console.js" + }, + "kind": "namespace", + "name": "console", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Outputs a message to the app's debugging console.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 51 + }, + "end": { + "line": 3, + "column": 3, + "offset": 53 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "console.log(rocky.watchInfo.platform);", + "position": { + "start": { + "line": 3, + "column": 3, + "offset": 53 + }, + "end": { + "line": 3, + "column": 43, + "offset": 93 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 51 + }, + "end": { + "line": 3, + "column": 43, + "offset": 93 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 43, + "offset": 93 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Outputs a message to the app's debugging console.\n\n `console.log(rocky.watchInfo.platform);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "lineNumber": 5, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + }, + "name": "obj" + } + ], + "loc": { + "start": { + "line": 17, + "column": 0 + }, + "end": { + "line": 25, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 26, + "column": 0 + }, + "end": { + "line": 26, + "column": 33 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Console.js" + }, + "params": [ + { + "name": "obj", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + } + } + }, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + } + } + ], + "name": "log", + "kind": "function", + "memberof": "console", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "console", + "kind": "namespace" + }, + { + "name": "log", + "kind": "function", + "scope": "static" + } + ], + "namespace": "console.log" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Outputs a warning message to the app's debugging console.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 58, + "offset": 57 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 58, + "offset": 57 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 59 + }, + "end": { + "line": 3, + "column": 3, + "offset": 61 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "console.warn('Something seems wrong');", + "position": { + "start": { + "line": 3, + "column": 3, + "offset": 61 + }, + "end": { + "line": 3, + "column": 43, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 59 + }, + "end": { + "line": 3, + "column": 43, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 43, + "offset": 101 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Outputs a warning message to the app's debugging console.\n\n `console.warn('Something seems wrong');`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "lineNumber": 5, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + }, + "name": "obj" + } + ], + "loc": { + "start": { + "line": 28, + "column": 0 + }, + "end": { + "line": 36, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 37, + "column": 0 + }, + "end": { + "line": 37, + "column": 34 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Console.js" + }, + "params": [ + { + "name": "obj", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + } + } + }, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + } + } + ], + "name": "warn", + "kind": "function", + "memberof": "console", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "console", + "kind": "namespace" + }, + { + "name": "warn", + "kind": "function", + "scope": "static" + } + ], + "namespace": "console.warn" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Outputs an error message to the app's debugging console.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 58 + }, + "end": { + "line": 3, + "column": 3, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "console.error(JSON.stringify(obj));", + "position": { + "start": { + "line": 3, + "column": 3, + "offset": 60 + }, + "end": { + "line": 3, + "column": 40, + "offset": 97 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 58 + }, + "end": { + "line": 3, + "column": 40, + "offset": 97 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 40, + "offset": 97 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Outputs an error message to the app's debugging console.\n\n `console.error(JSON.stringify(obj));`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "lineNumber": 5, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + }, + "name": "obj" + } + ], + "loc": { + "start": { + "line": 39, + "column": 0 + }, + "end": { + "line": 47, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 48, + "column": 0 + }, + "end": { + "line": 48, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Console.js" + }, + "params": [ + { + "name": "obj", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "One or more JavaScript objects to output. The string\n representations of each of these objects are appended together in the order\n listed and output.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 21, + "offset": 151 + } + } + }, + "type": { + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "Object" + } + } + } + ], + "name": "error", + "kind": "function", + "memberof": "console", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "console", + "kind": "namespace" + }, + { + "name": "error", + "kind": "function", + "scope": "static" + } + ], + "namespace": "console.error" + } + ] + }, + "path": [ + { + "name": "console", + "kind": "namespace" + } + ], + "namespace": "console" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Creates a JavaScript Date instance that represents a single moment in\n time. Date objects are based on a time value that is the number of\n milliseconds since 1 January, 1970 UTC.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 41, + "offset": 178 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 41, + "offset": 178 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "var d = new Date();", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 180 + }, + "end": { + "line": 5, + "column": 22, + "offset": 201 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 180 + }, + "end": { + "line": 5, + "column": 22, + "offset": 201 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "We fully implement standard JavaScript ", + "position": { + "start": { + "line": 7, + "column": 1, + "offset": 203 + }, + "end": { + "line": 7, + "column": 40, + "offset": 242 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Date", + "position": { + "start": { + "line": 7, + "column": 40, + "offset": 242 + }, + "end": { + "line": 7, + "column": 46, + "offset": 248 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " functions, such as: ", + "position": { + "start": { + "line": 7, + "column": 46, + "offset": 248 + }, + "end": { + "line": 7, + "column": 67, + "offset": 269 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "getDay()", + "position": { + "start": { + "line": 7, + "column": 67, + "offset": 269 + }, + "end": { + "line": 7, + "column": 77, + "offset": 279 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 7, + "column": 77, + "offset": 279 + }, + "end": { + "line": 7, + "column": 79, + "offset": 281 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "getHours()", + "position": { + "start": { + "line": 7, + "column": 79, + "offset": 281 + }, + "end": { + "line": 7, + "column": 91, + "offset": 293 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " etc.", + "position": { + "start": { + "line": 7, + "column": 91, + "offset": 293 + }, + "end": { + "line": 7, + "column": 96, + "offset": 298 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1, + "offset": 203 + }, + "end": { + "line": 7, + "column": 96, + "offset": 298 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "For full ", + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 300 + }, + "end": { + "line": 9, + "column": 10, + "offset": 309 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Date", + "position": { + "start": { + "line": 9, + "column": 10, + "offset": 309 + }, + "end": { + "line": 9, + "column": 16, + "offset": 315 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " documentation see:\n ", + "position": { + "start": { + "line": 9, + "column": 16, + "offset": 315 + }, + "end": { + "line": 10, + "column": 2, + "offset": 336 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date" + } + ], + "position": { + "start": { + "line": 10, + "column": 2, + "offset": 336 + }, + "end": { + "line": 10, + "column": 92, + "offset": 426 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1, + "offset": 300 + }, + "end": { + "line": 10, + "column": 92, + "offset": 426 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "Locale Date Methods", + "position": { + "start": { + "line": 12, + "column": 5, + "offset": 432 + }, + "end": { + "line": 12, + "column": 24, + "offset": 451 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 12, + "column": 1, + "offset": 428 + }, + "end": { + "line": 12, + "column": 24, + "offset": 451 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The locale date methods (", + "position": { + "start": { + "line": 14, + "column": 1, + "offset": 453 + }, + "end": { + "line": 14, + "column": 26, + "offset": 478 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#toLocaleString", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "toLocaleString" + } + ], + "position": { + "start": { + "line": 14, + "column": 26, + "offset": 478 + }, + "end": { + "line": 14, + "column": 64, + "offset": 516 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", \n ", + "position": { + "start": { + "line": 14, + "column": 64, + "offset": 516 + }, + "end": { + "line": 15, + "column": 2, + "offset": 520 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#toLocaleTimeString", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "toLocaleTimeString" + } + ], + "position": { + "start": { + "line": 15, + "column": 2, + "offset": 520 + }, + "end": { + "line": 15, + "column": 48, + "offset": 566 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and \n ", + "position": { + "start": { + "line": 15, + "column": 48, + "offset": 566 + }, + "end": { + "line": 16, + "column": 2, + "offset": 573 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#toLocaleDateString", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "toLocaleDateString" + } + ], + "position": { + "start": { + "line": 16, + "column": 2, + "offset": 573 + }, + "end": { + "line": 16, + "column": 48, + "offset": 619 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ") are currently limited in \n their initial implementation.", + "position": { + "start": { + "line": 16, + "column": 48, + "offset": 619 + }, + "end": { + "line": 17, + "column": 31, + "offset": 678 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 1, + "offset": 453 + }, + "end": { + "line": 17, + "column": 31, + "offset": 678 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Available ", + "position": { + "start": { + "line": 19, + "column": 1, + "offset": 680 + }, + "end": { + "line": 19, + "column": 11, + "offset": 690 + }, + "indent": [] + } + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "options", + "position": { + "start": { + "line": 19, + "column": 13, + "offset": 692 + }, + "end": { + "line": 19, + "column": 20, + "offset": 699 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 11, + "offset": 690 + }, + "end": { + "line": 19, + "column": 22, + "offset": 701 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ":", + "position": { + "start": { + "line": 19, + "column": 22, + "offset": 701 + }, + "end": { + "line": 19, + "column": 23, + "offset": 702 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1, + "offset": 680 + }, + "end": { + "line": 19, + "column": 23, + "offset": 702 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "hour12", + "position": { + "start": { + "line": 21, + "column": 3, + "offset": 706 + }, + "end": { + "line": 21, + "column": 9, + "offset": 712 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1, + "offset": 704 + }, + "end": { + "line": 21, + "column": 11, + "offset": 714 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1, + "offset": 704 + }, + "end": { + "line": 21, + "column": 11, + "offset": 714 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Use 12-hour time (as opposed to 24-hour time). Possible values are ", + "position": { + "start": { + "line": 23, + "column": 1, + "offset": 716 + }, + "end": { + "line": 23, + "column": 68, + "offset": 783 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "true", + "position": { + "start": { + "line": 23, + "column": 68, + "offset": 783 + }, + "end": { + "line": 23, + "column": 74, + "offset": 789 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ", + "position": { + "start": { + "line": 23, + "column": 74, + "offset": 789 + }, + "end": { + "line": 23, + "column": 79, + "offset": 794 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "false", + "position": { + "start": { + "line": 23, + "column": 79, + "offset": 794 + }, + "end": { + "line": 23, + "column": 86, + "offset": 801 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "; the default is locale dependent (Pebble setting).", + "position": { + "start": { + "line": 23, + "column": 86, + "offset": 801 + }, + "end": { + "line": 23, + "column": 137, + "offset": 852 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1, + "offset": 716 + }, + "end": { + "line": 23, + "column": 137, + "offset": 852 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "weekday", + "position": { + "start": { + "line": 25, + "column": 3, + "offset": 856 + }, + "end": { + "line": 25, + "column": 10, + "offset": 863 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 1, + "offset": 854 + }, + "end": { + "line": 25, + "column": 12, + "offset": 865 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 25, + "column": 1, + "offset": 854 + }, + "end": { + "line": 25, + "column": 12, + "offset": 865 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the weekday. Possible values are \"narrow\", \"short\", \"long\".", + "position": { + "start": { + "line": 27, + "column": 1, + "offset": 867 + }, + "end": { + "line": 27, + "column": 82, + "offset": 948 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 27, + "column": 1, + "offset": 867 + }, + "end": { + "line": 27, + "column": 82, + "offset": 948 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "year", + "position": { + "start": { + "line": 29, + "column": 3, + "offset": 952 + }, + "end": { + "line": 29, + "column": 7, + "offset": 956 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 1, + "offset": 950 + }, + "end": { + "line": 29, + "column": 9, + "offset": 958 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 1, + "offset": 950 + }, + "end": { + "line": 29, + "column": 9, + "offset": 958 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the year. Possible values are \"numeric\", \"2-digit\".", + "position": { + "start": { + "line": 31, + "column": 1, + "offset": 960 + }, + "end": { + "line": 31, + "column": 74, + "offset": 1033 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 31, + "column": 1, + "offset": 960 + }, + "end": { + "line": 31, + "column": 74, + "offset": 1033 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "month", + "position": { + "start": { + "line": 33, + "column": 3, + "offset": 1037 + }, + "end": { + "line": 33, + "column": 8, + "offset": 1042 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 33, + "column": 1, + "offset": 1035 + }, + "end": { + "line": 33, + "column": 10, + "offset": 1044 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 33, + "column": 1, + "offset": 1035 + }, + "end": { + "line": 33, + "column": 10, + "offset": 1044 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the month. Possible values are \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\".", + "position": { + "start": { + "line": 35, + "column": 1, + "offset": 1046 + }, + "end": { + "line": 35, + "column": 102, + "offset": 1147 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 35, + "column": 1, + "offset": 1046 + }, + "end": { + "line": 35, + "column": 102, + "offset": 1147 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "day", + "position": { + "start": { + "line": 37, + "column": 3, + "offset": 1151 + }, + "end": { + "line": 37, + "column": 6, + "offset": 1154 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 37, + "column": 1, + "offset": 1149 + }, + "end": { + "line": 37, + "column": 8, + "offset": 1156 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 37, + "column": 1, + "offset": 1149 + }, + "end": { + "line": 37, + "column": 8, + "offset": 1156 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the day. Possible values are \"numeric\", \"2-digit\".", + "position": { + "start": { + "line": 39, + "column": 1, + "offset": 1158 + }, + "end": { + "line": 39, + "column": 73, + "offset": 1230 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 39, + "column": 1, + "offset": 1158 + }, + "end": { + "line": 39, + "column": 73, + "offset": 1230 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "hour", + "position": { + "start": { + "line": 41, + "column": 3, + "offset": 1234 + }, + "end": { + "line": 41, + "column": 7, + "offset": 1238 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 41, + "column": 1, + "offset": 1232 + }, + "end": { + "line": 41, + "column": 9, + "offset": 1240 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 41, + "column": 1, + "offset": 1232 + }, + "end": { + "line": 41, + "column": 9, + "offset": 1240 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the hour. Possible values are \"numeric\", \"2-digit\".", + "position": { + "start": { + "line": 43, + "column": 1, + "offset": 1242 + }, + "end": { + "line": 43, + "column": 74, + "offset": 1315 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 43, + "column": 1, + "offset": 1242 + }, + "end": { + "line": 43, + "column": 74, + "offset": 1315 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "minute", + "position": { + "start": { + "line": 45, + "column": 3, + "offset": 1319 + }, + "end": { + "line": 45, + "column": 9, + "offset": 1325 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 45, + "column": 1, + "offset": 1317 + }, + "end": { + "line": 45, + "column": 11, + "offset": 1327 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 45, + "column": 1, + "offset": 1317 + }, + "end": { + "line": 45, + "column": 11, + "offset": 1327 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the minute. Possible values are \"numeric\", \"2-digit\".", + "position": { + "start": { + "line": 47, + "column": 1, + "offset": 1329 + }, + "end": { + "line": 47, + "column": 76, + "offset": 1404 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 47, + "column": 1, + "offset": 1329 + }, + "end": { + "line": 47, + "column": 76, + "offset": 1404 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "second", + "position": { + "start": { + "line": 49, + "column": 3, + "offset": 1408 + }, + "end": { + "line": 49, + "column": 9, + "offset": 1414 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 49, + "column": 1, + "offset": 1406 + }, + "end": { + "line": 49, + "column": 11, + "offset": 1416 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 49, + "column": 1, + "offset": 1406 + }, + "end": { + "line": 49, + "column": 11, + "offset": 1416 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The representation of the second. Possible values are \"numeric\", \"2-digit\".", + "position": { + "start": { + "line": 51, + "column": 1, + "offset": 1418 + }, + "end": { + "line": 51, + "column": 76, + "offset": 1493 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 51, + "column": 1, + "offset": 1418 + }, + "end": { + "line": 51, + "column": 76, + "offset": 1493 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Please note that locale based date and time functions have the following\n limitations at this time:", + "position": { + "start": { + "line": 54, + "column": 1, + "offset": 1496 + }, + "end": { + "line": 55, + "column": 27, + "offset": 1595 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 54, + "column": 1, + "offset": 1496 + }, + "end": { + "line": 55, + "column": 27, + "offset": 1595 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": true, + "children": [ + { + "type": "listItem", + "loose": true, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "You cannot manually specify a locale, it's automatically based upon the \ncurrent device settings. Locale is optional, or you can specify ", + "position": { + "start": { + "line": 57, + "column": 5, + "offset": 1601 + }, + "end": { + "line": 58, + "column": 66, + "offset": 1739 + }, + "indent": [ + 2 + ] + } + }, + { + "type": "inlineCode", + "value": "undefined", + "position": { + "start": { + "line": 58, + "column": 66, + "offset": 1739 + }, + "end": { + "line": 58, + "column": 77, + "offset": 1750 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 58, + "column": 77, + "offset": 1750 + }, + "end": { + "line": 58, + "column": 78, + "offset": 1751 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 57, + "column": 5, + "offset": 1601 + }, + "end": { + "line": 58, + "column": 78, + "offset": 1751 + }, + "indent": [ + 2 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "console.log(d.toLocaleDateString());", + "position": { + "start": { + "line": 60, + "column": 2, + "offset": 1754 + }, + "end": { + "line": 60, + "column": 40, + "offset": 1792 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 60, + "column": 2, + "offset": 1754 + }, + "end": { + "line": 60, + "column": 40, + "offset": 1792 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 57, + "column": 1, + "offset": 1597 + }, + "end": { + "line": 61, + "column": 1, + "offset": 1793 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": true, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Only a single date/time value can be requested in each method call. Do \nNOT request multiple options. e.g. ", + "position": { + "start": { + "line": 62, + "column": 5, + "offset": 1798 + }, + "end": { + "line": 63, + "column": 37, + "offset": 1906 + }, + "indent": [ + 2 + ] + } + }, + { + "type": "inlineCode", + "value": "{hour: 'numeric', minute: '2-digit'}", + "position": { + "start": { + "line": 63, + "column": 37, + "offset": 1906 + }, + "end": { + "line": 63, + "column": 75, + "offset": 1944 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 62, + "column": 5, + "offset": 1798 + }, + "end": { + "line": 63, + "column": 75, + "offset": 1944 + }, + "indent": [ + 2 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "console.log(d.toLocaleTimeString(undefined, {hour: 'numeric'}));", + "position": { + "start": { + "line": 65, + "column": 2, + "offset": 1947 + }, + "end": { + "line": 65, + "column": 68, + "offset": 2013 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 65, + "column": 2, + "offset": 1947 + }, + "end": { + "line": 65, + "column": 68, + "offset": 2013 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 62, + "column": 1, + "offset": 1794 + }, + "end": { + "line": 65, + "column": 68, + "offset": 2013 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 57, + "column": 1, + "offset": 1597 + }, + "end": { + "line": 65, + "column": 68, + "offset": 2013 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 65, + "column": 68, + "offset": 2013 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "Date" + }, + { + "title": "desc", + "description": "Creates a JavaScript Date instance that represents a single moment in\n time. Date objects are based on a time value that is the number of\n milliseconds since 1 January, 1970 UTC.\n\n`var d = new Date();`\n\nWe fully implement standard JavaScript `Date` functions, such as: `getDay()`, `getHours()` etc.\n\nFor full `Date` documentation see:\n {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date}\n\n### Locale Date Methods\n\nThe locale date methods ({@link #toLocaleString toLocaleString}, \n {@link #toLocaleTimeString toLocaleTimeString} and \n {@link #toLocaleDateString toLocaleDateString}) are currently limited in \n their initial implementation.\n\nAvailable **options**:\n\n**hour12**\n\nUse 12-hour time (as opposed to 24-hour time). Possible values are `true` and `false`; the default is locale dependent (Pebble setting).\n\n**weekday**\n\nThe representation of the weekday. Possible values are \"narrow\", \"short\", \"long\".\n\n**year**\n\nThe representation of the year. Possible values are \"numeric\", \"2-digit\".\n\n**month**\n\nThe representation of the month. Possible values are \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\".\n\n**day**\n\nThe representation of the day. Possible values are \"numeric\", \"2-digit\".\n\n**hour**\n\nThe representation of the hour. Possible values are \"numeric\", \"2-digit\".\n\n**minute**\n\nThe representation of the minute. Possible values are \"numeric\", \"2-digit\".\n\n**second**\n\nThe representation of the second. Possible values are \"numeric\", \"2-digit\".\n\n\nPlease note that locale based date and time functions have the following\n limitations at this time:\n\n * You cannot manually specify a locale, it's automatically based upon the \n current device settings. Locale is optional, or you can specify `undefined`.\n\n `console.log(d.toLocaleDateString());`\n\n * Only a single date/time value can be requested in each method call. Do \n NOT request multiple options. e.g. `{hour: 'numeric', minute: '2-digit'}`\n\n `console.log(d.toLocaleTimeString(undefined, {hour: 'numeric'}));`", + "lineNumber": 3 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 69, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 70, + "column": 0 + }, + "end": { + "line": 70, + "column": 24 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Date.js" + }, + "kind": "namespace", + "name": "Date", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This method returns a string with a language sensitive representation\n of this date object.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 91 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 91 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "d.toLocaleString();", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 93 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 93 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 22, + "offset": 114 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "This method returns a string with a language sensitive representation\n of this date object.\n\n`d.toLocaleString();`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "(Optional) The name of the locale.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "locale" + }, + { + "title": "param", + "description": "(Optional) Only a single option is currently supported.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "options" + } + ], + "loc": { + "start": { + "line": 72, + "column": 0 + }, + "end": { + "line": 80, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 81, + "column": 0 + }, + "end": { + "line": 81, + "column": 52 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Date.js" + }, + "params": [ + { + "name": "locale", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) The name of the locale.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "options", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) Only a single option is currently supported.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "name": "toLocaleString", + "kind": "function", + "memberof": "Date", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Date", + "kind": "namespace" + }, + { + "name": "toLocaleString", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Date.toLocaleString" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This method returns a string with a language sensitive representation\nof the date portion of this date object.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 110 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 110 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "d.toLocaleTimeString(undefined, {hour: 'numeric'});", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 112 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 112 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "This method returns a string with a language sensitive representation\nof the date portion of this date object.\n\n`d.toLocaleTimeString(undefined, {hour: 'numeric'});`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "(Optional) The name of the locale.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "locale" + }, + { + "title": "param", + "description": "(Optional) Only a single option is currently supported.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "options" + } + ], + "loc": { + "start": { + "line": 83, + "column": 0 + }, + "end": { + "line": 91, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 92, + "column": 0 + }, + "end": { + "line": 92, + "column": 56 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Date.js" + }, + "params": [ + { + "name": "locale", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) The name of the locale.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "options", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) Only a single option is currently supported.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "name": "toLocaleTimeString", + "kind": "function", + "memberof": "Date", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Date", + "kind": "namespace" + }, + { + "name": "toLocaleTimeString", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Date.toLocaleTimeString" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This method returns a string with a language sensitive representation\nof the time portion of this date object.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 110 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 110 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "d.toLocaleDateString(undefined, {weekday: 'long'});", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 112 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 112 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 54, + "offset": 165 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "This method returns a string with a language sensitive representation\nof the time portion of this date object.\n\n`d.toLocaleDateString(undefined, {weekday: 'long'});`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "(Optional) The name of the locale.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "locale" + }, + { + "title": "param", + "description": "(Optional) Only a single option is currently supported.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "options" + } + ], + "loc": { + "start": { + "line": 94, + "column": 0 + }, + "end": { + "line": 102, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 103, + "column": 0 + }, + "end": { + "line": 103, + "column": 41 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/Date.js" + }, + "params": [ + { + "name": "locale", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) The name of the locale.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 35, + "offset": 34 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "options", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "(Optional) Only a single option is currently supported.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "name": "toLocaleDateString", + "kind": "function", + "memberof": "Date", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Date", + "kind": "namespace" + }, + { + "name": "toLocaleDateString", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Date.toLocaleDateString" + } + ] + }, + "path": [ + { + "name": "Date", + "kind": "namespace" + } + ], + "namespace": "Date" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Calls a function after a specified delay.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "var timeoutId = setTimeout(function(...){}, 10000);", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 43 + }, + "end": { + "line": 3, + "column": 54, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 43 + }, + "end": { + "line": 3, + "column": 54, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 96 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Calls a function after a specified delay.\n\n`var timeoutId = setTimeout(function(...){}, 10000);`", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "timeoutId - The ID of the timeout", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "The function to execute", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "fct" + }, + { + "title": "param", + "description": "The delay (in ms)", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "delay" + } + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 38 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/global.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "timeoutId - The ID of the timeout", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 34, + "offset": 33 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "fct", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The function to execute", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "delay", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The delay (in ms)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "setTimeout", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "setTimeout", + "kind": "function" + } + ], + "namespace": "setTimeout" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Clears the delay set by ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 25, + "offset": 24 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "setTimeout", + "position": { + "start": { + "line": 1, + "column": 25, + "offset": 24 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 39, + "offset": 38 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "clearTimeout(timeoutId);", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 41 + }, + "end": { + "line": 3, + "column": 27, + "offset": 67 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 41 + }, + "end": { + "line": 3, + "column": 27, + "offset": 67 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 27, + "offset": 67 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Clears the delay set by ``setTimeout``.\n\n`clearTimeout(timeoutId);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The ID of the timeout you wish to clear.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "timeoutId" + } + ], + "loc": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 20, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 21, + "column": 0 + }, + "end": { + "line": 21, + "column": 39 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/global.js" + }, + "params": [ + { + "name": "timeoutId", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The ID of the timeout you wish to clear.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "clearTimeout", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "clearTimeout", + "kind": "function" + } + ], + "namespace": "clearTimeout" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Repeatedly calls a function, with a fixed time delay between\n each call.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 12, + "offset": 72 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 12, + "offset": 72 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "var intervalId = setInterval(function(...){}, 10000);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 74 + }, + "end": { + "line": 4, + "column": 56, + "offset": 129 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 74 + }, + "end": { + "line": 4, + "column": 56, + "offset": 129 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 56, + "offset": 129 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Repeatedly calls a function, with a fixed time delay between\n each call.\n\n`var intervalId = setInterval(function(...){}, 10000);`", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "intervalId - The ID of the interval", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "The function to execute", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "fct" + }, + { + "title": "param", + "description": "The delay (in ms)", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "delay" + } + ], + "loc": { + "start": { + "line": 23, + "column": 0 + }, + "end": { + "line": 32, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 33, + "column": 0 + }, + "end": { + "line": 33, + "column": 39 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/global.js" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "intervalId - The ID of the interval", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "fct", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The function to execute", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 24, + "offset": 23 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "delay", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The delay (in ms)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "setInterval", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "setInterval", + "kind": "function" + } + ], + "namespace": "setInterval" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Clears the interval set by ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 28, + "offset": 27 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "setInterval", + "position": { + "start": { + "line": 1, + "column": 28, + "offset": 27 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 43, + "offset": 42 + }, + "end": { + "line": 1, + "column": 44, + "offset": 43 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 44, + "offset": 43 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "clearInterval(intervalId);", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 45 + }, + "end": { + "line": 3, + "column": 29, + "offset": 73 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 45 + }, + "end": { + "line": 3, + "column": 29, + "offset": 73 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 29, + "offset": 73 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Clears the interval set by ``setInterval``.\n\n`clearInterval(intervalId);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The ID of the interval you wish to clear", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Number" + }, + "name": "intervalId" + } + ], + "loc": { + "start": { + "line": 35, + "column": 0 + }, + "end": { + "line": 41, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 42, + "column": 0 + }, + "end": { + "line": 42, + "column": 41 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/global.js" + }, + "params": [ + { + "name": "intervalId", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The ID of the interval you wish to clear", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 41, + "offset": 40 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "name": "clearInterval", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "clearInterval", + "kind": "function" + } + ], + "namespace": "clearInterval" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides an interface for interacting with application context and\n events. Developers can access the Rocky object with the following line of\n code:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 9, + "offset": 152 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 9, + "offset": 152 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 154 + }, + "end": { + "line": 5, + "column": 4, + "offset": 157 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "var rocky = require('rocky');", + "position": { + "start": { + "line": 5, + "column": 4, + "offset": 157 + }, + "end": { + "line": 5, + "column": 35, + "offset": 188 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 154 + }, + "end": { + "line": 5, + "column": 35, + "offset": 188 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 35, + "offset": 188 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "rocky" + }, + { + "title": "desc", + "description": "Provides an interface for interacting with application context and\n events. Developers can access the Rocky object with the following line of\n code:\n\n `var rocky = require('rocky');`", + "lineNumber": 3 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 138, + "column": 2 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "namespace", + "name": "rocky", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 71, + "offset": 70 + }, + "end": { + "line": 2, + "column": 4, + "offset": 74 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 74 + }, + "end": { + "line": 2, + "column": 21, + "offset": 91 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 91 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 92 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyPostMessageErrorCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessageerror`\n {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.\n * `data` - The data failed to send within the message.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 72, + "column": 2 + }, + "end": { + "line": 80, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyPostMessageErrorCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 11, + "offset": 112 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The data failed to send within the message.", + "position": { + "start": { + "line": 3, + "column": 11, + "offset": 112 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 102 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 57, + "offset": 158 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyPostMessageErrorCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyPostMessageErrorCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessageconnected", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 2, + "column": 4, + "offset": 78 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 78 + }, + "end": { + "line": 2, + "column": 21, + "offset": 95 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 95 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 96 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyPostMessageConnectedCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessageconnected`\n {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 82, + "column": 2 + }, + "end": { + "line": 89, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyPostMessageConnectedCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyPostMessageConnectedCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyPostMessageConnectedCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides information about the currently connected Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 70, + "offset": 69 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "WatchInfo" + }, + { + "title": "desc", + "description": "Provides information about the currently connected Pebble smartwatch.", + "lineNumber": 2 + }, + { + "title": "property", + "description": "The name of the Pebble model. (e.g. pebble_time_round_silver_20mm)", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "model" + }, + { + "title": "property", + "description": "The name of the Pebble platform. (e.g. basalt)", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "platform" + }, + { + "title": "property", + "description": "Not available yet.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "language" + }, + { + "title": "property", + "description": "An object with the following fields:\n * `major` - The major version of the smartwatch's firmware.\n * `minor` - The minor version of the smartwatch's firmware.\n * `patch` - The patch version of the smartwatch's firmware.\n * `suffix` - The suffix of the smartwatch's firmware. (e.g. beta3)", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "firmware" + } + ], + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 24, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "WatchInfo", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "properties": [ + { + "name": "model", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The name of the Pebble model. (e.g. pebble_time_round_silver_20mm)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 67, + "offset": 66 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 67, + "offset": 66 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 67, + "offset": 66 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "platform", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The name of the Pebble platform. (e.g. basalt)", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 47, + "offset": 46 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 47, + "offset": 46 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 47, + "offset": 46 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "language", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Not available yet.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 19, + "offset": 18 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "firmware", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object with the following fields:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "major", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 41 + }, + "end": { + "line": 2, + "column": 12, + "offset": 48 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The major version of the smartwatch's firmware.", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 48 + }, + "end": { + "line": 2, + "column": 62, + "offset": 98 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 41 + }, + "end": { + "line": 2, + "column": 62, + "offset": 98 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 37 + }, + "end": { + "line": 2, + "column": 62, + "offset": 98 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "minor", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 103 + }, + "end": { + "line": 3, + "column": 12, + "offset": 110 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The minor version of the smartwatch's firmware.", + "position": { + "start": { + "line": 3, + "column": 12, + "offset": 110 + }, + "end": { + "line": 3, + "column": 62, + "offset": 160 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 103 + }, + "end": { + "line": 3, + "column": 62, + "offset": 160 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 99 + }, + "end": { + "line": 3, + "column": 62, + "offset": 160 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "patch", + "position": { + "start": { + "line": 4, + "column": 5, + "offset": 165 + }, + "end": { + "line": 4, + "column": 12, + "offset": 172 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The patch version of the smartwatch's firmware.", + "position": { + "start": { + "line": 4, + "column": 12, + "offset": 172 + }, + "end": { + "line": 4, + "column": 62, + "offset": 222 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 5, + "offset": 165 + }, + "end": { + "line": 4, + "column": 62, + "offset": 222 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 161 + }, + "end": { + "line": 4, + "column": 62, + "offset": 222 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "suffix", + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 227 + }, + "end": { + "line": 5, + "column": 13, + "offset": 235 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The suffix of the smartwatch's firmware. (e.g. beta3)", + "position": { + "start": { + "line": 5, + "column": 13, + "offset": 235 + }, + "end": { + "line": 5, + "column": 69, + "offset": 291 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 227 + }, + "end": { + "line": 5, + "column": 69, + "offset": 291 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 223 + }, + "end": { + "line": 5, + "column": 69, + "offset": 291 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 37 + }, + "end": { + "line": 5, + "column": 69, + "offset": 291 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 69, + "offset": 291 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "WatchInfo", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.WatchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#WatchInfo", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "WatchInfo" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object containing information about the\n connected Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 31, + "offset": 30 + }, + "end": { + "line": 2, + "column": 31, + "offset": 101 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 31, + "offset": 101 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "console.log(rocky.watchInfo.model);
> pebble_2_hr_lime", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 103 + }, + "end": { + "line": 4, + "column": 63, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 103 + }, + "end": { + "line": 4, + "column": 63, + "offset": 165 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 63, + "offset": 165 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "A {@link #WatchInfo WatchInfo} object containing information about the\n connected Pebble smartwatch.\n\n`console.log(rocky.watchInfo.model);
> pebble_2_hr_lime`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 122, + "column": 2 + }, + "end": { + "line": 128, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "name": "watchInfo", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "watchInfo", + "scope": "static" + } + ], + "namespace": "rocky.watchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "memorypressure", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 69, + "offset": 68 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 69, + "offset": 68 + }, + "end": { + "line": 2, + "column": 4, + "offset": 72 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 72 + }, + "end": { + "line": 2, + "column": 21, + "offset": 89 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 89 + }, + "end": { + "line": 2, + "column": 22, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 90 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 90 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyMemoryPressureCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `memorypressure`\n {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `level` (String) - The current level of memory pressure.\n\n * `high` - This is a critical level, indicating that the application will \n be terminated if memory isn't immediately free'd.\n\n Important Notes:\n - Avoid creating any new objects/arrays/strings when this level is raised.\n - Drop object properties you don't need using the `delete` operator or by assigning `undefined` to it.\n - Don't use the `in` operator in the handler for large objects/arrays. Avoid `for (var x in y)` due to memory constraints.\n - Array has large memory requirements for certain operations/methods. Avoid `Array.pop()`, `Array.slice` and length assignment `Array.length = 123`, on large arrays.\n\n * `normal` - Not yet implemented.\n\n * `low` - Not yet implemented.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 100, + "column": 2 + }, + "end": { + "line": 120, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyMemoryPressureCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": true, + "children": [ + { + "type": "listItem", + "loose": true, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "level", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 53 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " (String) - The current level of memory pressure.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 60, + "offset": 109 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 53 + }, + "end": { + "line": 2, + "column": 60, + "offset": 109 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": true, + "children": [ + { + "type": "listItem", + "loose": true, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "high", + "position": { + "start": { + "line": 4, + "column": 7, + "offset": 117 + }, + "end": { + "line": 4, + "column": 13, + "offset": 123 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - This is a critical level, indicating that the application will \nbe terminated if memory isn't immediately free'd.", + "position": { + "start": { + "line": 4, + "column": 13, + "offset": 123 + }, + "end": { + "line": 5, + "column": 56, + "offset": 245 + }, + "indent": [ + 7 + ] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 7, + "offset": 117 + }, + "end": { + "line": 5, + "column": 56, + "offset": 245 + }, + "indent": [ + 7 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Important Notes:", + "position": { + "start": { + "line": 7, + "column": 7, + "offset": 253 + }, + "end": { + "line": 7, + "column": 23, + "offset": 269 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 7, + "offset": 253 + }, + "end": { + "line": 7, + "column": 23, + "offset": 269 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Avoid creating any new objects/arrays/strings when this level is raised.", + "position": { + "start": { + "line": 8, + "column": 9, + "offset": 278 + }, + "end": { + "line": 8, + "column": 81, + "offset": 350 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 9, + "offset": 278 + }, + "end": { + "line": 8, + "column": 81, + "offset": 350 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 7, + "offset": 276 + }, + "end": { + "line": 8, + "column": 81, + "offset": 350 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Drop object properties you don't need using the ", + "position": { + "start": { + "line": 9, + "column": 9, + "offset": 359 + }, + "end": { + "line": 9, + "column": 57, + "offset": 407 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "delete", + "position": { + "start": { + "line": 9, + "column": 57, + "offset": 407 + }, + "end": { + "line": 9, + "column": 65, + "offset": 415 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " operator or by assigning ", + "position": { + "start": { + "line": 9, + "column": 65, + "offset": 415 + }, + "end": { + "line": 9, + "column": 91, + "offset": 441 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "undefined", + "position": { + "start": { + "line": 9, + "column": 91, + "offset": 441 + }, + "end": { + "line": 9, + "column": 102, + "offset": 452 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " to it.", + "position": { + "start": { + "line": 9, + "column": 102, + "offset": 452 + }, + "end": { + "line": 9, + "column": 109, + "offset": 459 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 9, + "offset": 359 + }, + "end": { + "line": 9, + "column": 109, + "offset": 459 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 7, + "offset": 357 + }, + "end": { + "line": 9, + "column": 109, + "offset": 459 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Don't use the ", + "position": { + "start": { + "line": 10, + "column": 9, + "offset": 468 + }, + "end": { + "line": 10, + "column": 23, + "offset": 482 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "in", + "position": { + "start": { + "line": 10, + "column": 23, + "offset": 482 + }, + "end": { + "line": 10, + "column": 27, + "offset": 486 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " operator in the handler for large objects/arrays. Avoid ", + "position": { + "start": { + "line": 10, + "column": 27, + "offset": 486 + }, + "end": { + "line": 10, + "column": 84, + "offset": 543 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "for (var x in y)", + "position": { + "start": { + "line": 10, + "column": 84, + "offset": 543 + }, + "end": { + "line": 10, + "column": 102, + "offset": 561 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " due to memory constraints.", + "position": { + "start": { + "line": 10, + "column": 102, + "offset": 561 + }, + "end": { + "line": 10, + "column": 129, + "offset": 588 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 9, + "offset": 468 + }, + "end": { + "line": 10, + "column": 129, + "offset": 588 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 7, + "offset": 466 + }, + "end": { + "line": 10, + "column": 129, + "offset": 588 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Array has large memory requirements for certain operations/methods. Avoid ", + "position": { + "start": { + "line": 11, + "column": 9, + "offset": 597 + }, + "end": { + "line": 11, + "column": 83, + "offset": 671 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Array.pop()", + "position": { + "start": { + "line": 11, + "column": 83, + "offset": 671 + }, + "end": { + "line": 11, + "column": 96, + "offset": 684 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 11, + "column": 96, + "offset": 684 + }, + "end": { + "line": 11, + "column": 98, + "offset": 686 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Array.slice", + "position": { + "start": { + "line": 11, + "column": 98, + "offset": 686 + }, + "end": { + "line": 11, + "column": 111, + "offset": 699 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and length assignment ", + "position": { + "start": { + "line": 11, + "column": 111, + "offset": 699 + }, + "end": { + "line": 11, + "column": 134, + "offset": 722 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Array.length = 123", + "position": { + "start": { + "line": 11, + "column": 134, + "offset": 722 + }, + "end": { + "line": 11, + "column": 154, + "offset": 742 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", on large arrays.", + "position": { + "start": { + "line": 11, + "column": 154, + "offset": 742 + }, + "end": { + "line": 11, + "column": 172, + "offset": 760 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 9, + "offset": 597 + }, + "end": { + "line": 11, + "column": 172, + "offset": 760 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 7, + "offset": 595 + }, + "end": { + "line": 11, + "column": 172, + "offset": 760 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 7, + "offset": 276 + }, + "end": { + "line": 11, + "column": 172, + "offset": 760 + }, + "indent": [ + 7, + 7, + 7 + ] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 4, + "offset": 114 + }, + "end": { + "line": 12, + "column": 1, + "offset": 761 + }, + "indent": [ + 4, + 1, + 4, + 4, + 4, + 4, + 4, + 1 + ] + } + }, + { + "type": "listItem", + "loose": true, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "normal", + "position": { + "start": { + "line": 13, + "column": 7, + "offset": 768 + }, + "end": { + "line": 13, + "column": 15, + "offset": 776 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Not yet implemented.", + "position": { + "start": { + "line": 13, + "column": 15, + "offset": 776 + }, + "end": { + "line": 13, + "column": 38, + "offset": 799 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 7, + "offset": 768 + }, + "end": { + "line": 13, + "column": 38, + "offset": 799 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 4, + "offset": 765 + }, + "end": { + "line": 14, + "column": 1, + "offset": 800 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "low", + "position": { + "start": { + "line": 15, + "column": 7, + "offset": 807 + }, + "end": { + "line": 15, + "column": 12, + "offset": 812 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Not yet implemented.", + "position": { + "start": { + "line": 15, + "column": 12, + "offset": 812 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 7, + "offset": 807 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 4, + "offset": 804 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 4, + "offset": 114 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [ + 4, + 1, + 4, + 4, + 4, + 4, + 4, + 1, + 4, + 1, + 4 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 15, + "column": 35, + "offset": 835 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyMemoryPressureCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyMemoryPressureCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "postmessagedisconnected", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 78, + "offset": 77 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 78, + "offset": 77 + }, + "end": { + "line": 2, + "column": 4, + "offset": 81 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 81 + }, + "end": { + "line": 2, + "column": 21, + "offset": 98 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 98 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 99 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyPostMessageDisconnectedCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `postmessagedisconnected`\n {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 91, + "column": 2 + }, + "end": { + "line": 98, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyPostMessageDisconnectedCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyPostMessageDisconnectedCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyPostMessageDisconnectedCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides access to user related settings from the currently connected Pebble smartwatch. \n The size itself will vary between platforms, see the \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 7, + "offset": 156 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "link", + "url": "/guides/user-interfaces/content-size/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "ContentSize guide" + } + ], + "position": { + "start": { + "line": 3, + "column": 7, + "offset": 156 + }, + "end": { + "line": 3, + "column": 70, + "offset": 219 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for more information.", + "position": { + "start": { + "line": 3, + "column": 70, + "offset": 219 + }, + "end": { + "line": 3, + "column": 92, + "offset": 241 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 92, + "offset": 241 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 92, + "offset": 241 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "UserPreferences" + }, + { + "title": "desc", + "description": "Provides access to user related settings from the currently connected Pebble smartwatch. \n The size itself will vary between platforms, see the \n {@link /guides/user-interfaces/content-size/ ContentSize guide} for more information.", + "lineNumber": 2 + }, + { + "title": "property", + "description": "Pebble > System > Notifications > Text Size:\n * `small` - Not available on Emery.\n * `medium` - The default setting.\n * `large` - The default setting on Emery.\n * `x-large` - Only available on Emery.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "contentSize" + } + ], + "loc": { + "start": { + "line": 26, + "column": 2 + }, + "end": { + "line": 37, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "UserPreferences", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "properties": [ + { + "name": "contentSize", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pebble > System > Notifications > Text Size:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 45, + "offset": 44 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 45, + "offset": 44 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "small", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 49 + }, + "end": { + "line": 2, + "column": 12, + "offset": 56 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Not available on Emery.", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 56 + }, + "end": { + "line": 2, + "column": 38, + "offset": 82 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 49 + }, + "end": { + "line": 2, + "column": 38, + "offset": 82 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 45 + }, + "end": { + "line": 2, + "column": 38, + "offset": 82 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "medium", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 87 + }, + "end": { + "line": 3, + "column": 13, + "offset": 95 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The default setting.", + "position": { + "start": { + "line": 3, + "column": 13, + "offset": 95 + }, + "end": { + "line": 3, + "column": 36, + "offset": 118 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 87 + }, + "end": { + "line": 3, + "column": 36, + "offset": 118 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 83 + }, + "end": { + "line": 3, + "column": 36, + "offset": 118 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "large", + "position": { + "start": { + "line": 4, + "column": 5, + "offset": 123 + }, + "end": { + "line": 4, + "column": 12, + "offset": 130 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The default setting on Emery.", + "position": { + "start": { + "line": 4, + "column": 12, + "offset": 130 + }, + "end": { + "line": 4, + "column": 44, + "offset": 162 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 5, + "offset": 123 + }, + "end": { + "line": 4, + "column": 44, + "offset": 162 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 119 + }, + "end": { + "line": 4, + "column": 44, + "offset": 162 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "x-large", + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 167 + }, + "end": { + "line": 5, + "column": 14, + "offset": 176 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Only available on Emery.", + "position": { + "start": { + "line": 5, + "column": 14, + "offset": 176 + }, + "end": { + "line": 5, + "column": 41, + "offset": 203 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 167 + }, + "end": { + "line": 5, + "column": 41, + "offset": 203 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 163 + }, + "end": { + "line": 5, + "column": 41, + "offset": 203 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 45 + }, + "end": { + "line": 5, + "column": 41, + "offset": 203 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 41, + "offset": 203 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "UserPreferences", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.UserPreferences" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the draw ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 58, + "offset": 57 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 1, + "column": 58, + "offset": 57 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 1, + "column": 76, + "offset": 75 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 76, + "offset": 75 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 76, + "offset": 75 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyDrawCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the draw {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `context` - A {@link /docs/rockyjs/CanvasRenderingContext2D CanvasRenderingContext2D}\n object that can be used to draw information on the disply.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 39, + "column": 2 + }, + "end": { + "line": 46, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyDrawCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "context", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 14, + "offset": 63 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - A ", + "position": { + "start": { + "line": 2, + "column": 14, + "offset": 63 + }, + "end": { + "line": 2, + "column": 19, + "offset": 68 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "/docs/rockyjs/CanvasRenderingContext2D", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "CanvasRenderingContext2D" + } + ], + "position": { + "start": { + "line": 2, + "column": 19, + "offset": 68 + }, + "end": { + "line": 2, + "column": 90, + "offset": 139 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n object that can be used to draw information on the disply.", + "position": { + "start": { + "line": 2, + "column": 90, + "offset": 139 + }, + "end": { + "line": 3, + "column": 65, + "offset": 204 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 3, + "column": 65, + "offset": 204 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 65, + "offset": 204 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 65, + "offset": 204 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 65, + "offset": 204 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyDrawCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyDrawCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "message", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 62, + "offset": 61 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n ", + "position": { + "start": { + "line": 1, + "column": 62, + "offset": 61 + }, + "end": { + "line": 2, + "column": 4, + "offset": 65 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "event" + } + ], + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 65 + }, + "end": { + "line": 2, + "column": 21, + "offset": 82 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 21, + "offset": 82 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 22, + "offset": 83 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyMessageCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `message`\n {@link #on event}.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `type` - The type of event which was triggered.\n * `data` - The data sent within the message.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 62, + "column": 2 + }, + "end": { + "line": 70, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyMessageCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event which was triggered.", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 2, + "column": 52, + "offset": 101 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 11, + "offset": 112 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The data sent within the message.", + "position": { + "start": { + "line": 3, + "column": 11, + "offset": 112 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 5, + "offset": 106 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 102 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 47, + "offset": 148 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyMessageCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyMessageCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The callback function signature to be used with the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 53, + "offset": 52 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "secondchange", + "position": { + "start": { + "line": 1, + "column": 53, + "offset": 52 + }, + "end": { + "line": 1, + "column": 67, + "offset": 66 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ",\n ", + "position": { + "start": { + "line": 1, + "column": 67, + "offset": 66 + }, + "end": { + "line": 2, + "column": 3, + "offset": 70 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "minutechange", + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 70 + }, + "end": { + "line": 2, + "column": 17, + "offset": 84 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 2, + "column": 17, + "offset": 84 + }, + "end": { + "line": 2, + "column": 19, + "offset": 86 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "hourchange", + "position": { + "start": { + "line": 2, + "column": 19, + "offset": 86 + }, + "end": { + "line": 2, + "column": 31, + "offset": 98 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " and ", + "position": { + "start": { + "line": 2, + "column": 31, + "offset": 98 + }, + "end": { + "line": 2, + "column": 36, + "offset": 103 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "daychange", + "position": { + "start": { + "line": 2, + "column": 36, + "offset": 103 + }, + "end": { + "line": 2, + "column": 47, + "offset": 114 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 2, + "column": 47, + "offset": 114 + }, + "end": { + "line": 2, + "column": 48, + "offset": 115 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "events" + } + ], + "position": { + "start": { + "line": 2, + "column": 48, + "offset": 115 + }, + "end": { + "line": 2, + "column": 66, + "offset": 133 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 66, + "offset": 133 + }, + "end": { + "line": 2, + "column": 67, + "offset": 134 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 67, + "offset": 134 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "In addition to firing these tick events at the appropriate time change, \n they are also emitted when the application starts.", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 136 + }, + "end": { + "line": 5, + "column": 52, + "offset": 260 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 136 + }, + "end": { + "line": 5, + "column": 52, + "offset": 260 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 52, + "offset": 260 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "RockyTickCallback" + }, + { + "title": "desc", + "description": "The callback function signature to be used with the `secondchange`,\n `minutechange`, `hourchange` and `daychange` {@link #on events}.\n\nIn addition to firing these tick events at the appropriate time change, \n they are also emitted when the application starts.", + "lineNumber": 2 + }, + { + "title": "param", + "description": "An object containing information about the event:\n * `date` - A JavaScript\n {@link http://www.w3schools.com/jsref/jsref_obj_date.asp date} object\n representing the current time.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 48, + "column": 2 + }, + "end": { + "line": 60, + "column": 5 + } + }, + "context": { + "loc": { + "start": { + "line": 129, + "column": 2 + }, + "end": { + "line": 129, + "column": 11 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "kind": "typedef", + "name": "RockyTickCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "params": [ + { + "name": "event", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the event:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 50, + "offset": 49 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "date", + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 2, + "column": 11, + "offset": 60 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - A JavaScript\n ", + "position": { + "start": { + "line": 2, + "column": 11, + "offset": 60 + }, + "end": { + "line": 3, + "column": 6, + "offset": 81 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "link", + "url": "http://www.w3schools.com/jsref/jsref_obj_date.asp", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "date" + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 81 + }, + "end": { + "line": 3, + "column": 68, + "offset": 143 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object\n representing the current time.", + "position": { + "start": { + "line": 3, + "column": 68, + "offset": 143 + }, + "end": { + "line": 4, + "column": 36, + "offset": 186 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 5, + "offset": 54 + }, + "end": { + "line": 4, + "column": 36, + "offset": 186 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 4, + "column": 36, + "offset": 186 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 50 + }, + "end": { + "line": 4, + "column": 36, + "offset": 186 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 36, + "offset": 186 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "RockyTickCallback", + "kind": "typedef", + "scope": "static" + } + ], + "namespace": "rocky.RockyTickCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#UserPreferences", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "UserPreferences" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object access to user related settings from the currently connected Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 43, + "offset": 42 + }, + "end": { + "line": 1, + "column": 130, + "offset": 129 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 130, + "offset": 129 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "console.log(rocky.userPreferences.contentSize);
> medium", + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 131 + }, + "end": { + "line": 3, + "column": 65, + "offset": 195 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 131 + }, + "end": { + "line": 3, + "column": 65, + "offset": 195 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 65, + "offset": 195 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "A {@link #UserPreferences UserPreferences} object access to user related settings from the currently connected Pebble smartwatch.\n\n`console.log(rocky.userPreferences.contentSize);
> medium`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 131, + "column": 2 + }, + "end": { + "line": 136, + "column": 4 + } + }, + "context": { + "loc": { + "start": { + "line": 137, + "column": 2 + }, + "end": { + "line": 137, + "column": 17 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "name": "userPreferences", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "userPreferences", + "scope": "static" + } + ], + "namespace": "rocky.userPreferences" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Attaches an event handler to the specified events. You may subscribe\n with multiple handlers, but at present there is no way to unsubscribe.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 143 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 75, + "offset": 143 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " ", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 145 + }, + "end": { + "line": 4, + "column": 2, + "offset": 146 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "rocky.on('minutechange', function() {...});", + "position": { + "start": { + "line": 4, + "column": 2, + "offset": 146 + }, + "end": { + "line": 4, + "column": 47, + "offset": 191 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 145 + }, + "end": { + "line": 4, + "column": 47, + "offset": 191 + }, + "indent": [] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "text", + "value": "Event Type Options", + "position": { + "start": { + "line": 6, + "column": 8, + "offset": 200 + }, + "end": { + "line": 6, + "column": 26, + "offset": 218 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 193 + }, + "end": { + "line": 6, + "column": 26, + "offset": 218 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 220 + }, + "end": { + "line": 8, + "column": 19, + "offset": 238 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 220 + }, + "end": { + "line": 8, + "column": 19, + "offset": 238 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "draw", + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 244 + }, + "end": { + "line": 10, + "column": 11, + "offset": 250 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 10, + "column": 11, + "offset": 250 + }, + "end": { + "line": 10, + "column": 24, + "offset": 263 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyDrawCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyDrawCallback" + } + ], + "position": { + "start": { + "line": 10, + "column": 24, + "offset": 263 + }, + "end": { + "line": 10, + "column": 68, + "offset": 307 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " as the\n callback. The draw event is being emitted after each call to\n ", + "position": { + "start": { + "line": 10, + "column": 68, + "offset": 307 + }, + "end": { + "line": 12, + "column": 7, + "offset": 388 + }, + "indent": [ + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "#requestDraw", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "requestDraw" + } + ], + "position": { + "start": { + "line": 12, + "column": 7, + "offset": 388 + }, + "end": { + "line": 12, + "column": 39, + "offset": 420 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " but at most once for each screen update,\n even if ", + "position": { + "start": { + "line": 12, + "column": 39, + "offset": 420 + }, + "end": { + "line": 13, + "column": 15, + "offset": 476 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "link", + "url": "#requestDraw", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "requestDraw" + } + ], + "position": { + "start": { + "line": 13, + "column": 15, + "offset": 476 + }, + "end": { + "line": 13, + "column": 47, + "offset": 508 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is called frequently the 'draw'\n event might also fire at other meaningful times (e.g. upon launch).", + "position": { + "start": { + "line": 13, + "column": 47, + "offset": 508 + }, + "end": { + "line": 14, + "column": 74, + "offset": 614 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 244 + }, + "end": { + "line": 14, + "column": 74, + "offset": 614 + }, + "indent": [ + 5, + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 240 + }, + "end": { + "line": 14, + "column": 74, + "offset": 614 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "secondchange", + "position": { + "start": { + "line": 15, + "column": 5, + "offset": 619 + }, + "end": { + "line": 15, + "column": 19, + "offset": 633 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 15, + "column": 19, + "offset": 633 + }, + "end": { + "line": 15, + "column": 32, + "offset": 646 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyTickCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyTickCallback" + } + ], + "position": { + "start": { + "line": 15, + "column": 32, + "offset": 646 + }, + "end": { + "line": 15, + "column": 76, + "offset": 690 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " as the\n callback. The secondchange event is emitted every time the clock's second changes.", + "position": { + "start": { + "line": 15, + "column": 76, + "offset": 690 + }, + "end": { + "line": 16, + "column": 89, + "offset": 786 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 5, + "offset": 619 + }, + "end": { + "line": 16, + "column": 89, + "offset": 786 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1, + "offset": 615 + }, + "end": { + "line": 16, + "column": 89, + "offset": 786 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "minutechange", + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 791 + }, + "end": { + "line": 17, + "column": 19, + "offset": 805 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 17, + "column": 19, + "offset": 805 + }, + "end": { + "line": 17, + "column": 32, + "offset": 818 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyTickCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyTickCallback" + } + ], + "position": { + "start": { + "line": 17, + "column": 32, + "offset": 818 + }, + "end": { + "line": 17, + "column": 76, + "offset": 862 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " as the\n callback. The minutechange event is emitted every time the clock's minute changes.", + "position": { + "start": { + "line": 17, + "column": 76, + "offset": 862 + }, + "end": { + "line": 18, + "column": 89, + "offset": 958 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 5, + "offset": 791 + }, + "end": { + "line": 18, + "column": 89, + "offset": 958 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1, + "offset": 787 + }, + "end": { + "line": 18, + "column": 89, + "offset": 958 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "hourchange", + "position": { + "start": { + "line": 19, + "column": 5, + "offset": 963 + }, + "end": { + "line": 19, + "column": 17, + "offset": 975 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 19, + "column": 17, + "offset": 975 + }, + "end": { + "line": 19, + "column": 30, + "offset": 988 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyTickCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyTickCallback" + } + ], + "position": { + "start": { + "line": 19, + "column": 30, + "offset": 988 + }, + "end": { + "line": 19, + "column": 74, + "offset": 1032 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " as the\n callback. The hourchange event is emitted every time the clock's hour changes.", + "position": { + "start": { + "line": 19, + "column": 74, + "offset": 1032 + }, + "end": { + "line": 20, + "column": 85, + "offset": 1124 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 5, + "offset": 963 + }, + "end": { + "line": 20, + "column": 85, + "offset": 1124 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1, + "offset": 959 + }, + "end": { + "line": 20, + "column": 85, + "offset": 1124 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "daychange", + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 1129 + }, + "end": { + "line": 21, + "column": 16, + "offset": 1140 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 21, + "column": 16, + "offset": 1140 + }, + "end": { + "line": 21, + "column": 29, + "offset": 1153 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyTickCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyTickCallback" + } + ], + "position": { + "start": { + "line": 21, + "column": 29, + "offset": 1153 + }, + "end": { + "line": 21, + "column": 73, + "offset": 1197 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " as the\n callback. The daychange event is emitted every time the clock's day changes.", + "position": { + "start": { + "line": 21, + "column": 73, + "offset": 1197 + }, + "end": { + "line": 22, + "column": 83, + "offset": 1287 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 5, + "offset": 1129 + }, + "end": { + "line": 22, + "column": 83, + "offset": 1287 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1, + "offset": 1125 + }, + "end": { + "line": 22, + "column": 83, + "offset": 1287 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "memorypressure", + "position": { + "start": { + "line": 23, + "column": 5, + "offset": 1292 + }, + "end": { + "line": 23, + "column": 21, + "offset": 1308 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provides a ", + "position": { + "start": { + "line": 23, + "column": 21, + "offset": 1308 + }, + "end": { + "line": 23, + "column": 35, + "offset": 1322 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyMemoryPressureCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyMemoryPressureCallback" + } + ], + "position": { + "start": { + "line": 23, + "column": 35, + "offset": 1322 + }, + "end": { + "line": 23, + "column": 99, + "offset": 1386 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ". The\n event is emitted every time there is a notable change in available system memory.\n You can see an example implementation of memory pressure handling ", + "position": { + "start": { + "line": 23, + "column": 99, + "offset": 1386 + }, + "end": { + "line": 25, + "column": 73, + "offset": 1552 + }, + "indent": [ + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "https://github.com/pebble-examples/rocky-memorypressure", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "here" + } + ], + "position": { + "start": { + "line": 25, + "column": 73, + "offset": 1552 + }, + "end": { + "line": 25, + "column": 141, + "offset": 1620 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 25, + "column": 141, + "offset": 1620 + }, + "end": { + "line": 25, + "column": 142, + "offset": 1621 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 5, + "offset": 1292 + }, + "end": { + "line": 25, + "column": 142, + "offset": 1621 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1, + "offset": 1288 + }, + "end": { + "line": 25, + "column": 142, + "offset": 1621 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "message", + "position": { + "start": { + "line": 26, + "column": 5, + "offset": 1626 + }, + "end": { + "line": 26, + "column": 14, + "offset": 1635 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 26, + "column": 14, + "offset": 1635 + }, + "end": { + "line": 26, + "column": 27, + "offset": 1648 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyMessageCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyMessageCallback" + } + ], + "position": { + "start": { + "line": 26, + "column": 27, + "offset": 1648 + }, + "end": { + "line": 26, + "column": 77, + "offset": 1698 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n as the callback. The message event is emitted every time the application\n receives a ", + "position": { + "start": { + "line": 26, + "column": 77, + "offset": 1698 + }, + "end": { + "line": 28, + "column": 18, + "offset": 1795 + }, + "indent": [ + 5, + 5 + ] + } + }, + { + "type": "link", + "url": "#postMessage", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "postMessage" + } + ], + "position": { + "start": { + "line": 28, + "column": 18, + "offset": 1795 + }, + "end": { + "line": 28, + "column": 50, + "offset": 1827 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " from the mobile companion.", + "position": { + "start": { + "line": 28, + "column": 50, + "offset": 1827 + }, + "end": { + "line": 28, + "column": 77, + "offset": 1854 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 26, + "column": 5, + "offset": 1626 + }, + "end": { + "line": 28, + "column": 77, + "offset": 1854 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 26, + "column": 1, + "offset": 1622 + }, + "end": { + "line": 28, + "column": 77, + "offset": 1854 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessageconnected", + "position": { + "start": { + "line": 29, + "column": 5, + "offset": 1859 + }, + "end": { + "line": 29, + "column": 27, + "offset": 1881 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 29, + "column": 27, + "offset": 1881 + }, + "end": { + "line": 29, + "column": 40, + "offset": 1894 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyPostMessageConnectedCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyPostMessageConnectedCallback" + } + ], + "position": { + "start": { + "line": 29, + "column": 40, + "offset": 1894 + }, + "end": { + "line": 29, + "column": 116, + "offset": 1970 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already connected. It is also emitted when connectivity is established.", + "position": { + "start": { + "line": 29, + "column": 116, + "offset": 1970 + }, + "end": { + "line": 31, + "column": 98, + "offset": 2148 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 5, + "offset": 1859 + }, + "end": { + "line": 31, + "column": 98, + "offset": 2148 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 29, + "column": 1, + "offset": 1855 + }, + "end": { + "line": 31, + "column": 98, + "offset": 2148 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessagedisconnected", + "position": { + "start": { + "line": 32, + "column": 5, + "offset": 2153 + }, + "end": { + "line": 32, + "column": 30, + "offset": 2178 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 32, + "column": 30, + "offset": 2178 + }, + "end": { + "line": 32, + "column": 43, + "offset": 2191 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyPostMessageDisconnectedCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyPostMessageDisconnectedCallback" + } + ], + "position": { + "start": { + "line": 32, + "column": 43, + "offset": 2191 + }, + "end": { + "line": 32, + "column": 125, + "offset": 2273 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already disconnected. It is also emitted when connectivity is lost.", + "position": { + "start": { + "line": 32, + "column": 125, + "offset": 2273 + }, + "end": { + "line": 34, + "column": 94, + "offset": 2447 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 32, + "column": 5, + "offset": 2153 + }, + "end": { + "line": 34, + "column": 94, + "offset": 2447 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 32, + "column": 1, + "offset": 2149 + }, + "end": { + "line": 34, + "column": 94, + "offset": 2447 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 35, + "column": 5, + "offset": 2452 + }, + "end": { + "line": 35, + "column": 23, + "offset": 2470 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Provide a ", + "position": { + "start": { + "line": 35, + "column": 23, + "offset": 2470 + }, + "end": { + "line": 35, + "column": 36, + "offset": 2483 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#RockyPostMessageErrorCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "RockyPostMessageErrorCallback" + } + ], + "position": { + "start": { + "line": 35, + "column": 36, + "offset": 2483 + }, + "end": { + "line": 35, + "column": 104, + "offset": 2551 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n as the callback. The event is emitted when a transmission error occurrs. The type \n of error is not provided, but the message has not been delivered.", + "position": { + "start": { + "line": 35, + "column": 104, + "offset": 2551 + }, + "end": { + "line": 37, + "column": 72, + "offset": 2712 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 35, + "column": 5, + "offset": 2452 + }, + "end": { + "line": 37, + "column": 72, + "offset": 2712 + }, + "indent": [ + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 35, + "column": 1, + "offset": 2448 + }, + "end": { + "line": 37, + "column": 72, + "offset": 2712 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 240 + }, + "end": { + "line": 37, + "column": 72, + "offset": 2712 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 37, + "column": 72, + "offset": 2712 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Attaches an event handler to the specified events. You may subscribe\n with multiple handlers, but at present there is no way to unsubscribe.\n\n `rocky.on('minutechange', function() {...});`\n\n #### Event Type Options\n\n Possible values:\n\n * `draw` - Provide a {@link #RockyDrawCallback RockyDrawCallback} as the\n callback. The draw event is being emitted after each call to\n {@link #requestDraw requestDraw} but at most once for each screen update,\n even if {@link #requestDraw requestDraw} is called frequently the 'draw'\n event might also fire at other meaningful times (e.g. upon launch).\n * `secondchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the\n callback. The secondchange event is emitted every time the clock's second changes.\n * `minutechange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the\n callback. The minutechange event is emitted every time the clock's minute changes.\n * `hourchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the\n callback. The hourchange event is emitted every time the clock's hour changes.\n * `daychange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the\n callback. The daychange event is emitted every time the clock's day changes.\n * `memorypressure` - Provides a {@link #RockyMemoryPressureCallback RockyMemoryPressureCallback}. The\n event is emitted every time there is a notable change in available system memory.\n You can see an example implementation of memory pressure handling {@link https://github.com/pebble-examples/rocky-memorypressure here}.\n * `message` - Provide a {@link #RockyMessageCallback RockyMessageCallback}\n as the callback. The message event is emitted every time the application\n receives a {@link #postMessage postMessage} from the mobile companion.\n * `postmessageconnected` - Provide a {@link #RockyPostMessageConnectedCallback RockyPostMessageConnectedCallback}\n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already connected. It is also emitted when connectivity is established.\n * `postmessagedisconnected` - Provide a {@link #RockyPostMessageDisconnectedCallback RockyPostMessageDisconnectedCallback}\n as the callback. The event may be emitted immediately upon subscription, \n if the subsystem is already disconnected. It is also emitted when connectivity is lost.\n * `postmessageerror` - Provide a {@link #RockyPostMessageErrorCallback RockyPostMessageErrorCallback}\n as the callback. The event is emitted when a transmission error occurrs. The type \n of error is not provided, but the message has not been delivered.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The event being subscribed to.", + "lineNumber": 39, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "A callback function that will be executed when\n the event occurs. See below for more details.", + "lineNumber": 40, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 140, + "column": 0 + }, + "end": { + "line": 183, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 184, + "column": 0 + }, + "end": { + "line": 184, + "column": 40 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 39, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The event being subscribed to.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 40, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A callback function that will be executed when\n the event occurs. See below for more details.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 94 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 94 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 94 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "on", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "on", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.on" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Attaches an event handler to the specified events. Synonymous with \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 7, + "offset": 74 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#on", + "children": [ + { + "type": "text", + "value": "rocky.on()", + "position": { + "start": { + "line": 2, + "column": 8, + "offset": 75 + }, + "end": { + "line": 2, + "column": 18, + "offset": 85 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 7, + "offset": 74 + }, + "end": { + "line": 2, + "column": 24, + "offset": 91 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 24, + "offset": 91 + }, + "end": { + "line": 2, + "column": 25, + "offset": 92 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 92 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.addEventListener(type, callback);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 94 + }, + "end": { + "line": 4, + "column": 42, + "offset": 135 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 94 + }, + "end": { + "line": 4, + "column": 42, + "offset": 135 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 42, + "offset": 135 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Attaches an event handler to the specified events. Synonymous with \n [rocky.on()](#on).\n\n`rocky.addEventListener(type, callback);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The event being subscribed to.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "A callback function that will be executed when \n the event occurs. See below for more details.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 186, + "column": 0 + }, + "end": { + "line": 195, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 196, + "column": 0 + }, + "end": { + "line": 196, + "column": 54 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The event being subscribed to.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A callback function that will be executed when \n the event occurs. See below for more details.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 95 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 95 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 48, + "offset": 95 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "addEventListener", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "addEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.addEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Remove an existing event listener previously registered with \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 64 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#on", + "children": [ + { + "type": "text", + "value": "rocky.on()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 65 + }, + "end": { + "line": 2, + "column": 14, + "offset": 75 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 64 + }, + "end": { + "line": 2, + "column": 20, + "offset": 81 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 2, + "column": 20, + "offset": 81 + }, + "end": { + "line": 2, + "column": 24, + "offset": 85 + }, + "indent": [] + } + }, + { + "type": "link", + "title": null, + "url": "#addEventListener", + "children": [ + { + "type": "text", + "value": "rocky.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 25, + "offset": 86 + }, + "end": { + "line": 2, + "column": 49, + "offset": 110 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 24, + "offset": 85 + }, + "end": { + "line": 2, + "column": 69, + "offset": 130 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 69, + "offset": 130 + }, + "end": { + "line": 2, + "column": 70, + "offset": 131 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 70, + "offset": 131 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 70, + "offset": 131 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Remove an existing event listener previously registered with \n [rocky.on()](#on) or [rocky.addEventListener()](#addEventListener).", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event listener to be removed. See \n [rocky.on()](#on) for a list of available event types.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The existing developer-defined function that was \n previously registered.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 198, + "column": 0 + }, + "end": { + "line": 206, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 207, + "column": 0 + }, + "end": { + "line": 207, + "column": 57 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event listener to be removed. See \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 53 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#on", + "children": [ + { + "type": "text", + "value": "rocky.on()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 54 + }, + "end": { + "line": 2, + "column": 14, + "offset": 64 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 53 + }, + "end": { + "line": 2, + "column": 20, + "offset": 70 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for a list of available event types.", + "position": { + "start": { + "line": 2, + "column": 20, + "offset": 70 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The existing developer-defined function that was \n previously registered.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "removeEventListener", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "removeEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.removeEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Remove an existing event handler from the specified events. Synonymous \n with ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 13, + "offset": 84 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#removeEventListener", + "children": [ + { + "type": "text", + "value": "rocky.removeEventListener()", + "position": { + "start": { + "line": 2, + "column": 14, + "offset": 85 + }, + "end": { + "line": 2, + "column": 41, + "offset": 112 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 13, + "offset": 84 + }, + "end": { + "line": 2, + "column": 64, + "offset": 135 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 64, + "offset": 135 + }, + "end": { + "line": 2, + "column": 65, + "offset": 136 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 136 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.off(type, callback);", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 138 + }, + "end": { + "line": 4, + "column": 29, + "offset": 166 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 138 + }, + "end": { + "line": 4, + "column": 29, + "offset": 166 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 29, + "offset": 166 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Remove an existing event handler from the specified events. Synonymous \n with [rocky.removeEventListener()](#removeEventListener).\n\n`rocky.off(type, callback);`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event listener to be removed. See \n [rocky.on()](#on) for a list of available event types.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The existing developer-defined function that was \n previously registered.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 209, + "column": 0 + }, + "end": { + "line": 219, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 220, + "column": 0 + }, + "end": { + "line": 220, + "column": 41 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "params": [ + { + "name": "type", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event listener to be removed. See \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 53 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "title": null, + "url": "#on", + "children": [ + { + "type": "text", + "value": "rocky.on()", + "position": { + "start": { + "line": 2, + "column": 4, + "offset": 54 + }, + "end": { + "line": 2, + "column": 14, + "offset": 64 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 53 + }, + "end": { + "line": 2, + "column": 20, + "offset": 70 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for a list of available event types.", + "position": { + "start": { + "line": 2, + "column": 20, + "offset": 70 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 57, + "offset": 107 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The existing developer-defined function that was \n previously registered.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 74 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "off", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "off", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.off" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sends a message to the mobile companion component. Please be aware \n that messages should be kept concise. Each message is queued, so \n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 4, + "offset": 140 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "postMessage()", + "position": { + "start": { + "line": 3, + "column": 4, + "offset": 140 + }, + "end": { + "line": 3, + "column": 19, + "offset": 155 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " can be called multiple times immediately. If there is a momentary loss of connectivity, queued \n messages may still be delivered, or automatically removed from the queue \n after a few seconds of failed connectivity. Any transmission failures, or \n out of memory errors will be raised via the ", + "position": { + "start": { + "line": 3, + "column": 19, + "offset": 155 + }, + "end": { + "line": 6, + "column": 48, + "offset": 454 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "postmessageerror", + "position": { + "start": { + "line": 6, + "column": 48, + "offset": 454 + }, + "end": { + "line": 6, + "column": 66, + "offset": 472 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " event.", + "position": { + "start": { + "line": 6, + "column": 66, + "offset": 472 + }, + "end": { + "line": 6, + "column": 73, + "offset": 479 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 73, + "offset": 479 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.postMessage({cmd: 'fetch'});", + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 481 + }, + "end": { + "line": 8, + "column": 37, + "offset": 517 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 481 + }, + "end": { + "line": 8, + "column": 37, + "offset": 517 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 8, + "column": 37, + "offset": 517 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Sends a message to the mobile companion component. Please be aware \n that messages should be kept concise. Each message is queued, so \n `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued \n messages may still be delivered, or automatically removed from the queue \n after a few seconds of failed connectivity. Any transmission failures, or \n out of memory errors will be raised via the `postmessageerror` event.\n\n`rocky.postMessage({cmd: 'fetch'});`", + "lineNumber": 1 + }, + { + "title": "param", + "description": "An object containing the data to deliver to the mobile\n device. This will be received in the `data` field of the `event`\n delivered to the `on('message', ...)` handler.", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + } + ], + "loc": { + "start": { + "line": 222, + "column": 0 + }, + "end": { + "line": 235, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 236, + "column": 0 + }, + "end": { + "line": 236, + "column": 39 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "params": [ + { + "name": "data", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the data to deliver to the mobile\n device. This will be received in the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 100 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "data", + "position": { + "start": { + "line": 2, + "column": 46, + "offset": 100 + }, + "end": { + "line": 2, + "column": 52, + "offset": 106 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " field of the ", + "position": { + "start": { + "line": 2, + "column": 52, + "offset": 106 + }, + "end": { + "line": 2, + "column": 66, + "offset": 120 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "event", + "position": { + "start": { + "line": 2, + "column": 66, + "offset": 120 + }, + "end": { + "line": 2, + "column": 73, + "offset": 127 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n delivered to the ", + "position": { + "start": { + "line": 2, + "column": 73, + "offset": 127 + }, + "end": { + "line": 3, + "column": 26, + "offset": 153 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "on('message', ...)", + "position": { + "start": { + "line": 3, + "column": 26, + "offset": 153 + }, + "end": { + "line": 3, + "column": 46, + "offset": 173 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " handler.", + "position": { + "start": { + "line": 3, + "column": 46, + "offset": 173 + }, + "end": { + "line": 3, + "column": 55, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 55, + "offset": 182 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 55, + "offset": 182 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "name": "postMessage", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "postMessage", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.postMessage" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Flags the canvas (display) as requiring a redraw. Invoking this method\n will cause the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 19, + "offset": 89 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "link", + "url": "#on", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "draw event" + } + ], + "position": { + "start": { + "line": 2, + "column": 19, + "offset": 89 + }, + "end": { + "line": 2, + "column": 41, + "offset": 111 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " to be emitted. Only 1 draw event\n will occur, regardless of how many times the redraw is requested before\n the next draw event.", + "position": { + "start": { + "line": 2, + "column": 41, + "offset": 111 + }, + "end": { + "line": 4, + "column": 24, + "offset": 243 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 24, + "offset": 243 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "rocky.on('secondchange', function(e) {
  rocky.requestDraw();
});", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 245 + }, + "end": { + "line": 6, + "column": 84, + "offset": 328 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 245 + }, + "end": { + "line": 6, + "column": 84, + "offset": 328 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 84, + "offset": 328 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Flags the canvas (display) as requiring a redraw. Invoking this method\n will cause the {@link #on draw event} to be emitted. Only 1 draw event\n will occur, regardless of how many times the redraw is requested before\n the next draw event.\n\n`rocky.on('secondchange', function(e) {
  rocky.requestDraw();
});`", + "lineNumber": 1 + } + ], + "loc": { + "start": { + "line": 238, + "column": 0 + }, + "end": { + "line": 245, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 246, + "column": 0 + }, + "end": { + "line": 246, + "column": 35 + } + }, + "file": "/Users/devsite/Pebble/developer.getpebble.com/js-docs/rocky/rocky.js" + }, + "name": "requestDraw", + "kind": "function", + "memberof": "rocky", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + }, + { + "name": "requestDraw", + "kind": "function", + "scope": "static" + } + ], + "namespace": "rocky.requestDraw" + } + ] + }, + "path": [ + { + "name": "rocky", + "kind": "namespace" + } + ], + "namespace": "rocky" + } +] \ No newline at end of file diff --git a/devsite/source/_data/landing_page.yaml b/devsite/source/_data/landing_page.yaml new file mode 100644 index 00000000..36a1e8d7 --- /dev/null +++ b/devsite/source/_data/landing_page.yaml @@ -0,0 +1,14 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/devsite/source/_data/meetups.yaml b/devsite/source/_data/meetups.yaml new file mode 100644 index 00000000..8746fdbf --- /dev/null +++ b/devsite/source/_data/meetups.yaml @@ -0,0 +1,94 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- city: San Francisco + group_id: PebbleSF + pin: + latitude: 37.774929 + longitude: -122.419416 +- city: London + group_id: PebbleLDN + pin: + latitude: 51.507351 + longitude: -0.127758 +- city: Paris + group_id: PebbleFR + pin: + latitude: 48.86 + longitude: 2.34 +- city: Ottawa + group_id: PebbleOTT + pin: + latitude: 45.42 + longitude: -75.7 +- city: Chicago + group_id: PebbleChi + pin: + latitude: 41.88 + longitude: -87.64 +- city: Hamburg + group_id: Pebble_HH + pin: + latitude: 53.54999923706055 + longitude: 10 +- city: Houston + group_id: Pebble-Houston + pin: + latitude: 29.719999313354492 + longitude: -95.22000122070312 +- city: Los Angeles + group_id: PebbleLA + pin: + latitude: 33.970001220703125 + longitude: -118.23999786376953 +- city: New York + group_id: PebbleNYC + pin: + latitude: 40.709999084472656 + longitude: -74.01000213623047 +- city: Orlando + group_id: PebbleORL + pin: + latitude: 28.540000915527344 + longitude: -81.37000274658203 +- city: Seattle + group_id: PebbleSEA + pin: + latitude: 47.61000061035156 + longitude: -122.33000183105469 +- city: Helsinki + group_id: PebbleHEL + pin: + latitude: 60.16999816894531 + longitude: 24.940000534057617 +- city: Amsterdam + group_id: PebbleAMS + pin: + latitude: 52.369998931884766 + longitude: 4.889999866485596 +- city: Brussels + group_id: PebbleBelgium + pin: + latitude: 50.83000183105469 + longitude: 4.329999923706055 +- city: Reno + group_id: PebbleReno + pin: + latitude: 39.529998779296875 + longitude: -119.80999755859375 +- city: Toronto + group_id: PebbleTO + pin: + latitude: 43.63999938964844 + longitude: -79.43000030517578 diff --git a/devsite/source/_data/open-source.yaml b/devsite/source/_data/open-source.yaml new file mode 100644 index 00000000..d342948a --- /dev/null +++ b/devsite/source/_data/open-source.yaml @@ -0,0 +1,102 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: CloudPebble + slug: cloudpebble + projects: + - name: CloudPebble + github: pebble/cloudpebble + summary: Web-based IDE for Pebble development. + license: MIT + - name: CloudPebble QEMU Controller + github: pebble/cloudpebble-qemu-controller + summary: Controls QEMUs + license: MIT + - name: CloudPebble ycmd Proxy + github: pebble/cloudpebble-ycmd-proxy + summary: Handles communication with the autocompletion daemon(s) on behalf of CloudPebble clients. + license: MIT +- name: Pebble SDK + slug: pebble-sdk + projects: + - name: libpebble2 + github: pebble/libpebble2 + summary: Library for communication with Pebble devices and emulators + license: MIT + - name: pebble-tool + github: pebble/pebble-tool + summary: The Pebble tool, as distributed in the Pebble SDK + license: MIT + - name: pypkjs + github: pebble/pypkjs + summary: Python implementation of PebbleKit JS + license: MIT + - name: PyV8 + github: pebble/pyv8 + summary: Pebble fork of PyV8 + license: Apache License 2.0 + - name: QEMU + github: pebble/qemu + summary: Pebble's fork of QEMU, with support for Pebble and Pebble Time + license: GPLv2 +- name: Node.js + slug: node + projects: + - name: pebble-api + github: pebble/pebble-api-node + summary: Node.js client for the Pebble timeline APIs + license: MIT + - name: koa-bunyan-logger + github: pebble/koa-bunyan-logger + summary: Koa middleware for bunyan request logging + license: MIT + - name: yieldb + github: pebble/yieldb + summary: Simple, expressive and yieldable MongoDB + license: MIT + - name: event-loop-lag + github: pebble/event-loop-lag + summary: Measures Node.js event loop lag + license: MIT + - name: joi-objectid + github: pebble/joi-objectid + summary: A MongoDB ObjectId validator for Joi + license: MIT + - name: koa-joi-router + github: pebble/koa-joi-router + summary: Easy, rich and fully validated koa routing. + license: MIT + - name: koa-resourcer + github: pebble/koa-resourcer + summary: A simple resource directory mounter for koa + license: MIT + - name: has-own + github: pebble/has-own + summary: "A safer .hasOwnProperty() where property name comes first: `hasOwn(name, obj)`" + license: MIT + - name: qdog + github: pebble/qdog + summary: Module used for adding and reading from SQS and eventually Redis + license: MIT +- name: Other + slug: other + projects: + - name: Pebble.js + github: pebble/pebblejs + summary: Program the Pebble with simply JavaScript + license: MIT + - name: git-deploy + github: pebble/git-deploy + summary: Git driven deployment strategy using git-hooks. + license: MIT diff --git a/devsite/source/_data/redirects.yaml b/devsite/source/_data/redirects.yaml new file mode 100644 index 00000000..44954b62 --- /dev/null +++ b/devsite/source/_data/redirects.yaml @@ -0,0 +1,2765 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- + - /1/01_GetStarted/01_Step_1/index.html + - /getting-started/ +- + - /1/01_GetStarted/01_Step_2/index.html + - /getting-started/ +- + - /1/01_GetStarted/01_Step_3/index.html + - /getting-started/ +- + - /1/01_GetStarted/01_Step_4/index.html + - /getting-started/ +- + - /1/01_GetStarted/index.html + - /getting-started/ +- + - /1/02_Guides/01_AppsVsWatchfaces/index.html + - /guides/publishing-tools/publish-to-pebble-appstore/ +- + - /1/02_Guides/02_KeyConcepts/index.html + - /guides/pebble-apps/ +- + - /1/02_Guides/03_AppAnatomy/index.html + - /guides/pebble-apps/app-structure/ +- + - /1/02_Guides/04_UsingResources/index.html + - /guides/pebble-apps/resources/ +- + - /1/02_Guides/05_UiFramework/index.html + - /guides/pebble-apps/display-and-animations/layers/ +- + - /1/02_Guides/06_Performance/index.html + - /guides/best-practices/ +- + - /1/02_Guides/07_CommunicatingPhone/index.html + - /guides/pebble-apps/communications/ +- + - /1/02_Guides/08_Sports/index.html + - /guides/mobile-apps/ +- + - /1/02_Reference/05_UiFramework/index.html + - /docs/c/group___u_i.html +- + - /1/03_API_Reference/03_Android/index.html + - /docs/pebblekit-android/ +- + - /1/04_MoreResources/00_Examples/index.html + - /examples/ +- + - /1/04_MoreResources/01_FAQ/index.html + - /faqs/ +- + - /1/04_MoreResources/015_libpebble/index.html + - /community/libraries/libpebble/ +- + - /1/04_MoreResources/02_Support/index.html + - /contact/ +- + - /1/04_MoreResources/3rd-Party/index.html + - /community/ +- + - /1/04_MoreResources/Whitelisting/index.html + - /guides/publishing-tools/whitelisting/ +- + - /1/getting-started/index.html + - /getting-started/ +- + - /1/GettingStarted/HelloWorld/index.html + - /getting-started/watchface-tutorial/ +- + - /1/GettingStarted/Linux/index.html + - /sdk/install/linux/ +- + - /1/GettingStarted/MacOS/index.html + - /sdk/install/mac/ +- + - /1/GettingStarted/UsingExamples/index.html + - /examples/ +- + - /1/GettingStarted/Windows/index.html + - /sdk/install/windows/ +- + - /1/index.html + - / +- + - /2/additional/battery-perform-guide.html + - /guides/best-practices/battery-perform-guide/ +- + - /2/additional/developer-connection/index.html + - /guides/publishing-tools/developer-connection/ +- + - /2/additional/faqs.html + - /faqs/ +- + - /2/additional/hello-world/index.html + - /getting-started/watchface-tutorial/ +- + - /2/additional/pebble-tool/index.html + - /guides/publishing-tools/pebble-tool/ +- + - /2/additional/technical-support.html + - /contact/ +- + - /2/additional/third-party-libs.html + - /community/libraries/ +- + - /2/additional/using-examples/index.html + - /examples/ +- + - /2/android-reference/allclasses-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/allclasses-noframe.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/Constants.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants/ +- + - /2/android-reference/com/getpebble/android/kit/Constants.PebbleAppType.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType/ +- + - /2/android-reference/com/getpebble/android/kit/Constants.PebbleDataType.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType/ +- + - /2/android-reference/com/getpebble/android/kit/package-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/package-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/package-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver/ +- + - /2/android-reference/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver/ +- + - /2/android-reference/com/getpebble/android/kit/util/package-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/util/package-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/util/package-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/com/getpebble/android/kit/util/PebbleDictionary.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary/ +- + - /2/android-reference/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException/ +- + - /2/android-reference/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException/ +- + - /2/android-reference/constant-values.html + - /docs/pebblekit-android/com/constant-values/ +- + - /2/android-reference/deprecated-list.html + - /docs/pebblekit-android/ +- + - /2/android-reference/help-doc.html + - /docs/pebblekit-android/ +- + - /2/android-reference/index-all.html + - /docs/pebblekit-android/ +- + - /2/android-reference/index.html + - /docs/pebblekit-android/ +- + - /2/android-reference/overview-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/overview-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/overview-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/allclasses-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/allclasses-noframe.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/Constants.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/Constants.PebbleAppType.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/Constants.PebbleDataType.html + - /docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/package-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/package-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/package-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.html + - /docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/package-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/package-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/package-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/PebbleDictionary.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException/ +- + - /2/android-reference/PebbleKit-Android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException.html + - /docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException/ +- + - /2/android-reference/PebbleKit-Android/constant-values.html + - /docs/pebblekit-android/com/constant-values/ +- + - /2/android-reference/PebbleKit-Android/deprecated-list.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/help-doc.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/index-all.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/index.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/overview-frame.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/overview-summary.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/overview-tree.html + - /docs/pebblekit-android/ +- + - /2/android-reference/PebbleKit-Android/serialized-form.html + - /docs/pebblekit-android/com/serialized-form/ +- + - /2/android-reference/serialized-form.html + - /docs/pebblekit-android/com/serialized-form/ +- + - /2/api-reference/_a_p_i_changelog.html + - /docs/c/ +- + - /2/api-reference/annotated.html + - /docs/c/ +- + - /2/api-reference/classes.html + - /docs/c/ +- + - /2/api-reference/dir_1c0b520d3dde23966608b739253ba894.html + - /docs/c/ +- + - /2/api-reference/dir_2b0141dd57d5fafe8882ba9edd393fcc.html + - /docs/c/ +- + - /2/api-reference/dir_31b2ddb2365bf074d9be377bc0cb8b65.html + - /docs/c/ +- + - /2/api-reference/dir_4e7c8c4fc0d923782da8a23d70a9725b.html + - /docs/c/ +- + - /2/api-reference/dir_4fef79e7177ba769987a8da36c892c5f.html + - /docs/c/ +- + - /2/api-reference/dir_786940c6392c5fad07a9f448ec172eeb.html + - /docs/c/ +- + - /2/api-reference/functions_vars.html + - /docs/c/ +- + - /2/api-reference/functions.html + - /docs/c/ +- + - /2/api-reference/group___accelerometer_service.html + - /docs/c/group___accelerometer_service.html +- + - /2/api-reference/group___action_bar_layer.html + - /docs/c/group___action_bar_layer.html +- + - /2/api-reference/group___animation.html + - /docs/c/group___animation.html +- + - /2/api-reference/group___app_comm.html + - /docs/c/group___app_comm.html +- + - /2/api-reference/group___app_focus_service.html + - /docs/c/group___app_focus_service.html +- + - /2/api-reference/group___app_message.html + - /docs/c/group___app_message.html +- + - /2/api-reference/group___app_sync.html + - /docs/c/group___app_sync.html +- + - /2/api-reference/group___app_worker_service.html + - /docs/c/group___app_worker.html +- + - /2/api-reference/group___app_worker.html + - /docs/c/group___app_worker.html +- + - /2/api-reference/group___app.html + - /docs/c/group___app.html +- + - /2/api-reference/group___battery_state_service.html + - /docs/c/group___battery_state_service.html +- + - /2/api-reference/group___bitmap_layer.html + - /docs/c/group___bitmap_layer.html +- + - /2/api-reference/group___bluetooth_connection_service.html + - /docs/c/group___bluetooth_connection_service.html +- + - /2/api-reference/group___clicks.html + - /docs/c/group___clicks.html +- + - /2/api-reference/group___compass_service.html + - /docs/c/group___compass_service.html +- + - /2/api-reference/group___data_logging.html + - /docs/c/group___data_logging.html +- + - /2/api-reference/group___data_spooling.html + - /docs/c/group___data_logging.html +- + - /2/api-reference/group___data_structures.html + - /docs/c/group___data_structures.html +- + - /2/api-reference/group___dictionary.html + - /docs/c/group___dictionary.html +- + - /2/api-reference/group___drawing.html + - /docs/c/group___drawing.html +- + - /2/api-reference/group___event_service.html + - /docs/c/group___event_service.html +- + - /2/api-reference/group___fonts.html + - /docs/c/group___fonts.html +- + - /2/api-reference/group___foundation.html + - /docs/c/group___foundation.html +- + - /2/api-reference/group___graphics_context.html + - /docs/c/group___graphics_context.html +- + - /2/api-reference/group___graphics_types.html + - /docs/c/group___graphics_types.html +- + - /2/api-reference/group___graphics.html + - /docs/c/group___graphics.html +- + - /2/api-reference/group___inverter_layer.html + - /docs/c/group___inverter_layer.html +- + - /2/api-reference/group___launch_reason.html + - /docs/c/group___launch_reason.html +- + - /2/api-reference/group___layer.html + - /docs/c/group___layer.html +- + - /2/api-reference/group___light.html + - /docs/c/group___light.html +- + - /2/api-reference/group___logging.html + - /docs/c/group___logging.html +- + - /2/api-reference/group___math.html + - /docs/c/group___math.html +- + - /2/api-reference/group___memory_management.html + - /docs/c/group___memory_management.html +- + - /2/api-reference/group___menu_layer.html + - /docs/c/group___menu_layer.html +- + - /2/api-reference/group___number_window.html + - /docs/c/group___number_window.html +- + - /2/api-reference/group___path_drawing.html + - /docs/c/group___path_drawing.html +- + - /2/api-reference/group___persistent.html + - /docs/c/group___storage.html +- + - /2/api-reference/group___property_animation.html + - /docs/c/group___property_animation.html +- + - /2/api-reference/group___resources.html + - /docs/c/group___resources.html +- + - /2/api-reference/group___rot_bitmap_layer.html + - /docs/c/group___rot_bitmap_layer.html +- + - /2/api-reference/group___scroll_layer.html + - /docs/c/group___scroll_layer.html +- + - /2/api-reference/group___simple_menu_layer.html + - /docs/c/group___simple_menu_layer.html +- + - /2/api-reference/group___standard_c.html + - /docs/c/group___standard_c.html +- + - /2/api-reference/group___standard_i_o.html + - /docs/c/group___standard_i_o.html +- + - /2/api-reference/group___standard_math.html + - /docs/c/group___standard_math.html +- + - /2/api-reference/group___standard_memory.html + - /docs/c/group___standard_memory.html +- + - /2/api-reference/group___standard_string.html + - /docs/c/group___standard_string.html +- + - /2/api-reference/group___standard_time.html + - /docs/c/group___standard_time.html +- + - /2/api-reference/group___storage.html + - /docs/c/group___storage.html +- + - /2/api-reference/group___text_drawing.html + - /docs/c/group___text_drawing.html +- + - /2/api-reference/group___text_layer.html + - /docs/c/group___text_layer.html +- + - /2/api-reference/group___tick_timer_service.html + - /docs/c/group___tick_timer_service.html +- + - /2/api-reference/group___time.html + - /docs/c/group___standard_time.html +- + - /2/api-reference/group___timer.html + - /docs/c/group___timer.html +- + - /2/api-reference/group___u_i.html + - /docs/c/group___u_i.html +- + - /2/api-reference/group___u_u_i_d.html + - /docs/c/group___u_u_i_d.html +- + - /2/api-reference/group___vibes.html + - /docs/c/group___vibes.html +- + - /2/api-reference/group___wakeup.html + - /docs/c/group___wakeup.html +- + - /2/api-reference/group___wall_time.html + - /docs/c/group___wall_time.html +- + - /2/api-reference/group___watch_info.html + - /docs/c/group___watch_info.html +- + - /2/api-reference/group___window_stack.html + - /docs/c/group___window_stack.html +- + - /2/api-reference/group___window.html + - /docs/c/group___window.html +- + - /2/api-reference/group___worker.html + - /docs/c/group___worker.html +- + - /2/api-reference/index.html + - /docs/c/ +- + - /2/api-reference/modules.html + - /docs/c/ +- + - /2/api-reference/pages.html + - /docs/c/ +- + - /2/api-reference/search/all_0.html + - /docs/c/ +- + - /2/api-reference/search/all_1.html + - /docs/c/ +- + - /2/api-reference/search/all_10.html + - /docs/c/ +- + - /2/api-reference/search/all_11.html + - /docs/c/ +- + - /2/api-reference/search/all_12.html + - /docs/c/ +- + - /2/api-reference/search/all_13.html + - /docs/c/ +- + - /2/api-reference/search/all_14.html + - /docs/c/ +- + - /2/api-reference/search/all_15.html + - /docs/c/ +- + - /2/api-reference/search/all_16.html + - /docs/c/ +- + - /2/api-reference/search/all_17.html + - /docs/c/ +- + - /2/api-reference/search/all_2.html + - /docs/c/ +- + - /2/api-reference/search/all_3.html + - /docs/c/ +- + - /2/api-reference/search/all_4.html + - /docs/c/ +- + - /2/api-reference/search/all_5.html + - /docs/c/ +- + - /2/api-reference/search/all_6.html + - /docs/c/ +- + - /2/api-reference/search/all_61.html + - /docs/c/ +- + - /2/api-reference/search/all_62.html + - /docs/c/ +- + - /2/api-reference/search/all_63.html + - /docs/c/ +- + - /2/api-reference/search/all_64.html + - /docs/c/ +- + - /2/api-reference/search/all_65.html + - /docs/c/ +- + - /2/api-reference/search/all_66.html + - /docs/c/ +- + - /2/api-reference/search/all_67.html + - /docs/c/ +- + - /2/api-reference/search/all_68.html + - /docs/c/ +- + - /2/api-reference/search/all_69.html + - /docs/c/ +- + - /2/api-reference/search/all_6b.html + - /docs/c/ +- + - /2/api-reference/search/all_6c.html + - /docs/c/ +- + - /2/api-reference/search/all_6d.html + - /docs/c/ +- + - /2/api-reference/search/all_6e.html + - /docs/c/ +- + - /2/api-reference/search/all_6f.html + - /docs/c/ +- + - /2/api-reference/search/all_7.html + - /docs/c/ +- + - /2/api-reference/search/all_70.html + - /docs/c/ +- + - /2/api-reference/search/all_72.html + - /docs/c/ +- + - /2/api-reference/search/all_73.html + - /docs/c/ +- + - /2/api-reference/search/all_74.html + - /docs/c/ +- + - /2/api-reference/search/all_75.html + - /docs/c/ +- + - /2/api-reference/search/all_76.html + - /docs/c/ +- + - /2/api-reference/search/all_77.html + - /docs/c/ +- + - /2/api-reference/search/all_78.html + - /docs/c/ +- + - /2/api-reference/search/all_79.html + - /docs/c/ +- + - /2/api-reference/search/all_7a.html + - /docs/c/ +- + - /2/api-reference/search/all_8.html + - /docs/c/ +- + - /2/api-reference/search/all_9.html + - /docs/c/ +- + - /2/api-reference/search/all_a.html + - /docs/c/ +- + - /2/api-reference/search/all_b.html + - /docs/c/ +- + - /2/api-reference/search/all_c.html + - /docs/c/ +- + - /2/api-reference/search/all_d.html + - /docs/c/ +- + - /2/api-reference/search/all_e.html + - /docs/c/ +- + - /2/api-reference/search/all_f.html + - /docs/c/ +- + - /2/api-reference/search/classes_0.html + - /docs/c/ +- + - /2/api-reference/search/classes_1.html + - /docs/c/ +- + - /2/api-reference/search/classes_2.html + - /docs/c/ +- + - /2/api-reference/search/classes_3.html + - /docs/c/ +- + - /2/api-reference/search/classes_4.html + - /docs/c/ +- + - /2/api-reference/search/classes_5.html + - /docs/c/ +- + - /2/api-reference/search/classes_6.html + - /docs/c/ +- + - /2/api-reference/search/classes_61.html + - /docs/c/ +- + - /2/api-reference/search/classes_62.html + - /docs/c/ +- + - /2/api-reference/search/classes_63.html + - /docs/c/ +- + - /2/api-reference/search/classes_64.html + - /docs/c/ +- + - /2/api-reference/search/classes_67.html + - /docs/c/ +- + - /2/api-reference/search/classes_6c.html + - /docs/c/ +- + - /2/api-reference/search/classes_6d.html + - /docs/c/ +- + - /2/api-reference/search/classes_6e.html + - /docs/c/ +- + - /2/api-reference/search/classes_7.html + - /docs/c/ +- + - /2/api-reference/search/classes_70.html + - /docs/c/ +- + - /2/api-reference/search/classes_72.html + - /docs/c/ +- + - /2/api-reference/search/classes_73.html + - /docs/c/ +- + - /2/api-reference/search/classes_74.html + - /docs/c/ +- + - /2/api-reference/search/classes_76.html + - /docs/c/ +- + - /2/api-reference/search/classes_77.html + - /docs/c/ +- + - /2/api-reference/search/classes_8.html + - /docs/c/ +- + - /2/api-reference/search/classes_9.html + - /docs/c/ +- + - /2/api-reference/search/classes_a.html + - /docs/c/ +- + - /2/api-reference/search/enums_0.html + - /docs/c/ +- + - /2/api-reference/search/enums_1.html + - /docs/c/ +- + - /2/api-reference/search/enums_2.html + - /docs/c/ +- + - /2/api-reference/search/enums_3.html + - /docs/c/ +- + - /2/api-reference/search/enums_4.html + - /docs/c/ +- + - /2/api-reference/search/enums_5.html + - /docs/c/ +- + - /2/api-reference/search/enums_6.html + - /docs/c/ +- + - /2/api-reference/search/enums_61.html + - /docs/c/ +- + - /2/api-reference/search/enums_62.html + - /docs/c/ +- + - /2/api-reference/search/enums_63.html + - /docs/c/ +- + - /2/api-reference/search/enums_64.html + - /docs/c/ +- + - /2/api-reference/search/enums_67.html + - /docs/c/ +- + - /2/api-reference/search/enums_6d.html + - /docs/c/ +- + - /2/api-reference/search/enums_73.html + - /docs/c/ +- + - /2/api-reference/search/enums_74.html + - /docs/c/ +- + - /2/api-reference/search/enums_77.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_0.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_1.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_2.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_3.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_4.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_5.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_6.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_61.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_62.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_63.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_64.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_65.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_66.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_67.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_68.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_6d.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_6e.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_7.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_73.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_74.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_77.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_79.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_8.html + - /docs/c/ +- + - /2/api-reference/search/enumvalues_9.html + - /docs/c/ +- + - /2/api-reference/search/functions_0.html + - /docs/c/ +- + - /2/api-reference/search/functions_1.html + - /docs/c/ +- + - /2/api-reference/search/functions_2.html + - /docs/c/ +- + - /2/api-reference/search/functions_3.html + - /docs/c/ +- + - /2/api-reference/search/functions_4.html + - /docs/c/ +- + - /2/api-reference/search/functions_5.html + - /docs/c/ +- + - /2/api-reference/search/functions_6.html + - /docs/c/ +- + - /2/api-reference/search/functions_61.html + - /docs/c/ +- + - /2/api-reference/search/functions_62.html + - /docs/c/ +- + - /2/api-reference/search/functions_63.html + - /docs/c/ +- + - /2/api-reference/search/functions_64.html + - /docs/c/ +- + - /2/api-reference/search/functions_66.html + - /docs/c/ +- + - /2/api-reference/search/functions_67.html + - /docs/c/ +- + - /2/api-reference/search/functions_68.html + - /docs/c/ +- + - /2/api-reference/search/functions_69.html + - /docs/c/ +- + - /2/api-reference/search/functions_6c.html + - /docs/c/ +- + - /2/api-reference/search/functions_6d.html + - /docs/c/ +- + - /2/api-reference/search/functions_6e.html + - /docs/c/ +- + - /2/api-reference/search/functions_7.html + - /docs/c/ +- + - /2/api-reference/search/functions_70.html + - /docs/c/ +- + - /2/api-reference/search/functions_72.html + - /docs/c/ +- + - /2/api-reference/search/functions_73.html + - /docs/c/ +- + - /2/api-reference/search/functions_74.html + - /docs/c/ +- + - /2/api-reference/search/functions_75.html + - /docs/c/ +- + - /2/api-reference/search/functions_76.html + - /docs/c/ +- + - /2/api-reference/search/functions_77.html + - /docs/c/ +- + - /2/api-reference/search/functions_8.html + - /docs/c/ +- + - /2/api-reference/search/functions_9.html + - /docs/c/ +- + - /2/api-reference/search/functions_a.html + - /docs/c/ +- + - /2/api-reference/search/functions_b.html + - /docs/c/ +- + - /2/api-reference/search/functions_c.html + - /docs/c/ +- + - /2/api-reference/search/functions_d.html + - /docs/c/ +- + - /2/api-reference/search/functions_e.html + - /docs/c/ +- + - /2/api-reference/search/functions_f.html + - /docs/c/ +- + - /2/api-reference/search/groups_0.html + - /docs/c/ +- + - /2/api-reference/search/groups_1.html + - /docs/c/ +- + - /2/api-reference/search/groups_10.html + - /docs/c/ +- + - /2/api-reference/search/groups_11.html + - /docs/c/ +- + - /2/api-reference/search/groups_2.html + - /docs/c/ +- + - /2/api-reference/search/groups_3.html + - /docs/c/ +- + - /2/api-reference/search/groups_4.html + - /docs/c/ +- + - /2/api-reference/search/groups_5.html + - /docs/c/ +- + - /2/api-reference/search/groups_6.html + - /docs/c/ +- + - /2/api-reference/search/groups_61.html + - /docs/c/ +- + - /2/api-reference/search/groups_62.html + - /docs/c/ +- + - /2/api-reference/search/groups_63.html + - /docs/c/ +- + - /2/api-reference/search/groups_64.html + - /docs/c/ +- + - /2/api-reference/search/groups_65.html + - /docs/c/ +- + - /2/api-reference/search/groups_66.html + - /docs/c/ +- + - /2/api-reference/search/groups_67.html + - /docs/c/ +- + - /2/api-reference/search/groups_69.html + - /docs/c/ +- + - /2/api-reference/search/groups_6c.html + - /docs/c/ +- + - /2/api-reference/search/groups_6d.html + - /docs/c/ +- + - /2/api-reference/search/groups_6e.html + - /docs/c/ +- + - /2/api-reference/search/groups_7.html + - /docs/c/ +- + - /2/api-reference/search/groups_70.html + - /docs/c/ +- + - /2/api-reference/search/groups_72.html + - /docs/c/ +- + - /2/api-reference/search/groups_73.html + - /docs/c/ +- + - /2/api-reference/search/groups_74.html + - /docs/c/ +- + - /2/api-reference/search/groups_75.html + - /docs/c/ +- + - /2/api-reference/search/groups_76.html + - /docs/c/ +- + - /2/api-reference/search/groups_77.html + - /docs/c/ +- + - /2/api-reference/search/groups_8.html + - /docs/c/ +- + - /2/api-reference/search/groups_9.html + - /docs/c/ +- + - /2/api-reference/search/groups_a.html + - /docs/c/ +- + - /2/api-reference/search/groups_b.html + - /docs/c/ +- + - /2/api-reference/search/groups_c.html + - /docs/c/ +- + - /2/api-reference/search/groups_d.html + - /docs/c/ +- + - /2/api-reference/search/groups_e.html + - /docs/c/ +- + - /2/api-reference/search/groups_f.html + - /docs/c/ +- + - /2/api-reference/search/nomatches.html + - /docs/c/ +- + - /2/api-reference/search/pages_0.html + - /docs/c/ +- + - /2/api-reference/search/pages_61.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_0.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_1.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_2.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_3.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_4.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_5.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_6.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_61.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_62.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_63.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_64.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_67.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_69.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_6c.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_6d.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_6e.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_7.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_72.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_73.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_74.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_75.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_77.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_8.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_9.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_a.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_b.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_c.html + - /docs/c/ +- + - /2/api-reference/search/typedefs_d.html + - /docs/c/ +- + - /2/api-reference/search/variables_0.html + - /docs/c/ +- + - /2/api-reference/search/variables_1.html + - /docs/c/ +- + - /2/api-reference/search/variables_10.html + - /docs/c/ +- + - /2/api-reference/search/variables_11.html + - /docs/c/ +- + - /2/api-reference/search/variables_12.html + - /docs/c/ +- + - /2/api-reference/search/variables_13.html + - /docs/c/ +- + - /2/api-reference/search/variables_14.html + - /docs/c/ +- + - /2/api-reference/search/variables_15.html + - /docs/c/ +- + - /2/api-reference/search/variables_2.html + - /docs/c/ +- + - /2/api-reference/search/variables_3.html + - /docs/c/ +- + - /2/api-reference/search/variables_4.html + - /docs/c/ +- + - /2/api-reference/search/variables_5.html + - /docs/c/ +- + - /2/api-reference/search/variables_6.html + - /docs/c/ +- + - /2/api-reference/search/variables_61.html + - /docs/c/ +- + - /2/api-reference/search/variables_62.html + - /docs/c/ +- + - /2/api-reference/search/variables_63.html + - /docs/c/ +- + - /2/api-reference/search/variables_64.html + - /docs/c/ +- + - /2/api-reference/search/variables_65.html + - /docs/c/ +- + - /2/api-reference/search/variables_67.html + - /docs/c/ +- + - /2/api-reference/search/variables_68.html + - /docs/c/ +- + - /2/api-reference/search/variables_69.html + - /docs/c/ +- + - /2/api-reference/search/variables_6b.html + - /docs/c/ +- + - /2/api-reference/search/variables_6c.html + - /docs/c/ +- + - /2/api-reference/search/variables_6d.html + - /docs/c/ +- + - /2/api-reference/search/variables_6e.html + - /docs/c/ +- + - /2/api-reference/search/variables_6f.html + - /docs/c/ +- + - /2/api-reference/search/variables_7.html + - /docs/c/ +- + - /2/api-reference/search/variables_70.html + - /docs/c/ +- + - /2/api-reference/search/variables_72.html + - /docs/c/ +- + - /2/api-reference/search/variables_73.html + - /docs/c/ +- + - /2/api-reference/search/variables_74.html + - /docs/c/ +- + - /2/api-reference/search/variables_75.html + - /docs/c/ +- + - /2/api-reference/search/variables_76.html + - /docs/c/ +- + - /2/api-reference/search/variables_77.html + - /docs/c/ +- + - /2/api-reference/search/variables_78.html + - /docs/c/ +- + - /2/api-reference/search/variables_79.html + - /docs/c/ +- + - /2/api-reference/search/variables_7a.html + - /docs/c/ +- + - /2/api-reference/search/variables_8.html + - /docs/c/ +- + - /2/api-reference/search/variables_9.html + - /docs/c/ +- + - /2/api-reference/search/variables_a.html + - /docs/c/ +- + - /2/api-reference/search/variables_b.html + - /docs/c/ +- + - /2/api-reference/search/variables_c.html + - /docs/c/ +- + - /2/api-reference/search/variables_d.html + - /docs/c/ +- + - /2/api-reference/search/variables_e.html + - /docs/c/ +- + - /2/api-reference/search/variables_f.html + - /docs/c/ +- + - /2/api-reference/struct_app_message_callbacks.html + - /docs/c/group___app_message.html +- + - /2/changelog-2.0.0.html + - /sdk/changelogs/2.0.0/ +- + - /2/changelog-2.0.1.html + - /sdk/changelogs/2.0.1/ +- + - /2/changelog-2.0.2.html + - /sdk/changelogs/2.0.2/ +- + - /2/changelog-2.1.1.html + - /sdk/changelogs/2.1.1/ +- + - /2/changelog-2.1.html + - /sdk/changelogs/2.1/ +- + - /2/changelog-2.2.html + - /sdk/changelogs/2.2/ +- + - /2/changelog-2.3.html + - /sdk/changelogs/2.3/ +- + - /2/changelog-2.4.1.html + - /sdk/changelogs/2.4.1/ +- + - /2/changelog-2.4.html + - /sdk/changelogs/2.4/ +- + - /2/changelog-2.5.html + - /sdk/changelogs/2.5/ +- + - /2/changelog-2.6.1.html + - /sdk/changelogs/2.6.1/ +- + - /2/changelog-2.6.html + - /sdk/changelogs/2.6/ +- + - /2/changelog-2.7.html + - /sdk/changelogs/2.7/ +- + - /2/changelog-beta0.html + - /sdk/changelogs/2.0-BETA0/ +- + - /2/changelog-dp2.html + - /sdk/changelogs/2.0-DP2/ +- + - /2/changelog-dp3.html + - /sdk/changelogs/2.0-DP3/ +- + - /2/design/index.html + - /guides/best-practices/design/ +- + - /2/design/ux-design-guide.html + - /guides/best-practices/design/ +- + - /2/design/ux-platform.html + - /guides/best-practices/design/ +- + - /2/distribute/distributing-apps.html + - /guides/publishing-tools/publish-to-pebble-appstore/ +- + - /2/distribute/publish-to-pebble-app-store.html + - /guides/publishing-tools/publish-to-pebble-appstore/ +- + - /2/distribute/publish-to-pebble-appstore.html + - /guides/publishing-tools/publish-to-pebble-appstore/ +- + - /2/distribute/whitelisting.html + - /guides/publishing-tools/whitelisting/ +- + - /2/getting-started/developer-connection/index.html + - /guides/publishing-tools/developer-connection/ +- + - /2/getting-started/hello-world/index.html + - /getting-started/watchface-tutorial/ +- + - /2/getting-started/index.html + - /sdk/install/ +- + - /2/getting-started/install-pebble-sdk/index.html + - /sdk/install/ +- + - /2/getting-started/linux/index.html + - /sdk/install/linux/ +- + - /2/getting-started/macosx/index.html + - /sdk/install/mac/ +- + - /2/getting-started/pb-sdk/index.html + - /guides/publishing-tools/pebble-tool/ +- + - /2/getting-started/pebble-tool/index.html + - /guides/publishing-tools/pebble-tool/ +- + - /2/getting-started/using-examples/index.html + - /examples/ +- + - /2/getting-started/windows/index.html + - /sdk/install/windows/ +- + - /2/guides/about-pebble-apps.html + - /guides/ +- + - /2/guides/accelerometer.html + - /guides/pebble-apps/sensors/accelerometer/ +- + - /2/guides/anatomy-of-pebble-application.html + - /guides/pebble-apps/app-structure/ +- + - /2/guides/app-phone-communication.html + - /guides/pebble-apps/communications/ +- + - /2/guides/background-guide.html + - /guides/pebble-apps/app-structure/background-guide/ +- + - /guides/pebble-apps/app-structure/background-guide/index.html + - /guides/pebble-apps/background/ +- + - /2/guides/battery-perform-guide.html + - /guides/best-practices/battery-perform-guide/ +- + - /2/guides/compass.html + - /guides/pebble-apps/sensors/magnetometer/ +- + - /2/guides/conceptual-overview.html + - /guides/ +- + - /2/guides/creating-ios-android-apps.html + - /guides/mobile-apps/ +- + - /2/guides/creating-pebble-watchapps.html + - /guides/pebble-apps/ +- + - /2/guides/datalogging-guide.html + - /guides/pebble-apps/communications/pebble-datalogging/ +- + - /2/guides/dataspooling-guide.html + - /guides/pebble-apps/communications/pebble-datalogging/ +- + - /2/guides/developer-guide.html + - /guides/ +- + - /2/guides/developer-roadmap.html + - /guides/ +- + - /2/guides/event-service-guide.html + - /guides/pebble-apps/app-events/ +- + - /2/guides/index.html + - /guides/ +- + - /2/guides/javascript-guide.html + - /guides/js-apps/pebblekit-js/ +- + - /2/guides/managing-resources.html + - /guides/pebble-apps/resources/ +- + - /2/guides/migration-guide.html + - /sdk/migration-guide/ +- + - /2/guides/persistent-storage.html + - /guides/pebble-apps/app-structure/persistent-storage/ +- + - /2/guides/prerequisites.html + - /guides/ +- + - /2/guides/ui-design-framework.html + - /guides/best-practices/design/ +- + - /2/guides/ui-framework.html + - /guides/pebble-apps/display-and-animations/layers/ +- + - /2/guides/ux-fonts.html + - /guides/pebble-apps/display-and-animations/ux-fonts/ +- + - /2/guides/wakeup.html + - /guides/pebble-apps/app-events/wakeup/ +- + - /2/index.html + - /sdk/install/ +- + - /2/ios-reference/Categories/NSDataPebble.html + - /docs/pebblekit-ios/Categories/NSData+Pebble/ +- + - /2/ios-reference/Categories/NSDictionaryPebble.html + - /docs/pebblekit-ios/Categories/NSDictionary+Pebble/ +- + - /2/ios-reference/Categories/NSErrorPebble.html + - /docs/pebblekit-ios/Categories/NSError+Pebble/ +- + - /2/ios-reference/Categories/NSJSONSerializationPBJSONHelpers.html + - /docs/pebblekit-ios/ +- + - /2/ios-reference/Categories/NSNumberstdint.html + - /docs/pebblekit-ios/ +- + - /2/ios-reference/Classes/PBBitmap.html + - /docs/pebblekit-ios/Classes/PBBitmap/ +- + - /2/ios-reference/Classes/PBDataLoggingService.html + - /docs/pebblekit-ios/Classes/PBDataLoggingService/ +- + - /2/ios-reference/Classes/PBDataLoggingSessionMetadata.html + - /docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata/ +- + - /2/ios-reference/Classes/PBDataSpoolingService.html + - /docs/pebblekit-ios/Classes/PBDataLoggingService/ +- + - /2/ios-reference/Classes/PBDataSpoolMetadata.html + - /docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata/ +- + - /2/ios-reference/Classes/PBFirmwareMetadata.html + - /docs/pebblekit-ios/Classes/PBFirmwareMetadata/ +- + - /2/ios-reference/Classes/PBFirmwareVersion.html + - /docs/pebblekit-ios/Classes/PBFirmwareVersion/ +- + - /2/ios-reference/Classes/PBPebbleCentral.html + - /docs/pebblekit-ios/Classes/PBPebbleCentral/ +- + - /2/ios-reference/Classes/PBResourceMetadata.html + - /docs/pebblekit-ios/Classes/PBResourceMetadata/ +- + - /2/ios-reference/Classes/PBSportsUpdate.html + - /docs/pebblekit-ios/Classes/PBSportsUpdate/ +- + - /2/ios-reference/Classes/PBVersionInfo.html + - /docs/pebblekit-ios/Classes/PBVersionInfo/ +- + - /2/ios-reference/Classes/PBWatch.html + - /docs/pebblekit-ios/Classes/PBWatch/ +- + - /2/ios-reference/Constants/PBAppState.html + - /docs/pebblekit-ios/Constants/PBAppState/ +- + - /2/ios-reference/hierarchy.html + - /docs/pebblekit-ios/hierarchy/ +- + - /2/ios-reference/index.html + - /docs/pebblekit-ios/ +- + - /2/ios-reference/Protocols/PBDataLoggingServiceDelegate.html + - /docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/ +- + - /2/ios-reference/Protocols/PBDataSpoolingServiceDelegate.html + - /docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/ +- + - /2/ios-reference/Protocols/PBPebbleCentralDelegate.html + - /docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate/ +- + - /2/ios-reference/Protocols/PBPhoneCallSimulatorDelegate.html + - /docs/pebblekit-ios/ +- + - /2/ios-reference/Protocols/PBWatchDelegate.html + - /docs/pebblekit-ios/Protocols/PBWatchDelegate/ +- + - /2/mobile-app-guide/android-guide.html + - /guides/mobile-apps/android/ +- + - /2/mobile-app-guide/index.html + - /guides/mobile-apps/ +- + - /2/mobile-app-guide/ios-guide.html + - /guides/mobile-apps/ios/ +- + - /2/releasenotice-beta1.html + - /sdk/changelogs/2.0-BETA1/ +- + - /2/releasenotice-beta2.html + - /sdk/changelogs/2.0-BETA2/ +- + - /2/releasenotice-beta3.html + - /sdk/changelogs/2.0-BETA3/ +- + - /2/releasenotice-beta4.html + - /sdk/changelogs/2.0-BETA4/ +- + - /2/releasenotice-beta5.html + - /sdk/changelogs/2.0-BETA5/ +- + - /2/releasenotice-beta6.html + - /sdk/changelogs/2.0-BETA6/ +- + - /2/releasenotice-beta7.html + - /sdk/changelogs/2.0-BETA7/ +- + - /blog/13/index.html + - /blog/archive/ +- + - /blog/2013/07/02/Lots-of-goodies-In-Pebble-SDK-1.12/index.html + - /blog/2013/07/03/Lots-of-goodies-In-Pebble-SDK-1.12/ +- + - /blog/2013/12/19/Javascript-Tips-and-Tricks/index.html + - /blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks/ +- + - /blog/2014/03/07/Pebble-App-Challenge/index.html + - /blog/2014/03/10/Pebble-App-Challenge/ +- + - /blog/2014/04/18/Learn-to-Program-My-Pebble/index.html + - /blog/2014/04/30/Learn-to-Program-My-Pebble/ +- + - /blog/2014/05/22/FreeRTOS-Modifications-From-Pebble/index.html + - /blog/2014/05/23/FreeRTOS-Modifications-From-Pebble/ +- + - /blog/2014/06/02/Android-Beta-Channel/index.html + - /blog/2014/06/12/Android-Beta-Channel/ +- + - /blog/2014/07/20/Pebble-XDA-Challenge/index.html + - /blog/2014/07/21/Pebble-XDA-Challenge/ +- + - /blog/archive.html + - /blog/archive/ +- + - /blog/archives/index.html + - /blog/archive/ +- + - /download-sdk/hello-world/index.html + - /getting-started/ +- + - /download-sdk/index.html + - /sdk/ +- + - /download-sdk/linux/index.html + - /sdk/install/linux/ +- + - /download-sdk/macosx/index.html + - /sdk/install/mac/ +- + - /download-sdk/windows/index.html + - /sdk/install/windows/ +- + - /events/aahackathon/index.html + - /community/events/aahackathon/ +- + - /events/developer-retreat-2013/index.html + - /community/events/developer-retreat-2013/ +- + - /events/developer-retreat-2014/detailed-schedule/index.html + - /community/events/developer-retreat-2014/detailed-schedule/ +- + - /events/developer-retreat-2014/index.html + - /community/events/developer-retreat-2014/ +- + - /events/index.html + - /community/events/ +- + - /community/events/meetups/index.html + - /community/events/ +- + - /getting-started/pebble-js-tutorial/part1.html + - /getting-started/pebble-js-tutorial/part1/ +- + - /getting-started/pebble-js-tutorial/part2.html + - /getting-started/pebble-js-tutorial/part2/ +- + - /getting-started/pebble-js-tutorial/part3.html + - /getting-started/pebble-js-tutorial/part3/ +- + - /getting-started/watchface-tutorial/part1.html + - /getting-started/watchface-tutorial/part1/ +- + - /getting-started/watchface-tutorial/part2.html + - /getting-started/watchface-tutorial/part2/ +- + - /getting-started/watchface-tutorial/part3.html + - /getting-started/watchface-tutorial/part3/ +- + - /hacktech.html + - /community/events/ +- + - /help/index.html + - /community/#stackoverflow +- + - /iossdkref/Categories/NSDataPebble.html + - /docs/pebblekit-ios/Categories/NSData+Pebble/ +- + - /iossdkref/Categories/NSDictionaryPebble.html + - /docs/pebblekit-ios/Categories/NSDictionary+Pebble/ +- + - /iossdkref/Categories/NSErrorPebble.html + - /docs/pebblekit-ios/Categories/NSError+Pebble/ +- + - /iossdkref/Categories/NSNumberstdint.html + - /docs/pebblekit-ios/ +- + - /iossdkref/Classes/PBBitmap.html + - /docs/pebblekit-ios/Classes/PBBitmap/ +- + - /iossdkref/Classes/PBFirmwareMetadata.html + - /docs/pebblekit-ios/Classes/PBFirmwareMetadata/ +- + - /iossdkref/Classes/PBFirmwareVersion.html + - /docs/pebblekit-ios/Classes/PBFirmwareVersion/ +- + - /iossdkref/Classes/PBPebbleCentral.html + - /docs/pebblekit-ios/Classes/PBPebbleCentral/ +- + - /iossdkref/Classes/PBResourceMetadata.html + - /docs/pebblekit-ios/Classes/PBResourceMetadata/ +- + - /iossdkref/Classes/PBSportsUpdate.html + - /docs/pebblekit-ios/Classes/PBSportsUpdate/ +- + - /iossdkref/Classes/PBVersionInfo.html + - /docs/pebblekit-ios/Classes/PBVersionInfo/ +- + - /iossdkref/Classes/PBWatch.html + - /docs/pebblekit-ios/Classes/PBWatch/ +- + - /iossdkref/hierarchy.html + - /docs/pebblekit-ios/hierarchy/ +- + - /iossdkref/index.html + - /docs/pebblekit-ios/ +- + - /iossdkref/Protocols/PBPebbleCentralDelegate.html + - /docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate/ +- + - /iossdkref/Protocols/PBWatchDelegate.html + - /docs/pebblekit-ios/Protocols/PBWatchDelegate/ +- + - /pennapps/index.html + - /blog/2014/03/20/pebble-goes-to-pennapps/ +- + - /sdkref/_a_p_i_changelog.html + - /docs/c/ +- + - /sdkref/annotated.html + - /docs/c/ +- + - /sdkref/classes.html + - /docs/c/ +- + - /sdkref/deprecated.html + - /docs/c/ +- + - /sdkref/dir_12afb154e95f5ba9bfd17ab65812e041.html + - /docs/c/ +- + - /sdkref/dir_15848a8d8d88d7953349a1e66f00cc04.html + - /docs/c/ +- + - /sdkref/dir_195aa4215760a77c8f43641b003b8f9c.html + - /docs/c/ +- + - /sdkref/dir_1e13eb70021b1d47a39bb763c978d2ce.html + - /docs/c/ +- + - /sdkref/dir_216c439117919bd6d1adc992c7f3b723.html + - /docs/c/ +- + - /sdkref/dir_243a4ba6b4f893d71afbb5a3e90552fa.html + - /docs/c/ +- + - /sdkref/dir_27fab2247fd80ac2fd263abffd8e1ad2.html + - /docs/c/ +- + - /sdkref/dir_2cdcb59b0cca3e640f5c28bb8465114a.html + - /docs/c/ +- + - /sdkref/dir_4fc88152649b3a7b0043869ddaf105b0.html + - /docs/c/ +- + - /sdkref/dir_63fecb0f73dd89b61c12f9cdbcf5ac7b.html + - /docs/c/ +- + - /sdkref/dir_68267d1309a1af8e8297ef4c3efbcdba.html + - /docs/c/ +- + - /sdkref/dir_9ba741889d9ec19694f6ba9fcf5ef84a.html + - /docs/c/ +- + - /sdkref/dir_aebb8dcc11953d78e620bbef0b9e2183.html + - /docs/c/ +- + - /sdkref/dir_c606c3a6e71085b9c99fda67e11b8127.html + - /docs/c/ +- + - /sdkref/dir_d1b33b83e9183a627b5a5eb67373ce17.html + - /docs/c/ +- + - /sdkref/dir_e322425b022fc9be25271df294647aa3.html + - /docs/c/ +- + - /sdkref/dir_f2f479b6cf6f4b2ff244ee0a1c5fadbb.html + - /docs/c/ +- + - /sdkref/functions_vars.html + - /docs/c/ +- + - /sdkref/functions.html + - /docs/c/ +- + - /sdkref/group___action_bar_layer.html + - /docs/c/group___action_bar_layer.html +- + - /sdkref/group___animation.html + - /docs/c/group___animation.html +- + - /sdkref/group___app_comm.html + - /docs/c/group___app_comm.html +- + - /sdkref/group___app_message.html + - /docs/c/group___app_message.html +- + - /sdkref/group___app_sync.html + - /docs/c/group___app_sync.html +- + - /sdkref/group___app.html + - /docs/c/group___app.html +- + - /sdkref/group___bitmap_layer.html + - /docs/c/group___bitmap_layer.html +- + - /sdkref/group___clicks.html + - /docs/c/group___clicks.html +- + - /sdkref/group___dictionary.html + - /docs/c/group___dictionary.html +- + - /sdkref/group___drawing.html + - /docs/c/group___drawing.html +- + - /sdkref/group___fonts.html + - /docs/c/group___fonts.html +- + - /sdkref/group___foundation.html + - /docs/c/group___foundation.html +- + - /sdkref/group___graphics_context.html + - /docs/c/group___graphics_context.html +- + - /sdkref/group___graphics_types.html + - /docs/c/group___graphics_types.html +- + - /sdkref/group___graphics.html + - /docs/c/group___graphics.html +- + - /sdkref/group___inverter_layer.html + - /docs/c/group___inverter_layer.html +- + - /sdkref/group___layer.html + - /docs/c/group___layer.html +- + - /sdkref/group___light.html + - /docs/c/group___light.html +- + - /sdkref/group___logging.html + - /docs/c/group___logging.html +- + - /sdkref/group___math.html + - /docs/c/group___math.html +- + - /sdkref/group___media_utils.html + - /docs/c/ +- + - /sdkref/group___menu_layer.html + - /docs/c/group___menu_layer.html +- + - /sdkref/group___number_window.html + - /docs/c/group___number_window.html +- + - /sdkref/group___path_drawing.html + - /docs/c/group___path_drawing.html +- + - /sdkref/group___property_animation.html + - /docs/c/group___property_animation.html +- + - /sdkref/group___resources.html + - /docs/c/group___resources.html +- + - /sdkref/group___scroll_layer.html + - /docs/c/group___scroll_layer.html +- + - /sdkref/group___simple_menu_layer.html + - /docs/c/group___simple_menu_layer.html +- + - /sdkref/group___standard_c.html + - /docs/c/group___standard_c.html +- + - /sdkref/group___standard_i_o.html + - /docs/c/group___standard_i_o.html +- + - /sdkref/group___standard_math.html + - /docs/c/group___standard_math.html +- + - /sdkref/group___standard_memory.html + - /docs/c/group___standard_memory.html +- + - /sdkref/group___standard_string.html + - /docs/c/group___standard_string.html +- + - /sdkref/group___standard_time.html + - /docs/c/group___standard_time.html +- + - /sdkref/group___text_drawing.html + - /docs/c/group___text_drawing.html +- + - /sdkref/group___text_layer.html + - /docs/c/group___text_layer.html +- + - /sdkref/group___timer.html + - /docs/c/group___timer.html +- + - /sdkref/group___u_i.html + - /docs/c/group___u_i.html +- + - /sdkref/group___vibes.html + - /docs/c/group___vibes.html +- + - /sdkref/group___wall_time.html + - /docs/c/group___wall_time.html +- + - /sdkref/group___window_stack.html + - /docs/c/group___window_stack.html +- + - /sdkref/group___window.html + - /docs/c/group___window.html +- + - /sdkref/index.html + - /docs/c/ +- + - /sdkref/modules.html + - /docs/c/ +- + - /sdkref/pages.html + - /docs/c/ +- + - /sdkref/search/all_61.html + - /docs/c/ +- + - /sdkref/search/all_62.html + - /docs/c/ +- + - /sdkref/search/all_63.html + - /docs/c/ +- + - /sdkref/search/all_64.html + - /docs/c/ +- + - /sdkref/search/all_65.html + - /docs/c/ +- + - /sdkref/search/all_66.html + - /docs/c/ +- + - /sdkref/search/all_67.html + - /docs/c/ +- + - /sdkref/search/all_68.html + - /docs/c/ +- + - /sdkref/search/all_69.html + - /docs/c/ +- + - /sdkref/search/all_6b.html + - /docs/c/ +- + - /sdkref/search/all_6c.html + - /docs/c/ +- + - /sdkref/search/all_6d.html + - /docs/c/ +- + - /sdkref/search/all_6e.html + - /docs/c/ +- + - /sdkref/search/all_6f.html + - /docs/c/ +- + - /sdkref/search/all_70.html + - /docs/c/ +- + - /sdkref/search/all_72.html + - /docs/c/ +- + - /sdkref/search/all_73.html + - /docs/c/ +- + - /sdkref/search/all_74.html + - /docs/c/ +- + - /sdkref/search/all_75.html + - /docs/c/ +- + - /sdkref/search/all_76.html + - /docs/c/ +- + - /sdkref/search/all_77.html + - /docs/c/ +- + - /sdkref/search/all_78.html + - /docs/c/ +- + - /sdkref/search/all_79.html + - /docs/c/ +- + - /sdkref/search/classes_61.html + - /docs/c/ +- + - /sdkref/search/classes_62.html + - /docs/c/ +- + - /sdkref/search/classes_63.html + - /docs/c/ +- + - /sdkref/search/classes_64.html + - /docs/c/ +- + - /sdkref/search/classes_67.html + - /docs/c/ +- + - /sdkref/search/classes_68.html + - /docs/c/ +- + - /sdkref/search/classes_69.html + - /docs/c/ +- + - /sdkref/search/classes_6c.html + - /docs/c/ +- + - /sdkref/search/classes_6d.html + - /docs/c/ +- + - /sdkref/search/classes_6e.html + - /docs/c/ +- + - /sdkref/search/classes_70.html + - /docs/c/ +- + - /sdkref/search/classes_72.html + - /docs/c/ +- + - /sdkref/search/classes_73.html + - /docs/c/ +- + - /sdkref/search/classes_74.html + - /docs/c/ +- + - /sdkref/search/classes_76.html + - /docs/c/ +- + - /sdkref/search/classes_77.html + - /docs/c/ +- + - /sdkref/search/enums_61.html + - /docs/c/ +- + - /sdkref/search/enums_62.html + - /docs/c/ +- + - /sdkref/search/enums_64.html + - /docs/c/ +- + - /sdkref/search/enums_67.html + - /docs/c/ +- + - /sdkref/search/enums_6d.html + - /docs/c/ +- + - /sdkref/search/enums_70.html + - /docs/c/ +- + - /sdkref/search/enums_73.html + - /docs/c/ +- + - /sdkref/search/enums_74.html + - /docs/c/ +- + - /sdkref/search/enumvalues_61.html + - /docs/c/ +- + - /sdkref/search/enumvalues_62.html + - /docs/c/ +- + - /sdkref/search/enumvalues_64.html + - /docs/c/ +- + - /sdkref/search/enumvalues_67.html + - /docs/c/ +- + - /sdkref/search/enumvalues_68.html + - /docs/c/ +- + - /sdkref/search/enumvalues_6d.html + - /docs/c/ +- + - /sdkref/search/enumvalues_6e.html + - /docs/c/ +- + - /sdkref/search/enumvalues_73.html + - /docs/c/ +- + - /sdkref/search/enumvalues_74.html + - /docs/c/ +- + - /sdkref/search/enumvalues_79.html + - /docs/c/ +- + - /sdkref/search/functions_61.html + - /docs/c/ +- + - /sdkref/search/functions_62.html + - /docs/c/ +- + - /sdkref/search/functions_63.html + - /docs/c/ +- + - /sdkref/search/functions_64.html + - /docs/c/ +- + - /sdkref/search/functions_66.html + - /docs/c/ +- + - /sdkref/search/functions_67.html + - /docs/c/ +- + - /sdkref/search/functions_68.html + - /docs/c/ +- + - /sdkref/search/functions_69.html + - /docs/c/ +- + - /sdkref/search/functions_6c.html + - /docs/c/ +- + - /sdkref/search/functions_6d.html + - /docs/c/ +- + - /sdkref/search/functions_6e.html + - /docs/c/ +- + - /sdkref/search/functions_70.html + - /docs/c/ +- + - /sdkref/search/functions_72.html + - /docs/c/ +- + - /sdkref/search/functions_73.html + - /docs/c/ +- + - /sdkref/search/functions_74.html + - /docs/c/ +- + - /sdkref/search/functions_76.html + - /docs/c/ +- + - /sdkref/search/functions_77.html + - /docs/c/ +- + - /sdkref/search/groups_61.html + - /docs/c/ +- + - /sdkref/search/groups_62.html + - /docs/c/ +- + - /sdkref/search/groups_63.html + - /docs/c/ +- + - /sdkref/search/groups_64.html + - /docs/c/ +- + - /sdkref/search/groups_66.html + - /docs/c/ +- + - /sdkref/search/groups_67.html + - /docs/c/ +- + - /sdkref/search/groups_69.html + - /docs/c/ +- + - /sdkref/search/groups_6c.html + - /docs/c/ +- + - /sdkref/search/groups_6d.html + - /docs/c/ +- + - /sdkref/search/groups_6e.html + - /docs/c/ +- + - /sdkref/search/groups_70.html + - /docs/c/ +- + - /sdkref/search/groups_72.html + - /docs/c/ +- + - /sdkref/search/groups_73.html + - /docs/c/ +- + - /sdkref/search/groups_74.html + - /docs/c/ +- + - /sdkref/search/groups_75.html + - /docs/c/ +- + - /sdkref/search/groups_76.html + - /docs/c/ +- + - /sdkref/search/groups_77.html + - /docs/c/ +- + - /sdkref/search/nomatches.html + - /docs/c/ +- + - /sdkref/search/pages_61.html + - /docs/c/ +- + - /sdkref/search/pages_64.html + - /docs/c/ +- + - /sdkref/search/typedefs_61.html + - /docs/c/ +- + - /sdkref/search/typedefs_63.html + - /docs/c/ +- + - /sdkref/search/typedefs_64.html + - /docs/c/ +- + - /sdkref/search/typedefs_67.html + - /docs/c/ +- + - /sdkref/search/typedefs_69.html + - /docs/c/ +- + - /sdkref/search/typedefs_6c.html + - /docs/c/ +- + - /sdkref/search/typedefs_6d.html + - /docs/c/ +- + - /sdkref/search/typedefs_6e.html + - /docs/c/ +- + - /sdkref/search/typedefs_70.html + - /docs/c/ +- + - /sdkref/search/typedefs_72.html + - /docs/c/ +- + - /sdkref/search/typedefs_73.html + - /docs/c/ +- + - /sdkref/search/typedefs_74.html + - /docs/c/ +- + - /sdkref/search/typedefs_75.html + - /docs/c/ +- + - /sdkref/search/typedefs_77.html + - /docs/c/ +- + - /sdkref/search/variables_61.html + - /docs/c/ +- + - /sdkref/search/variables_62.html + - /docs/c/ +- + - /sdkref/search/variables_63.html + - /docs/c/ +- + - /sdkref/search/variables_64.html + - /docs/c/ +- + - /sdkref/search/variables_65.html + - /docs/c/ +- + - /sdkref/search/variables_67.html + - /docs/c/ +- + - /sdkref/search/variables_68.html + - /docs/c/ +- + - /sdkref/search/variables_69.html + - /docs/c/ +- + - /sdkref/search/variables_6b.html + - /docs/c/ +- + - /sdkref/search/variables_6c.html + - /docs/c/ +- + - /sdkref/search/variables_6d.html + - /docs/c/ +- + - /sdkref/search/variables_6e.html + - /docs/c/ +- + - /sdkref/search/variables_6f.html + - /docs/c/ +- + - /sdkref/search/variables_70.html + - /docs/c/ +- + - /sdkref/search/variables_72.html + - /docs/c/ +- + - /sdkref/search/variables_73.html + - /docs/c/ +- + - /sdkref/search/variables_74.html + - /docs/c/ +- + - /sdkref/search/variables_75.html + - /docs/c/ +- + - /sdkref/search/variables_76.html + - /docs/c/ +- + - /sdkref/search/variables_77.html + - /docs/c/ +- + - /sdkref/search/variables_78.html + - /docs/c/ +- + - /sdkref/search/variables_79.html + - /docs/c/ +- + - /sdkref/struct_app_message_callbacks.html + - /docs/c/group___app_message.html +- + - /sdkref/struct_progress_layer.html + - /docs/c/ +- + - /sdkref/struct_rot_bmp_pair_layer.html + - /docs/c/group___rot_bitmap_layer.html +- + - /guides/pebble-apps/display-and-animations/resources/index.html + - /guides/pebble-apps/resources/ +- + - /blog/archive/index.html + - /blog/ +- + - /guides/js-apps/pebblekit-js/index.html + - /guides/pebble-apps/pebblekit-js/ +- + - /guides/js-apps/pebblekit-js/adding-js/index.html + - /guides/pebble-apps/pebblekit-js/adding-js/ +- + - /guides/js-apps/pebblekit-js/js-app-comm/index.html + - /guides/pebble-apps/pebblekit-js/js-app-comm/ +- + - /guides/js-apps/pebblekit-js/js-capabilities/index.html + - /guides/pebble-apps/pebblekit-js/js-capabilities/ +- + - /guides/js-apps/pebblekit-js/app-configuration/index.html + - /guides/pebble-apps/pebblekit-js/app-configuration/ +- + - /guides/js-apps/pebble-js/index.html + - /guides/js-apps/ +- + - /guides/js-apps/pebble-js/js-ui/index.html + - /guides/js-apps/js-ui/ +- + - /guides/js-apps/pebble-js/js-ajax/index.html + - /guides/js-apps/js-ajax/ +- + - /guides/js-apps/pebble-js/js-other/index.html + - /guides/js-apps/js-other/ +- + - /getting-started/index.html + - /tutorials/ +- + - /getting-started/watchface-tutorial/index.html + - /tutorials/watchface-tutorial/part1/ +- + - /getting-started/watchface-tutorial/part1/index.html + - /tutorials/watchface-tutorial/part1/ +- + - /getting-started/watchface-tutorial/part2/index.html + - /tutorials/watchface-tutorial/part2/ +- + - /getting-started/watchface-tutorial/part3/index.html + - /tutorials/watchface-tutorial/part3/ +- + - /getting-started/pebble-js-tutorial/index.html + - /tutorials/pebble-js-tutorial/part1/ +- + - /getting-started/pebble-js-tutorial/part1/index.html + - /tutorials/pebble-js-tutorial/part1/ +- + - /getting-started/pebble-js-tutorial/part2/index.html + - /tutorials/pebble-js-tutorial/part2/ +- + - /getting-started/pebble-js-tutorial/part3/index.html + - /tutorials/pebble-js-tutorial/part3/ +- + - /getting-started/android-tutorial/index.html + - /guides/communication/using-pebblekit-android/ +- + - /getting-started/android-tutorial/part1/index.html + - /guides/communication/using-pebblekit-android/ +- + - /getting-started/android-tutorial/part2/index.html + - /guides/communication/using-pebblekit-android/ +- + - /getting-started/android-tutorial/part3/index.html + - /guides/communication/using-pebblekit-android/ +- + - /tutorials/android-tutorial/part1/ + - /guides/communication/using-pebblekit-android/ +- + - /tutorials/android-tutorial/part2/ + - /guides/communication/using-pebblekit-android/ +- + - /tutorials/android-tutorial/part3/ + - /guides/communication/using-pebblekit-android/ +- + - /getting-started/ios-tutorial/index.html + - /guides/communication/using-pebblekit-ios/ +- + - /getting-started/ios-tutorial/part1/index.html + - /guides/communication/using-pebblekit-ios/ +- + - /getting-started/ios-tutorial/part2/index.html + - /guides/communication/using-pebblekit-ios/ +- + - /getting-started/ios-tutorial/part3/index.html + - /guides/communication/using-pebblekit-ios/ +- + - /tutorials/ios-tutorial/part1/ + - /guides/communication/using-pebblekit-ios/ +- + - /tutorials/ios-tutorial/part2/ + - /guides/communication/using-pebblekit-ios/ +- + - /tutorials/ios-tutorial/part3/ + - /guides/communication/using-pebblekit-ios/ +- + - /smartstraps/index.html + - /guides/hardware/ +- + - /tools/color-picker/index.html + - /more/color-picker/ +- + - /color-picker/index.html + - /more/color-picker/ +- + - /sdk/migration-guide/index.html + - /guides/migration/migration-guide-3/ +- + - /sdk/migration-guide-2/index.html + - /guides/migration/migration-guide/ +- + - /sdk/whats-new/index.html + - /sdk/changelogs/ +- + - /blog/2015/09/23/Pebble-Time-Round-SDK-Developer-Preview-1/index.html + - /blog/2015/09/23/Pebble-Time-Round-SDK-Developer-Preview-2/ +- + - /guides/publishing-tools/pebble-trademark/index.html + - /legal/pebble-trademark/ +- + - /guides/publishing-tools/whitelisting/index.html + - /guides/appstore-publishing/whitelisting/ +- + - /guides/pebble-timeline/timeline-enabling/index.html + - /guides/pebble-timeline/ +- + - /guides/best-practices/battery-perform-guide/index.html + - /guides/best-practices/conserving-battery-life/ +- + - /guides/best-practices/design/index.html + - /guides/design-and-interaction/ +- + - /sdk/beta/index.html + - /sdk/download/ +- + - /guides/pebble-apps/sensors/health/index.html + - /guides/events-and-services/health/ +- + - /guides/best-practices/multi-platform/index.html + - /guides/best-practices/building-for-every-pebble/ +- + - /guides/js-apps/index.html + - /guides/ +- + - /guides/mobile-apps/index.html + - /guides/communication/about-pebble-communication/ +- + - /guides/mobile-apps/android/index.html + - /guides/communication/ +- + - /guides/mobile-apps/android/android-advanced/index.html + - /guides/communication/ +- + - /guides/mobile-apps/android/android-comms/index.html + - /guides/communication/ +- + - /guides/mobile-apps/android/android-datalogging/index.html + - /guides/communication/datalogging/ +- + - /guides/mobile-apps/android/android-examples/index.html + - /guides/communication/ +- + - /guides/mobile-apps/android/android-sports/index.html + - /guides/communication/using-the-sports-api/ +- + - /guides/mobile-apps/ios/index.html + - /guides/communication/ +- + - /guides/mobile-apps/ios/ios-comms/index.html + - /guides/communication/ +- + - /guides/mobile-apps/ios/ios-datalogging/index.html + - /guides/communication/datalogging/ +- + - /guides/mobile-apps/ios/ios-install/index.html + - /guides/communication/using-pebblekit-android-or-ios/ +- + - /guides/mobile-apps/ios/ios-sports/index.html + - /guides/communication/ +- + - /guides/pebble-apps/index.html + - /guides/ +- + - /guides/pebble-apps/app-events/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-events/app-interruptions/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-events/battery-service/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-events/bluetooth-connection/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-events/tick-timer/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-events/wakeup/index.html + - /guides/user-interfaces/events/ +- + - /guides/pebble-apps/app-structure/index.html + - /guides/user-interfaces/ +- + - /guides/pebble-apps/app-structure/clicks/index.html + - /guides/events-and-services/buttons/ +- + - /guides/pebble-apps/app-structure/persistent-storage/index.html + - /guides/user-interfaces/persistent-storage/ +- + - /guides/pebble-apps/app-structure/windows/index.html + - /guides/user-interfaces/layers/ +- + - /guides/pebble-apps/background/index.html + - /guides/user-interfaces/background-worker/ +- + - /guides/pebble-apps/communications/index.html + - /guides/communication/ +- + - /guides/pebble-apps/communications/appmessage/index.html + - /guides/communication/sending-and-receiving-data/ +- + - /guides/pebble-apps/communications/appsync/index.html + - /guides/communication/sending-and-receiving-data/ +- + - /guides/pebble-apps/communications/pebble-datalogging/index.html + - /guides/communication/datalogging/ +- + - /guides/pebble-apps/debugging/index.html + - /guides/debugging/ +- + - /guides/pebble-apps/debugging/runtime-problems/index.html + - /guides/debugging/common-runtime-errors/ +- + - /guides/pebble-apps/debugging/common-c-problems/index.html + - /guides/debugging/common-syntax-errors/ +- + - /guides/pebble-apps/debugging/debugging-guide/index.html + - /guides/debugging/debugging-with-app-logs/ +- + - /guides/pebble-apps/display-and-animations/index.html + - /guides/graphics-and-animations/ +- + - /guides/pebble-apps/display-and-animations/action-menu/index.html + - /guides/user-interfaces/ +- + - /guides/pebble-apps/display-and-animations/circular-graphics/index.html + - /guides/user-interfaces/creating-round-apps/ +- + - /guides/pebble-apps/display-and-animations/composite-animations/index.html + - /guides/graphics-and-animations/animations/ +- + - /guides/pebble-apps/display-and-animations/drawing/index.html + - /guides/graphics-and-animations/drawing-primitives-images-and-text/ +- + - /guides/pebble-apps/display-and-animations/intro-to-colors/index.html + - /guides/graphics-and-animations/ +- + - /guides/pebble-apps/display-and-animations/layers/index.html + - /guides/user-interfaces/layers/ +- + - /guides/pebble-apps/display-and-animations/property-animations/index.html + - /guides/graphics-and-animations/animations/ +- + - /guides/pebble-apps/display-and-animations/ux-fonts/index.html + - /guides/app-resources/fonts/ +- + - /guides/pebble-apps/pebblekit-js/index.html + - /guides/communication/using-pebblekit-js/ +- + - /guides/pebble-apps/pebblekit-js/adding-js/index.html + - /guides/communication/using-pebblekit-js/ +- + - /guides/pebble-apps/pebblekit-js/app-configuration/index.html + - /guides/user-interfaces/app-configuration/ +- + - /guides/pebble-apps/pebblekit-js/js-app-comm/index.html + - /guides/communication/using-pebblekit-js/ +- + - /guides/pebble-apps/pebblekit-js/js-capabilities/index.html + - /guides/communication/using-pebblekit-js/ +- + - /guides/pebble-apps/resources/index.html + - /guides/app-resources/ +- + - /guides/pebble-apps/resources/animated-pngs/index.html + - /guides/app-resources/animated-images/ +- + - /guides/pebble-apps/resources/font-resources/index.html + - /guides/app-resources/fonts/ +- + - /guides/pebble-apps/resources/image-resources/index.html + - /guides/app-resources/images/ +- + - /guides/pebble-apps/resources/raw-resources/index.html + - /guides/app-resources/raw-data-files/ +- + - /guides/pebble-apps/sensors/index.html + - /guides/events-and-services/ +- + - /guides/pebble-apps/sensors/accelerometer/index.html + - /guides/events-and-services/accelerometer/ +- + - /guides/pebble-apps/sensors/dictation/index.html + - /guides/events-and-services/dictation/ +- + - /guides/pebble-apps/sensors/magnetometer/index.html + - /guides/events-and-services/compass/ +- + - /guides/hardware/index.html + - /guides/smartstraps/ +- + - /guides/hardware/smartstrap-hardware/index.html + - /guides/smartstraps/smartstrap-hardware/ +- + - /guides/hardware/smartstrap-protocol/index.html + - /guides/smartstraps/smartstrap-protocol/ +- + - /guides/hardware/talking-to-pebble/index.html + - /guides/smartstraps/takling-to-pebble/ +- + - /guides/hardware/talking-to-smartstraps/index.html + - /guides/smartstraps/takling-to-smartstraps/ +- + - /guides/timeline/index.html + - /guides/pebble-timeline/ +- + - /guides/timeline/pin-structure/index.html + - /guides/pebble-timeline/pin-structure/ +- + - /guides/timeline/timeline-architecture/index.html + - /guides/pebble-timeline/timeline-architecture/ +- + - /guides/timeline/timeline-enabling/index.html + - /guides/pebble-timeline/timeline-enabling/ +- + - /guides/timeline/timeline-js/index.html + - /guides/pebble-timeline/timeline-js/ +- + - /guides/timeline/timeline-libraries/index.html + - /guides/pebble-timeline/timeline-libraries/ +- + - /guides/timeline/timeline-public/index.html + - /guides/pebble-timeline/timeline-public/ +- + - /guides/publishing-tools/index.html + - /guides/ +- + - /guides/publishing-tools/cloudpebble-ui-editor/index.html + - /guides/tools-and-resources/cloudpebble/ +- + - /guides/publishing-tools/developer-connection/index.html + - /guides/tools-and-resources/developer-connection/ +- + - /guides/publishing-tools/i18n-guide/index.html + - /guides/tools-and-resources/internationalization/ +- + - /guides/publishing-tools/pebble-tool/index.html + - /guides/tools-and-resources/pebble-tool/ +- + - /guides/publishing-tools/pebble-trademark/index.html + - /guides/publishing-tools/ +- + - /guides/publishing-tools/publish-to-pebble-appstore/index.html + - /guides/appstore-publishing/ +- + - /guides/publishing-tools/publishing-faq/index.html + - /guides/appstore-publishing/ +- + - /guides/publishing-tools/screenshots/index.html + - /guides/appstore-publishing/ +- + - /guides/publishing-tools/whitelisting/index.html + - /guides/appstore-publishing/ +- + - /guides/publishing-tools/pebble-trademark/index.html + - /legal/pebble-trademark/ +- + - /guides/publishing-tools/whitelisting/index.html + - /guides/appstore-publishing/whitelisting/ +- + - /more/color-picker/index.html + - /guides/tools-and-resources/color-picker/ +- + - /guides/pebble-apps/app-structure/app-metadata/index.html + - /guides/tools-and-resources/app-metadata/ +- + - /guides/user-interfaces/events/index.html + - /guides/events-and-services/events/ +- + - /guides/graphics-and-animations/converting-svg-to-pdc/index.html + - /guides/app-resources/converting-svg-to-pdc/ +- + - /docs/pebblejs/index.html + - http://pebble.github.io/pebblejs/ +- + - /sdk/round-getting-started/index.html + - /round/getting-started/index.html +- + - /docs/c/preview/User_Interface/Layer + - /docs/c/User_Interface/Layer +- + - /docs/pebblekit-ios/Classes/Protocols/PBDataLoggingServiceDelegate.html + - /docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/ +- + - /docs/pebblekit-ios/Classes/Classes/PBFirmwareVersion.html + - /docs/pebblekit-ios/Classes/PBFirmwareVersion/ +- + - /docs/pebblekit-ios/Protocols/Classes/PBWatch.html + - /docs/pebblekit-ios/Classes/PBWatch/ +- + - /docs/pebblekit-ios/Classes/Protocols/PBWatchDelegate.html + - /docs/pebblekit-ios/Protocols/PBWatchDelegate/ +- + - /docs/pebblekit-ios/Classes/Classes/PBVersionInfo.html + - /docs/pebblekit-ios/Classes/PBVersionInfo/ +- + - /docs/pebblekit-ios/Classes/Classes/PBPebbleCentral.html + - /docs/pebblekit-ios/Classes/PBPebbleCentral/ +- + - /sdk/migration/migration-guide-3 + - /guides/migration/migration-guide-3/ +- + - /guides/pebble-imeline/pin-structure + - /guides/pebble-timeline/pin-structure/ diff --git a/devsite/source/_data/sdk.yaml b/devsite/source/_data/sdk.yaml new file mode 100644 index 00000000..a1bcad31 --- /dev/null +++ b/devsite/source/_data/sdk.yaml @@ -0,0 +1,23 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +c: + version: "4.3" +pebblekit-android: + version: 4.0.1 +pebblekit-ios: + version: 4.0.0 +path: ~/pebble-dev/ +pebble_tool: + version: "4.5" diff --git a/devsite/source/_data/search-other.yaml b/devsite/source/_data/search-other.yaml new file mode 100644 index 00000000..531fb8a5 --- /dev/null +++ b/devsite/source/_data/search-other.yaml @@ -0,0 +1,22 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- id: color-picker + title: Color Picker + url: /more/color-picker/ + description: An interactive color picker to help you design your Pebble apps. +- id: inspiration + title: Inspiration + url: /inspiration/ + description: Find some inspiration for your next Pebble app. diff --git a/devsite/source/_data/search_indexes.json b/devsite/source/_data/search_indexes.json new file mode 100644 index 00000000..59960144 --- /dev/null +++ b/devsite/source/_data/search_indexes.json @@ -0,0 +1,94 @@ +{ + "guides": { + "settings": { + "attributesToIndex": [ + "unordered(title)", + "unordered(content)", + "unordered(type)" + ], + "attributesToSnippet": [ + "content:30" + ], + "attributesForFaceting": [ + ], + "attributeForDistinct": "urlDisplay", + "highlightPreTag": "", + "highlightPostTag": "" + } + }, + "blog-posts": { + "settings": { + "attributesToIndex": [ + "unordered(title)", + "unordered(content)", + "unordered(type)" + ], + "attributesToSnippet": [ + "content:30" + ], + "attributesForFaceting": [ + ], + "customRanking": [ + "asc(age)" + ], + "attributeForDistinct": "urlDisplay", + "highlightPreTag": "", + "highlightPostTag": "" + } + }, + "documentation": { + "settings": { + "attributesToIndex": [ + "title", + "splitTitle", + "unordered(summary)", + "unordered(type)" + ], + "attributesToSnippet": [ + "summary:30" + ], + "attributesForFaceting": [ + "language" + ], + "customRanking": [ + "desc(ranking)" + ], + "attributeForDistinct": null, + "highlightPreTag": "", + "highlightPostTag": "" + } + }, + "examples": { + "settings": { + "attributesToIndex": [ + "unordered(title)", + "unordered(summary)", + "unordered(type)" + ], + "attributesToSnippet": [ + "summary:30" + ], + "attributesForFaceting": [ + ], + "attributeForDistinct": null, + "highlightPreTag": "", + "highlightPostTag": "" + } + }, + "other": { + "settings": { + "attributesToIndex": [ + "unordered(title)", + "unordered(content)" + ], + "attributesToSnippet": [ + "content:30" + ], + "attributesForFaceting": [ + ], + "attributeForDistinct": null, + "highlightPreTag": "", + "highlightPostTag": "" + } + } +} diff --git a/devsite/source/_guides/app-resources/animated-images.md b/devsite/source/_guides/app-resources/animated-images.md new file mode 100644 index 00000000..6d13d8ed --- /dev/null +++ b/devsite/source/_guides/app-resources/animated-images.md @@ -0,0 +1,145 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Animated Images +description: | + How to add animated image resources to a project in the APNG format, and + display them in your app. +guide_group: app-resources +order: 0 +platform_choice: true +--- + +The Pebble SDK allows animated images to be played inside an app using the +``GBitmapSequence`` API, which takes [APNG](https://en.wikipedia.org/wiki/APNG) +images as input files. APNG files are similar to well-known `.gif` files, which +are not supported directly but can be converted to APNG. + +A similar effect can be achieved with multiple image resources, a +``BitmapLayer`` and an ``AppTimer``, but would require a lot more code. The +``GBitmapSequence`` API handles the reading, decompression, and frame +duration/count automatically. + + +## Converting GIF to APNG + +A `.gif` file can be converted to the APNG `.png` format with +[gif2apng](http://gif2apng.sourceforge.net/) and the `-z0` flag: + +```text +./gif2apng -z0 animation.gif +``` + +> Note: The file extension must be `.png`, **not** `.apng`. + + +## Adding an APNG + +{% platform local %} +Include the APNG file in the `resources` array in `package.json` as a `raw` +resource: + +```js +"resources": { + "media": [ + { + "type":"raw", + "name":"ANIMATION", + "file":"images/animation.png" + } + ] +} +``` +{% endplatform %} + +{% platform cloudpebble %} +To add the APNG file as a raw resource, click 'Add New' in the Resources section +of the sidebar, and set the 'Resource Type' as 'raw binary blob'. +{% endplatform %} + +## Displaying APNG Frames + +The ``GBitmapSequence`` will use a ``GBitmap`` as a container and update its +contents each time a new frame is read from the APNG file. This means that the +first step is to create a blank ``GBitmap`` to be this container. + +Declare file-scope variables to hold the data: + +```c +static GBitmapSequence *s_sequence; +static GBitmap *s_bitmap; +``` + +Load the APNG from resources into the ``GBitmapSequence`` variable, and use the +frame size to create the blank ``GBitmap`` frame container: + + +```c +// Create sequence +s_sequence = gbitmap_sequence_create_with_resource(RESOURCE_ID_ANIMATION); + +// Create blank GBitmap using APNG frame size +GSize frame_size = gbitmap_sequence_get_bitmap_size(s_sequence); +s_bitmap = gbitmap_create_blank(frame_size, GBitmapFormat8Bit); +``` + +Once the app is ready to begin playing the animated image, advance each frame +using an ``AppTimer`` until the end of the sequence is reached. Loading the next +APNG frame is handled for you and written to the container ``GBitmap``. + +Declare a ``BitmapLayer`` variable to display the current frame, and set it up +as described under +{% guide_link app-resources/images#displaying-an-image "Displaying An Image" %}. + +```c +static BitmapLayer *s_bitmap_layer; +``` + +Create the callback to be used when the ``AppTimer`` has elapsed, and the next +frame should be displayed. This will occur in a loop until there are no more +frames, and ``gbitmap_sequence_update_bitmap_next_frame()`` returns `false`: + +```c +static void timer_handler(void *context) { + uint32_t next_delay; + + // Advance to the next APNG frame, and get the delay for this frame + if(gbitmap_sequence_update_bitmap_next_frame(s_sequence, s_bitmap, &next_delay)) { + // Set the new frame into the BitmapLayer + bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap); + layer_mark_dirty(bitmap_layer_get_layer(s_bitmap_layer)); + + // Timer for that frame's delay + app_timer_register(next_delay, timer_handler, NULL); + } +} +``` + +When appropriate, schedule the first frame advance with an ``AppTimer``: + +```c +uint32_t first_delay_ms = 10; + +// Schedule a timer to advance the first frame +app_timer_register(first_delay_ms, timer_handler, NULL); +``` + +When the app exits or the resource is no longer required, destroy the +``GBitmapSequence`` and the container ``GBitmap``: + +```c +gbitmap_sequence_destroy(s_sequence); +gbitmap_destroy(s_bitmap); +``` diff --git a/devsite/source/_guides/app-resources/app-assets.md b/devsite/source/_guides/app-resources/app-assets.md new file mode 100644 index 00000000..76dd69a7 --- /dev/null +++ b/devsite/source/_guides/app-resources/app-assets.md @@ -0,0 +1,68 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Assets +description: | + A collection of assets for use as resources in Pebble apps. +guide_group: app-resources +order: 1 +--- + +This guide contains some resources available for developers to use in their apps +to improve consistency, as well as for convenience. For example, most +``ActionBarLayer`` implementations will require at least one of the common icons +given below. + + +## Pebble Timeline Pin Icons + +Many timeline pin icons +[are available]({{ site.links.s3_assets }}/assets/other/pebble-timeline-icons-pdc.zip) +in Pebble Draw Command or PDC format (as described in +{% guide_link graphics-and-animations/vector-graphics %}) for use in watchfaces +and watchapps. These are useful in many kinds of generic apps. + + +## Example PDC icon SVG Files + +Many of the system PDC animations are available for use in watchfaces and +watchapps as part of the +[`pdc-sequence`](https://github.com/pebble-examples/pdc-sequence/tree/master/resources) +example project. + + +## Example Action Bar Icons + +There is a +[set of example icons](https://s3.amazonaws.com/developer.getpebble.com/assets/other/actionbar-icons.zip) +for developers to use for common actions. Each icon is shown below as a preview, +along with a short description about its suggested usage. + +| Preview | Description | +|---------|-------------| +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_check.png) | Check mark for confirmation actions. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_dismiss.png) | Cross mark for dismiss, cancel, or decline actions. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_up.png) | Up arrow for navigating or scrolling upwards. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_down.png) | Down arrow for navigating or scrolling downwards. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_edit.png) | Pencil icon for edit actions. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_delete.png) | Trash can icon for delete actions. | +| ![](/images/guides/design-and-interaction/icons/action_bar_icon_snooze.png) | Stylized 'zzz' for snooze actions. | +| ![](/images/guides/design-and-interaction/icons/music_icon_ellipsis.png) | Ellipsis to suggest further information or actions are available. | +| ![](/images/guides/design-and-interaction/icons/music_icon_play.png) | Common icon for play actions. | +| ![](/images/guides/design-and-interaction/icons/music_icon_pause.png) | Common icon for pause actions. | +| ![](/images/guides/design-and-interaction/icons/music_icon_skip_forward.png) | Common icon for skip forward actions. | +| ![](/images/guides/design-and-interaction/icons/music_icon_skip_backward.png) | Common icon for skip backward actions. | +| ![](/images/guides/design-and-interaction/icons/music_icon_volume_up.png) | Common icon for raising volume. | +| ![](/images/guides/design-and-interaction/icons/music_icon_volume_down.png) | Common icon for lowering volume. | diff --git a/devsite/source/_guides/app-resources/converting-svg-to-pdc.md b/devsite/source/_guides/app-resources/converting-svg-to-pdc.md new file mode 100644 index 00000000..1832af7e --- /dev/null +++ b/devsite/source/_guides/app-resources/converting-svg-to-pdc.md @@ -0,0 +1,136 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Converting SVG to PDC +description: | + How to create compatible SVG files using Inkscape and Illustrator. +guide_group: app-resources +order: 2 +related_docs: + - Draw Commands +--- + +[Pebble Draw Commands](``Draw Commands``) (PDC) are a powerful method of +creating vector images and icons that can be transformed and manipulated at +runtime. These can be used as a low-cost alternative to APNGs or bitmap +sequences. Currently the only simple way to create PDC files is to use the +[`svg2pdc.py`](https://github.com/pebble-examples/cards-example/blob/master/tools/svg2pdc.py) +tool. However, as noted in +[*Vector Animations*](/tutorials/advanced/vector-animations/#creating-compatible-files) +there are a some limitations to the nature of the input SVG file: + +> The `svg2pdc` tool currently supports SVG files that use **only** the +> following elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, +> `line`, `circle`. + +Fortunately, steps can be taken when creating SVG files in popular graphics +packages to avoid these limitations and ensure the output file is compatible +with `svg2pdc.py`. In this guide, we will be creating compatible PDC files using +an example SVG - this +[pencil icon](https://upload.wikimedia.org/wikipedia/commons/a/ac/Black_pencil.svg). + +![pencil icon](/images/guides/pebble-apps/resources/pencil.svg =100x) + + +## Using Inkscape + +* First, open the SVG in [Inkscape](https://inkscape.org/en/): + +![inkscape-open](/images/guides/pebble-apps/resources/inkscape-open.png) + +* Resize the viewport with *File*, *Document Properties*, + *Page*, *Resize Page to Drawing*: + +![inkscape-resize-page](/images/guides/pebble-apps/resources/inkscape-resize-page.png =350x) + +* Select the layer, then resize the image to fit Pebble (50 x 50 pixels in this + example) with *Object*, *Transform*: + +![inkscape-resize-pebble](/images/guides/pebble-apps/resources/inkscape-resize-pebble.png) + +* Now that the image has been resized, shrink the viewport again with *File*, + *Document Properties*, *Page*, *Resize Page to Drawing*: + +* Remove groupings with *Edit*, *Select All*, then *Object*, *Ungroup* until no + groups remain: + +![inkscape-ungroup](/images/guides/pebble-apps/resources/inkscape-ungroup.png) + +* Disable relative move in *Object*, *Transform*. Hit *Apply*: + +![inkscape-relative](/images/guides/pebble-apps/resources/inkscape-relative.png) + +* Finally, save the image as a 'Plain SVG': + +![inkscape-plain](/images/guides/pebble-apps/resources/inkscape-plain.png) + + +## Using Illustrator + +* First, open the SVG in Illustrator: + +![illustrator-open](/images/guides/pebble-apps/resources/illustrator-open.png) + +* Resize the image to fit Pebble (50 x 50 pixels in this example) by entering in + the desired values in the 'W' and 'H' fields of the *Transform* panel: + +![illustrator-resize](/images/guides/pebble-apps/resources/illustrator-resize.png) + +* Ungroup all items with *Select*, *All*, followed by *Object*, *Ungroup* until + no groups remain: + +![illustrator-ungroup](/images/guides/pebble-apps/resources/illustrator-ungroup.png) + +* Shrink the image bounds with *Object*, *Artboards*, *Fit to Selected Art*: + +![illustrator-fit](/images/guides/pebble-apps/resources/illustrator-fit.png) + +* Save the SVG using *File*, *Save As* with the *SVG Tiny 1.1* profile and 1 decimal places: + +![illustrator-settings](/images/guides/pebble-apps/resources/illustrator-settings.png =350x) + + +## Using the PDC Files + +Once the compatible SVG files have been created, it's time to use `svg2pdc.py` +to convert into PDC resources, which will contain all the vector information +needed to draw the image in the correct Pebble binary format. The command is +shown below, with the Inkscape output SVG used as an example: + +```nc|bash +$ python svg2pdc.py pencil-inkscape.svg # Use python 2.x! +``` + +> If a coordinate value's precision value isn't supported, a warning will be +> printed and the nearest compatible value will be used instead: +> +> ```text +> Invalid point: (9.4, 44.5). Closest supported coordinate: (9.5, 44.5) +> ``` + +To use the PDC file in a Pebble project, read +[*Drawing a PDC Image*](/tutorials/advanced/vector-animations/#drawing-a-pdc-image). +The result should look near-identical on Pebble: + +![svg-output >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/pebble-apps/resources/svg-output.png) + + +## Example Output + +For reference, compatible output files are listed below: + +* Inkscape: [SVG](/assets/other/pdc/pencil-inkscape.svg) | [PDC](/assets/other/pdc/pencil-inkscape.pdc) + +* Illustrator: [SVG](/assets/other/pdc/pencil-illustrator.svg) | [PDC](/assets/other/pdc/pencil-illustrator.pdc) \ No newline at end of file diff --git a/devsite/source/_guides/app-resources/fonts.md b/devsite/source/_guides/app-resources/fonts.md new file mode 100644 index 00000000..85cea369 --- /dev/null +++ b/devsite/source/_guides/app-resources/fonts.md @@ -0,0 +1,207 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Fonts +description: | + How to use built-in system fonts, or add your own font resources to a project. +guide_group: app-resources +order: 3 +platform_choice: true +--- + + +## Using Fonts + +Text drawn in a Pebble app can be drawn using a variety of built-in fonts or a +custom font specified as a project resource. + +Custom font resources must be in the `.ttf` (TrueType font) format. When the app +is built, the font file is processed by the SDK according to the `compatibility` +(See [*Font Compatibility*](#font-compatibility)) and `characterRegex` +fields (see [*Choosing Font Characters*](#choosing-font-characters)), the latter +of which is a standard Python regex describing the character set of the +resulting font. + + +## System Fonts + +All of the built-in system fonts are available to use with +``fonts_get_system_font()``. See {% guide_link app-resources/system-fonts %} for +a complete list with sample images. Examples of using a built-in system font in +code are [shown below](#using-a-system-font). + + +### Limitations + +There are limitations to the Bitham, Roboto, Droid and LECO fonts, owing to the +memory space available on Pebble, which only contain a subset of the default +character set. + +* Roboto 49 Bold Subset - contains digits and a colon. +* Bitham 34/42 Medium Numbers - contain digits and a colon. +* Bitham 18/34 Light Subset - only contains a few characters and is not suitable + for displaying general text. +* LECO Number sets - suitable for number-only usage. + + +## Using a System Font + +Using a system font is the easiest choice when displaying simple text. For more +advanced cases, a custom font may be advantageous. A system font can be obtained +at any time, and the developer is not responsible for destroying it when they +are done with it. Fonts can be used in two modes: + +```c +// Use a system font in a TextLayer +text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); +``` + +```c +// Use a system font when drawing text manually +graphics_draw_text(ctx, text, fonts_get_system_font(FONT_KEY_GOTHIC_24), bounds, + GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); +``` + + +## Adding a Custom Font + +{% platform local %} +After placing the font file in the project's `resources` directory, the custom +font can be added to a project as `font` `type` item in the `media` array in +`package.json`. The `name` field's contents will be made available at compile +time with `RESOURCE_ID_` at the front, and must end with the desired font size. +For example: + +```js +"resources": { + "media": [ + { + "type": "font", + "name": "EXAMPLE_FONT_20", + "file": "example_font.ttf" + } + ] +} +``` +{% endplatform %} + +{% platform cloudpebble %} +To add a custom font file to your project, click 'Add New' in the Resources +section of the sidebar. Set the 'Resource Type' to 'TrueType font', and upload +the file using the 'Choose file' button. Choose an 'Identifier', which will be +made available at compile time with `RESOURCE_ID_` at the front. This must end +with the desired font size ("EXAMPLE_FONT_20", for example). + +Configure the other options as appropriate, then hit 'Save' to save the +resource. +{% endplatform %} + +{% alert important %} +The maximum recommended font size is 48. +{% endalert %} + + +## Using a Custom Font + +Unlike a system font, a custom font must be loaded and unloaded by the +developer. Once this has been done, the font can easily be used in a similar +manner. + +When the app initializes, load the font from resources using the generated +`RESOURCE_ID`: + +```c +// Declare a file-scope variable +static GFont s_font; +``` + +```c +// Load the custom font +s_font = fonts_load_custom_font( + resource_get_handle(RESOURCE_ID_EXAMPLE_FONT_20)); +``` + +The font can now be used in two modes - with a ``TextLayer``, or when drawing +text manually in a ``LayerUpdateProc``: + +```c +// Use a custom font in a TextLayer +text_layer_set_font(s_text_layer, s_font); +``` + +```c +// Use a custom font when drawing text manually +graphics_draw_text(ctx, text, s_font, bounds, GTextOverflowModeWordWrap, + GTextAlignmentCenter, NULL); +``` + + +## Font Compatibility + +The font rendering process was improved in SDK 2.8. However, in some cases this +may cause the appearance of custom fonts to change slightly. To revert to the +old rendering process, add `"compatibility": "2.7"` to your font's object in the +`media` array (shown above) in `package.json` or set the 'Compatibility' +property in the font's resource view in CloudPebble to '2.7 and earlier'. + + +## Choosing Font Characters + +By default, the maximum number of supported characters is generated for a font +resource. In most cases this will be far too many, and can bloat the size of the +app. To optimize the size of your font resources you can use a standard regular +expression (or 'regex') string to limit the number of characters to only those +you require. + +The table below outlines some example regular expressions to use for limiting +font character sets in common watchapp scenarios: + +| Expression | Result | +|------------|--------| +| `[ -~]` | ASCII characters only. | +| `[0-9]` | Numbers only. | +| `[0-9 ]` | Numbers and spaces only. | +| `[a-zA-Z]` | Letters only. | +| `[a-zA-Z ]` | Letters and spaces only. | +| `[0-9:APM ]` | Time strings only (e.g.: "12:45 AM"). | +| `[0-9:A-Za-z ]` | Time and date strings (e.g.: "12:43 AM Wednesday 3rd March 2015". | +| `[0-9:A-Za-z° ]` | Time, date, and degree symbol for temperature gauges. | +| `[0-9°CF ]` | Numbers and degree symbol with 'C' and 'F' for temperature gauges. | + +{% platform cloudpebble %} +Open the font's configuration screen under 'Resources', then enter the desired +regex in the 'Characters' field. Check the preview of the new set of characters, +then choose 'Save'. +{% endplatform %} + +{% platform local %} +Add the `characterRegex` key to any font objects in `package.json`'s +`media` array. + +```js +"media": [ + { + "characterRegex": "[:0-9]", + "type": "font", + "name": "EXAMPLE_FONT", + "file": "example_font.ttf" + } +] +``` +{% endplatform %} + +Check out +[regular-expressions.info](http://www.regular-expressions.info/tutorial.html) +to learn more about how to use regular expressions. diff --git a/devsite/source/_guides/app-resources/images.md b/devsite/source/_guides/app-resources/images.md new file mode 100644 index 00000000..538d4384 --- /dev/null +++ b/devsite/source/_guides/app-resources/images.md @@ -0,0 +1,204 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Images +description: | + How to add image resources to a project and display them in your app. +guide_group: app-resources +order: 4 +platform_choice: true +--- + +Images can be displayed in a Pebble app by adding them as a project resource. +They are stored in memory as a ``GBitmap`` while the app is running, and can be +displayed either in a ``BitmapLayer`` or by using +``graphics_draw_bitmap_in_rect()``. + + +## Creating an Image + +In order to be compatible with Pebble, the image should be saved as a PNG file, +ideally in a palettized format (see below for palette files) with the +appropriate number of colors. The number of colors available on each platform is +shown below: + +| Platform | Number of Colors | +|----------|------------------| +| Aplite | 2 (black and white) | +| Basalt | 64 colors | +| Chalk | 64 colors | + + +## Color Palettes + +Palette files for popular graphics packages that contain the 64 supported colors +are available below. Use these when creating color image resources: + +* [Photoshop `.act`](/assets/other/pebble_colors_64.act) + +* [Illustrator `.ai`](/assets/other/pebble_colors_64.ai) + +* [GIMP `.pal`](/assets/other/pebble_colors_64.pal) + +* [ImageMagick `.gif`](/assets/other/pebble_colors_64.gif) + + +## Import the Image + +{% platform cloudpebble %} +Add the `.png` file as a resource using the 'Add New' button next to +'Resources'. Give the resource a suitable 'Identifier' such as 'EXAMPLE_IMAGE' +and click 'Save'. +{% endplatform %} + +{% platform local %} +After placing the image in the project's `resources` directory, add an entry to +the `resources` item in `package.json`. Specify the `type` as `bitmap`, choose a +`name` (to be used in code) and supply the path relative to the project's +`resources` directory. Below is an example: + +```js +"resources": { + "media": [ + { + "type": "bitmap", + "name": "EXAMPLE_IMAGE", + "file": "background.png" + } + ] +}, +``` +{% endplatform %} + + +## Specifying an Image Resource + +Image resources are used in a Pebble project when they are listed using the +`bitmap` resource type. + +Resources of this type can be optimized using additional attributes: + +| Attribute | Description | Values | +|-----------|-------------|--------| +| `memoryFormat` | Optional. Determines the bitmap type. Reflects values in the `GBitmapFormat` `enum`. | `Smallest`, `SmallestPalette`, `1Bit`, `8Bit`, `1BitPalette`, `2BitPalette`, or `4BitPalette`. | +| `storageFormat` | Optional. Determines the file format used for storage. Using `spaceOptimization` instead is preferred. | `pbi` or `png`. | +| `spaceOptimization` | Optional. Determines whether the output resource is optimized for low runtime memory or low resource space usage. | `storage` or `memory`. | + +{% platform cloudpebble %} +These attributes can be selected in CloudPebble from the resource's page: + +![](/images/guides/app-resources/cp-bitmap-attributes.png) +{% endplatform %} + +{% platform local %} +An example usage of these attributes in `package.json` is shown below: + +```js +{ + "type": "bitmap", + "name": "IMAGE_EXAMPLE", + "file": "images/example_image.png" + "memoryFormat": "Smallest", + "spaceOptimization": "memory" +} +``` +{% endplatform %} + +On all platforms `memoryFormat` will default to `Smallest`. On Aplite +`spaceOptimization` will default to `memory`, and `storage` on all other +platforms. + +> If you specify a combination of attributes that is not supported, such as a +> `1Bit` unpalettized PNG, the build will fail. Palettized 1-bit PNGs are +> supported. + +When compared to using image resources in previous SDK versions: + +* `png` is equivalent to `bitmap` with no additional specifiers. + +* `pbi` is equivalent to `bitmap` with `"memoryFormat": "1Bit"`. + +* `pbi8` is equivalent to `bitmap` with `"memoryFormat": "8Bit"` and + `"storageFormat": "pbi"`. + +Continuing to use the `png` resource type will result in a `bitmap` resource +with `"storageFormat": "png"`, which is not optimized for memory usage on the +Aplite platform due to less memory available in total, and is not encouraged. + + +## Specifying Resources Per Platform + +To save resource space, it is possible to include only certain image resources +when building an app for specific platforms. For example, this is useful for the +Aplite platform, which requires only black and white versions of images, which +can be significantly smaller in size. Resources can also be selected according +to platform and display shape. + +Read {% guide_link app-resources/platform-specific %} to learn more about how to +do this. + + +## Displaying an Image + +Declare a ``GBitmap`` pointer. This will be the object type the image data is +stored in while the app is running: + +```c +static GBitmap *s_bitmap; +``` + +{% platform cloudpebble %} +Create the ``GBitmap``, specifying the 'Identifier' chosen earlier, prefixed +with `RESOURCE_ID_`. This will manage the image data: +{% endplatform %} + +{% platform local %} +Create the ``GBitmap``, specifying the `name` chosen earlier, prefixed with +`RESOURCE_ID_`. This will manage the image data: +{% endplatform %} + +```c +s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE); +``` + +Declare a ``BitmapLayer`` pointer: + +```c +static BitmapLayer *s_bitmap_layer; +``` + +Create the ``BitmapLayer`` and set it to show the ``GBitmap``. Make sure to +supply the correct width and height of your image in the ``GRect``, as well as +using ``GCompOpSet`` to ensure color transparency is correctly applied: + +```c +s_bitmap_layer = bitmap_layer_create(GRect(5, 5, 48, 48)); +bitmap_layer_set_compositing_mode(s_bitmap_layer, GCompOpSet); +bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap); +``` + +Add the ``BitmapLayer`` as a child layer to the ``Window``: + +```c +layer_add_child(window_get_root_layer(window), + bitmap_layer_get_layer(s_bitmap_layer)); +``` + +Destroy both the ``GBitmap`` and ``BitmapLayer`` when the app exits: + +```c +gbitmap_destroy(s_bitmap); +bitmap_layer_destroy(s_bitmap_layer); +``` \ No newline at end of file diff --git a/devsite/source/_guides/app-resources/index.md b/devsite/source/_guides/app-resources/index.md new file mode 100644 index 00000000..4cefd612 --- /dev/null +++ b/devsite/source/_guides/app-resources/index.md @@ -0,0 +1,56 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Resources +description: | + Information on the many kinds of files that can be used inside Pebble apps. +guide_group: app-resources +menu: false +permalink: /guides/app-resources/ +generate_toc: false +hide_comments: true +platform_choice: true +--- + +The Pebble SDK allows apps to include extra files as app resources. These files +can include images, animated images, vector images, custom fonts, and raw data +files. These resources are stored in flash memory and loaded when required by +the SDK. Apps that use a large number of resources should consider only keeping +in memory those that are immediately required. + +{% alert notice %} +The maximum number of resources an app can include is **256**. In addition, the +maximum size of all resources bundled into a built app is **128 kB** on the +Aplite platform, and **256 kB** on the Basalt and Chalk platforms. These limits +include resources used by included Pebble Packages. +{% endalert %} + +{% platform local %} +App resources are included in a project by being listed in the `media` property +of `package.json`, and are converted into suitable firmware-compatible formats +at build time. Examples of this are shown in each type of resource's respective +guide. +{% endplatform %} + +{% platform cloudpebble %} +App resources are included in a project by clicking the 'Add New' button under +'Resources' and specifying the 'Resource Type' as appropriate. These are then +converted into suitable firmware-compatible formats at build time. +{% endplatform %} + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/app-resources/pdc-format.md b/devsite/source/_guides/app-resources/pdc-format.md new file mode 100644 index 00000000..0a1f4396 --- /dev/null +++ b/devsite/source/_guides/app-resources/pdc-format.md @@ -0,0 +1,160 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Draw Command File Format +description: | + The binary file format description for Pebble Draw Command Frames, Images and + Sequences. +guide_group: app-resources +order: 5 +related_docs: + - Draw Commands + - LayerUpdateProc + - Graphics +related_examples: + - title: PDC Sequence + url: https://github.com/pebble-examples/pdc-sequence + - title: Weather Cards Example + url: https://github.com/pebble-examples/cards-example +--- + +Pebble [`Draw Commands`](``Draw Commands``) (PDCs) are vector image files that +consist of a binary resource containing the instructions for each stroke, fill, +etc. that makes up the image. The byte format of all these components are +described in tabular form below. + +> **Important**: All fields are in the little-endian format! + +An example implementation with some +[usage limitations](/tutorials/advanced/vector-animations#creating-compatible-files) +can be seen in +[`svg2pdc.py`]({{site.links.examples_org}}/cards-example/blob/master/tools/svg2pdc.py). + + +## Component Types + +A PDC binary file consists of the following key components, in ascending order +of abstraction: + +* [Draw Command](#pebble-draw-command) - an instruction for a single line or + path to be drawn. + +* [Draw Command List](#pebble-draw-command-list) - a set of Draw Commands that + make up a shape. + +* [Draw Command Frame](#pebble-draw-command-frame) - a Draw Command List with + configurable duration making up one animation frame. Many of these are used in + a Draw Command Sequence. + +* [Draw Command Image](#pebble-draw-command-image) - A single vector image. + +* [Draw Command Sequence](#pebble-draw-command-sequence) - A set of Draw Command + Frames that make up an animated sequence of vector images. + + +## Versions + +| PDC Format Version | Implemented | +|--------------------|-------------| +| 1 | Firmware 3.0 | + + +## File Format Components + +### Point + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| X | 0 | 2 | X axis coordinate. Has one of two formats depending on the Draw Command type (see below):

Path/Circle type: signed integer.
Precise path type: 13.3 fixed point. | +| Y | 2 | 2 | Y axis coordinate. Has one of two formats depending on the Draw Command type (see below):

Path/Circle type: signed integer.
Precise path type: 13.3 fixed point. | + + +### View Box + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Width | 0 | 2 | Width of the view box (signed integer). | +| Height | 2 | 2 | Height of the view box (signed integer). | + + +### Pebble Draw Command + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Type | 0 | 1 | Draw command type. Possible values are:

`0` - Invalid
`1` - Path
`2` - Circle
`3` - Precise path | +| Flags | 1 | 1 | Bit 0: Hidden (Draw Command should not be drawn).
Bits 1-7: Reserved. | +| Stroke color | 2 | 1 | Pebble color (integer). | +| Stroke width | 3 | 1 | Stroke width (unsigned integer). | +| Fill color | 4 | 1 | Pebble color (integer). | +| Path open/radius | 5 | 2 | Path/Precise path type: Bit 0 indicates whether or not the path is drawn open (`1`) or closed (`0`).
Circle type: radius of the circle. | +| Number of points | 7 | 2 | Number of points (n) in the point array. See below. | +| Point array | 9 | n x 4 | The number of points (n) points. | + + +### Pebble Draw Command List + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Number of commands | 0 | 2 | Number of Draw Commands in this Draw Command List. (`0` is invalid). | +| Draw Command array | 2 | n x size of Draw Command | List of Draw Commands in the format [specified above](#pebble-draw-command). | + + +### Pebble Draw Command Frame + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Duration | 0 | 2 | Duration of the frame in milliseconds. If `0`, the frame will not be shown at all (unless it is the last frame in a sequence). | +| Command list | 2 | Size of Draw Command List | Pebble Draw Command List in the format [specified above](#pebble-draw-command-list). | + +### Pebble Draw Command Image + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Version | 8 | 1 | File version. | +| Reserved | 9 | 1 | Reserved field. Must be `0`. | +| [View box](#view-box) | 10 | 4 | Bounding box of the image. All Draw Commands are drawn relative to the top left corner of the view box. | +| Command list | 14 | Size of Draw Command List | Pebble Draw Command List in the format [specified above](#pebble-draw-command-list). | + + +### Pebble Draw Command Sequence + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Version | 8 | 1 | File version. | +| Reserved | 9 | 1 | Reserved field. Must be `0`. | +| [View box](#view-box) | 10 | 4 | Bounding box of the sequence. All Draw Commands are drawn relative to the top left corner of the view box. | +| Play count | 14 | 2 | Number of times to repeat the sequence. A value of `0` will result in no playback at all, whereas a value of `0xFFFF` will repeat indefinitely. | +| Frame count | 16 | 2 | Number of frames in the sequence. `0` is invalid. | +| Frame list | 18 | n x size of Draw Command Frame | Array of Draw Command Frames in the format [specified above](#pebble-draw-command-frame). | + + +## File Formats + +### Pebble Draw Command Image File + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Magic word | 0 | 4 | ASCII characters spelling "PDCI". | +| Image size | 4 | 4 | Size of the Pebble Draw Command Image (in bytes). | +| Image | 8 | Size of Pebble Draw Command Image. | The Draw Command Image in the format [specified above](#pebble-draw-command-image). | + + +### Pebble Draw Command Sequence File + +| Field | Offset (bytes) | Size (bytes) | Description | +|-------|----------------|--------------|-------------| +| Magic word | 0 | 4 | ASCII characters spelling "PDCS". | +| Sequence size | 4 | 4 | Size of the Pebble Draw Command Sequence (in bytes). | +| Sequence | 8 | Size of Draw Command Sequence | The Draw Command Sequence in the format [specified above](#pebble-draw-command-sequence). | diff --git a/devsite/source/_guides/app-resources/platform-specific.md b/devsite/source/_guides/app-resources/platform-specific.md new file mode 100644 index 00000000..6068462a --- /dev/null +++ b/devsite/source/_guides/app-resources/platform-specific.md @@ -0,0 +1,113 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Platform-specific Resources +description: | + How to include different resources for different platforms, as well as how to + include a resource only on a particular platform. +guide_group: app-resources +order: 6 +--- + +You may want to use different versions of a resource on one or more of the +Aplite, Basalt or Chalk platforms. To enable this, it is now possible to +"tag" resource files with the attributes that make them relevant to a given +platform. + +The follows tags exist for each platform: + +| Aplite | Basalt | Chalk | Diorite | Emery | +|---------|------------|------------|---------|------------| +| rect | rect | round | rect | rect | +| bw | color | color | bw | color | +| aplite | basalt | chalk | diroite | emery | +| 144w | 144w | 180w | 144w | 220w | +| 168h | 168h | 180h | 168h | 228h | +| compass | compass | compass | | compass | +| | mic | mic | mic | mic | +| | strap | strap | strap | strap | +| | strappower | strappower | | strappower | +| | health | health | health | health | + + +To tag a resource, add the tags after the file's using tildes (`~`) — for +instance, `example-image~color.png` to use the resource on only color platforms, +or `example-image~color~round.png` to use the resource on only platforms with +round, color displays. All tags must match for the file to be used. If no file +matches for a platform, a compilation error will occur. + +If the correct file for a platform is ambiguous, an error will occur at +compile time. You cannot, for instance, have both `example~color.png` and +`example~round.png`, because it is unclear which image to use when building +for Chalk. Instead, use `example~color~rect.png` and `example~round.png`. If +multiple images could match, the one with the most tags wins. + +We recommend avoiding the platform specific tags (aplite, basalt etc). When we +release new platforms in the future, you will need to create new files for that +platform. However, if you use the descriptive tags we will automatically use +them as appropriate. It is also worth noting that the platform tags are _not_ +special: if you have `example~basalt.png` and `example~rect.png`, that is +ambiguous (they both match Basalt) and will cause a compilation error. + +An example file structure is shown below. + +```text +my-project/ + resources/ + images/ + example-image~bw.png + example-image~color~rect.png + example-image~color~round.png + src/ + main.c + package.json + wscript +``` + +This resource will appear in `package.json` as shown below. + +``` +"resources": { + "media": [ + { + "type": "bitmap", + "name": "EXAMPLE_IMAGE", + "file": "images/example-image.png" + } + ] +} +``` + +**Single-platform Resources** + +If you want to only include a resource on a **specific** platform, you can add a +`targetPlatforms` field to the resource's entry in the `media` array in +`package.json`. For example, the resource shown below will only be included for +the Basalt build. + +``` +"resources": { + "media": [ + { + "type": "bitmap", + "name": "BACKGROUND_IMAGE", + "file": "images/background.png", + "targetPlatforms": [ + "basalt" + ] + } + ] +} +``` diff --git a/devsite/source/_guides/app-resources/raw-data-files.md b/devsite/source/_guides/app-resources/raw-data-files.md new file mode 100644 index 00000000..7bc4db8e --- /dev/null +++ b/devsite/source/_guides/app-resources/raw-data-files.md @@ -0,0 +1,99 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Raw Data Files +description: | + How to add raw data resources to a project and read them in your app. +guide_group: app-resources +order: 7 +platform_choice: true +--- + +Some kinds of apps will require extra data that is not a font or an image. In +these cases, the file can be included in a Pebble project as a raw resource. +When a file is included as a raw resource, it is not modified in any way from +the original when the app is built. + +Applications of this resource type can be found in the Pebble SDK for APIs +such as ``GDrawCommand`` and ``GBitmapSequence``, which both use raw resources +as input files. Other possible applications include localized string +dictionaries, CSV data files, etc. + + +## Adding Raw Data Files + +{% platform local %} +To add a file as a raw resource, specify its `type` as `raw` in `package.json`. +An example is shown below: + +```js +"resources": { + "media": [ + { + "type": "raw", + "name": "EXAMPLE_DATA_FILE", + "file": "data.bin" + } + ] +} +``` +{% endplatform %} + +{% platform cloudpebble %} +To add a file as a raw resource, click 'Add New' in the Resources section of the +sidebar, and set the 'Resource Type' as 'raw binary blob'. +{% endplatform %} + + +## Reading Bytes and Byte Ranges + +Once a raw resource has been added to a project, it can be loaded at runtime in +a manner similar to other resources types: + +```c +// Get resource handle +ResHandle handle = resource_get_handle(RESOURCE_ID_DATA); +``` + +With a handle to the resource now available in the app, the size of the resource +can be determined: + +```c +// Get size of the resource in bytes +size_t res_size = resource_size(handle); +``` + +To read bytes from the resource, create an appropriate byte buffer and copy data +into it: + +```c +// Create a buffer the exact size of the raw resource +uint8_t *s_buffer = (uint8_t*)malloc(res_size); +``` + +The example below copies the entire resource into a `uint8_t` buffer: + +```c +// Copy all bytes to a buffer +resource_load(handle, s_buffer, res_size); +``` + +It is also possible to read a specific range of bytes from a given offset into +the buffer: + +```c +// Read the second set of 8 bytes +resource_load_byte_range(handle, 8, s_buffer, 8); +``` diff --git a/devsite/source/_guides/app-resources/system-fonts.md b/devsite/source/_guides/app-resources/system-fonts.md new file mode 100644 index 00000000..4db0b6b7 --- /dev/null +++ b/devsite/source/_guides/app-resources/system-fonts.md @@ -0,0 +1,193 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: System Fonts +description: | + A complete list of all the system fonts available for use in Pebble projects. +guide_group: app-resources +order: 8 +--- + +The tables below show all the system font identifiers available in the Pebble +SDK, sorted by family. A sample of each is also shown. + +## Available System Fonts + +### Raster Gothic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available Font KeysPreview
FONT_KEY_GOTHIC_14
FONT_KEY_GOTHIC_14_BOLD
FONT_KEY_GOTHIC_18
FONT_KEY_GOTHIC_18_BOLD
FONT_KEY_GOTHIC_24
FONT_KEY_GOTHIC_24_BOLD
FONT_KEY_GOTHIC_28
FONT_KEY_GOTHIC_28_BOLD
+ + +### Bitham + + + + + + + + + + + + + + + + + + + + + + + + +
Available Font KeysPreview
FONT_KEY_BITHAM_30_BLACK
FONT_KEY_BITHAM_34_MEDIUM_NUMBERS
FONT_KEY_BITHAM_42_BOLD
FONT_KEY_BITHAM_42_LIGHT
FONT_KEY_BITHAM_42_MEDIUM_NUMBERS
+ + +### Roboto/Droid Serif + + + + + + + + + + + + + + + + + + +
Available Font KeysPreview
FONT_KEY_ROBOTO_CONDENSED_21
FONT_KEY_ROBOTO_BOLD_SUBSET_49
FONT_KEY_DROID_SERIF_28_BOLD
+ + +### LECO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available Font KeysPreview
FONT_KEY_LECO_20_BOLD_NUMBERS
FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM
FONT_KEY_LECO_28_LIGHT_NUMBERS
FONT_KEY_LECO_32_BOLD_NUMBERS
FONT_KEY_LECO_36_BOLD_NUMBERS
FONT_KEY_LECO_38_BOLD_NUMBERS
FONT_KEY_LECO_42_NUMBERS
+ + +## Obtaining System Font Files + +The following system fonts are available to developers in the SDK can be found +online for use in design mockups: + +* [Raster Gothic](http://www.marksimonson.com/) - By Mark Simonson + +* [Gotham (Bitham)](http://www.typography.com/fonts/gotham/overview/) - + Available from Typography.com + +* [Droid Serif](https://www.google.com/fonts/specimen/Droid+Serif) - Available + from Google Fonts + +* [LECO 1976](https://www.myfonts.com/fonts/carnoky/leco-1976/) - Available from + Myfonts.com + + +## Using Emoji Fonts + +A subset of the built-in system fonts support the use of a set of emoji +characters. These are the Gothic 24, Gothic 24 Bold, Gothic 18, and Gothic 18 +Bold fonts, but do not include the full range. + +To print an emoji on Pebble, specify the code in a character string like the one +shown below when using a ``TextLayer``, or ``graphics_draw_text()``: + +```c +text_layer_set_text(s_layer, "Smiley face: \U0001F603"); +``` + +An app containing a ``TextLayer`` displaying the above string will look similar +to this: + +![emoji-screenshot >{pebble-screenshot,pebble-screenshot--steel-black}](/images/guides/pebble-apps/resources/emoji-screenshot.png) + +The supported characters are displayed below with their corresponding unicode +values. + + + +### Deprecated Emoji Symbols + +The following emoji characters are no longer available on the Aplite platform. + + diff --git a/devsite/source/_guides/appstore-publishing/appstore-analytics.md b/devsite/source/_guides/appstore-publishing/appstore-analytics.md new file mode 100644 index 00000000..4d7ba076 --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/appstore-analytics.md @@ -0,0 +1,158 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Appstore Analytics +description: | + How to view and use analytics about your app's usage. +guide_group: appstore-publishing +order: 2 +--- + +After publishing an app, developers can view automatic analytical data and +graphs for a variety of metrics. These can be used to help track the performance +of an app, identify when crashes start occuring, measure how fast an update is +adopted, or which platforms are most popular with users. + +> Due to latencies in the analytics gathering process, data from the last 7 days +> may not be very accurate. + + +## Available Metrics + +Several different metrics are available to developers, and are reported on a +daily basis. These metrics can be sorted or viewed according to a number of +categories: + +* App version - Which release of the app the user is running. + +* Hardware platform - Which Pebble hardware version the user is wearing. + +* Mobile platform - Which mobile phone platform (such as Android or iOS) the + user is using. + +An example graph is shown with each metric type below, grouped by hardware +platform. + + +### Installations + +The total number of times an app has been installed. + +![installations-example](/images/guides/appstore-publishing/installations-example.png) + + +### Unique Users + +The total number of unique users who have installed the app. This is +different to the installation metric due to users installing the same app +multiple times. + +![unique-users-example](/images/guides/appstore-publishing/unique-users-example.png) + + +### Launches + +The total number of times the app has been launched. + +![launches-example](/images/guides/appstore-publishing/launches-example.png) + + +### Crash Count + +The total number of times the app has crashed. Use the filters to view +crash count by platform or app version to help identify the source of a crash. + +![crash-count-example](/images/guides/appstore-publishing/crash-count-example.png) + + +### Run Time + +The total run time of the app in hours. + +![run-time-example](/images/guides/appstore-publishing/run-time-example.png) + + +### Run Time per launch + +The average run time of the app each time it was launched in minutes. + +![run-time-per-launch-example](/images/guides/appstore-publishing/run-time-per-launch-example.png) + + +### Buttons Pressed Per Launch + +> Watchfaces only + +The average number of button presses per launch of the app. + +![buttons-pressed-example](/images/guides/appstore-publishing/buttons-pressed-example.png) + + +### Timeline: Users Opening Pin + +> Timeline-enabled apps only + +The number of users opening timeline pins associated with the app. + +![opening-pin-example](/images/guides/appstore-publishing/opening-pin-example.png) + + +### Timeline: Pins Opened + +> Timeline-enabled apps only + +The number of timeline pins opened. + +![pins-opened-example](/images/guides/appstore-publishing/pins-opened-example.png) + + +### Timeline: Users Launching App from Pin + +> Timeline-enabled apps only + +The number of users launching the app from a timeline pin. + +![launching-app-from-pin-example](/images/guides/appstore-publishing/launching-app-from-pin-example.png) + + +### Timeline: Times App Launched from Pin + +> Timeline-enabled apps only + +Number of times the app was launched from a timeline pin. + +![times-launched-example](/images/guides/appstore-publishing/times-launched-example.png) + + +## Battery Stats + +In addition to installation, run time, and launch statistics, developers can +also view a battery grade for their app. Grade 'A' is the best available, +indicating that the app is very battery friendly, while grade 'F' is the +opposite (the app is severely draining the user's battery). + +> An app must reach a certain threshold of data before battery statistics can be +> reliably calculated. This is around 200 users. + +This is calculated based upon how much a user's battery decreased while the app +was open, and so does not take into other factors such as account notifications +or backlight activity during that time. + +![grade](/images/guides/appstore-publishing/grade.png) + +Clicking 'View More Details' will show a detailed breakdown for all the data +available across all app versions. + +![grade-versions](/images/guides/appstore-publishing/grade-versions.png) diff --git a/devsite/source/_guides/appstore-publishing/appstore-assets.md b/devsite/source/_guides/appstore-publishing/appstore-assets.md new file mode 100644 index 00000000..d2c72e04 --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/appstore-assets.md @@ -0,0 +1,55 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Appstore Assets +description: | + A collection of downloads to help developers create and improve their appstore + listings. +guide_group: appstore-publishing +order: 3 +--- + +A number of graphical assets are required when publishing an app on the +[Developer Portal](https://dev-portal.getpebble.com/), such as a marketing +banners. The resources on this page serve to help developers give these assets a +more authentic feel. + + +## Example Marketing Banners + +Example +[marketing banners](https://s3.amazonaws.com/developer.getpebble.com/assets/other/banner-examples.zip) +from existing apps may help provide inspiration for an app's appstore banner +design. Readable fonts, an appropriate background image, and at least one framed +screenshot are all recommended. + + +## Marketing Banner Templates + +![](/images/guides/appstore-publishing/perspective-right.png =400x) + +Use these +[blank PSD templates](https://s3.amazonaws.com/developer.getpebble.com/assets/other/banner-templates-design.zip) +to start a new app marketing banner in Photoshop from a template. + + +## App Screenshot Frames + +Use these +[example screenshot frames](https://s3.amazonaws.com/developer.getpebble.com/assets/other/pebble-frames.zip) +for Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, and Pebble Time Round +to decorate app screenshots in marketing banners and other assets. + +> Note: The screenshots added to the listing itself must not be framed. diff --git a/devsite/source/_guides/appstore-publishing/index.md b/devsite/source/_guides/appstore-publishing/index.md new file mode 100644 index 00000000..d8fa281a --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/index.md @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Appstore Publishing +description: | + How to get your app ready for going live in the Pebble appstore. +guide_group: appstore-publishing +menu: false +permalink: /guides/appstore-publishing/ +generate_toc: false +hide_comments: true +--- + +When a developer is happy that their app is feature-complete and stable, they +can upload the compiled `.pbw` file to the +[Developer Portal](https://dev-portal.getpebble.com) to make it available on the +Pebble appstore for all users with compatible watches to share and enjoy. + +In order to be successfully listed in the Pebble appstore the developer must: + +* Provide all required assets and marketing material. + +* Provide at least one `.pbw` release. + +* Use a unique and valid UUID. + +* Build their app with a non-beta SDK. + +* Ensure their app complies with the various legal agreements. + +Information on how to meet these requirements is given in this group of guides, +as well as details about available analytical data for published apps and +example asset material templates. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/appstore-publishing/preparing-a-submission.md b/devsite/source/_guides/appstore-publishing/preparing-a-submission.md new file mode 100644 index 00000000..dff4d466 --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/preparing-a-submission.md @@ -0,0 +1,110 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Preparing a Submission +description: | + How to prepare an app submission for the Pebble appstore. +guide_group: appstore-publishing +order: 0 +--- + +Once a new Pebble watchface or watchapp has been created, the +[Pebble Developer Portal](https://dev-portal.getpebble.com/) allows the +developer to publish their creation to the appstore either publicly, or +privately. The appstore is built into the official mobile apps and means that +every new app can be found and also featured for increased exposure and +publicity. + +> Note: An app can only be published privately while it is not already published +> publicly. If an app is already public, it must be unpublished before it can be +> made private. + +To build the appstore listing for a new app, the following resources are +required from the developer. Some may not be required, depending on the type of +app being listed. Read +{% guide_link appstore-publishing/publishing-an-app#listing-resources "Listing Resources" %} +for a comparison. + + +## Basic Info + +| Resource | Details | +|----------|---------| +| App title | Title of the app. | +| Website URL | Link to the brand or other website related to the app. | +| Source code URL | Link to the source code of the app (such as GitHub or BitBucket). | +| Support email address | An email address for support issues. If left blank, the developer's account email address will be used. | +| Category | A watchapp may be categorized depending on the kind of functionality it offers. Users can browse the appstore by these categories. | +| Icons | A large and small icons representing the app. | + + +## Asset Collections + +An asset collection must be created for each of the platforms that the app +supports. These are used to tailor the description and screenshots shown to +users browing with a specific platform connected. + +| Resource | Details | +|----------|---------| +| Description | The details and features of the app. Maximum 1600 characters. | +| Screenshots | Screenshots showing off the design and features of the app. Maximum 5 per platform in PNG, GIF, or Animated GIF format. | +| Marketing banner | Large image used at the top of a listing in some places, as well as if an app is featured on one of the main pages. | + + +## Releases + +In addition to the visual assets in an appstore listing, the developer must +upload at least one valid release build in the form of a `.pbw` file generated +by the Pebble SDK. This is the file that will be distributed to users if they +choose to install your app. + +The appstore will automatically select the appropriate version to download based +on the SDK version. This is normally the latest release, with the one exception +of the latest release built for SDK 2.x (deprecated) distributed to users +running a watch firmware less than 3.0. A release is considered valid if the +UUID is not in use and the version is greater than all previously published +releases. + + +## Companion Apps + +If your app requires an Android or iOS companion app to function, it can be +listed here by providing the name, icon, and URL that users can use to obtain +the companion app. When a user install the watchapp, they will be prompted to +also download the companion app automatically. + + +## Timeline + +Developers that require the user of the timeline API will need to click 'Enable +timeline' to obtain API keys used for pushing pins. See the +{% guide_link pebble-timeline %} guides for more information. + + +## Promotion + +Once published, the key to growth in an app is through promotion. Aside from +users recommending the app to each other, posting on websites such as the +[Pebble Forums](https://forums.getpebble.com/categories/watchapp-directory), +[Reddit](https://www.reddit.com/r/pebble), and [Twitter](https://twitter.com) +can help increase exposure. + + +## Developer Retreat Video + +Watch the presentation given by Aaron Cannon at the 2016 Developer Retreat to +learn more about preparing asset collections for the appstore. + +[EMBED](//www.youtube.com/watch?v=qXmz3eINObU&index=10&list=PLDPHNsf1sb48bgS5oNr8hgFz0pL92XqtO) diff --git a/devsite/source/_guides/appstore-publishing/publishing-an-app.md b/devsite/source/_guides/appstore-publishing/publishing-an-app.md new file mode 100644 index 00000000..ff54a00d --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/publishing-an-app.md @@ -0,0 +1,192 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Publishing an App +description: | + How to upload and publish an app in the Pebble appstore. +guide_group: appstore-publishing +order: 1 +--- + +When an app is ready for publishing, the `.pbw` file needs to be uploaded to the +Pebble [Developer Portal](https://dev-portal.getpebble.com/), where a listing is +created. Depending on the type of app, different sets of additional resources +are required. These resources are then used to generate the listing pages +visible to potential users in the Pebble appstore, which is embedded within the Pebble mobile app. + +You can also view the [watchfaces](http://apps.getpebble.com/en_US/watchfaces) +and [watchapps](http://apps.getpebble.com/en_US/watchapps) from a desktop +computer, as well as perform searches and get shareable links. + + +## Listing Resources + +The table below gives a summary of which types of resources required by +different types of app. Use this to quickly assess how complete assets and +resources are before creating the listing. + +| Resource | Watchface | Watchapp | Companion | +|----------|-----------|----------|-----------| +| Title | Yes | Yes | Yes | +| `.pbw` release build | Yes | Yes | - | +| Asset collections | Yes | Yes | Yes | +| Category | - | Yes | Yes | +| Large and small icons | - | Yes | Yes | +| Compatible platforms | - | - | Yes | +| Android or iOS companion appstore listing | - | - | Yes | + + +## Publishing a Watchface + +1. After logging in, click 'Add a Watchface'. + +2. Enter the basic details of the watchface, such as the title, source code URL, + and support email (if different from the one associated with this developer + account): + + ![face-title](/images/guides/appstore-publishing/face-title.png) + +3. Click 'Create' to be taken to the listing page. This page details the status + of the listing, including links to subpages, a preview of the public page, + and any missing information preventing release. + + ![face-listing](/images/guides/appstore-publishing/face-listing.png) + +4. The status now says 'Missing: At least one published release'. Click 'Add a + release' to upload the `.pbw`, optionally adding release notes: + + ![face-release](/images/guides/appstore-publishing/face-release.png) + +5. Click 'Save'. After reloading the page, make the release public by clicking + 'Publish' next to the release: + + ![face-release-publish](/images/guides/appstore-publishing/face-release-publish.png) + +6. The status now says 'Missing: A complete X asset collection' for + each X supported platform. Click 'Manage Asset Collections', then click + 'Create' for a supported platform. + +7. Add a description, up to 5 screenshots, and optionally a marketing banner + before clicking 'Create Asset Collection'. + + ![face-assets](/images/guides/appstore-publishing/face-assets.png) + +8. Once all asset collections required have been created, click 'Publish' or + 'Publish Privately' to make the app available only to those viewing it + through the direct link. Note that once made public, an app cannot then be + made private. + +9. After publishing, reload the page to get the public appstore link for social + sharing, as well as a deep link that can be used to directly open the + appstore in the mobile app. + + +## Publishing a Watchapp + +1. After logging in, click 'Add a Watchapp'. + +2. Enter the basic details of the watchapp, such as the title, source code URL, + and support email (if different from the one associated with this developer + account): + + ![app-title](/images/guides/appstore-publishing/app-title.png) + +3. Select the most appropriate category for the app, depending on the features + it provides: + + ![app-category](/images/guides/appstore-publishing/app-category.png) + +4. Upload the large and small icons representing the app: + + ![app-icons](/images/guides/appstore-publishing/app-icons.png) + +5. Click 'Create' to be taken to the listing page. This page details the status + of the listing, including links to subpages, a preview of the public page, + and any missing information preventing release. + + ![app-listing](/images/guides/appstore-publishing/app-listing.png) + +6. The status now says 'Missing: At least one published release'. Click 'Add a + release' to upload the `.pbw`, optionally adding release notes: + + ![app-release](/images/guides/appstore-publishing/app-release.png) + +7. Click 'Save'. After reloading the page, make the release public by clicking + 'Publish' next to the release: + + ![face-release-publish](/images/guides/appstore-publishing/face-release-publish.png) + +8. The status now says 'Missing: A complete X asset collection' for + each X supported platform. Click 'Manage Asset Collections', then click + 'Create' for a supported platform. + +9. Add a description, up to 5 screenshots, optionally up to three header images, + and a marketing banner before clicking 'Create Asset Collection'. + + ![app-assets](/images/guides/appstore-publishing/app-assets.png) + +10. Once all asset collections required have been created, click 'Publish' or + 'Publish Privately' to make the app available only to those viewing it + through the direct link. + +11. After publishing, reload the page to get the public appstore link for social + sharing, as well as a deep link that can be used to directly open the + appstore in the mobile app. + + +## Publishing a Companion App + +> A companion app is one that is written for Pebble, but exists on the Google +> Play store, or the Appstore. Adding it to the Pebble appstore allows users to +> discover it from the mobile app. + +1. After logging in, click 'Add a Companion App'. + +2. Enter the basic details of the companion app, such as the title, source code + URL, and support email (if different from the one associated with this + developer account): + + ![companion-title](/images/guides/appstore-publishing/companion-title.png) + +3. Select the most appropriate category for the app, depending on the features + it provides: + + ![companion-category](/images/guides/appstore-publishing/companion-category.png) + +4. Check a box beside each hardware platform that the companion app supports. + For example, it may be a photo viewer app that does not support Aplite. + +5. Upload the large and small icons representing the app: + + ![companion-icons](/images/guides/appstore-publishing/companion-icons.png) + +6. Click 'Create' to be taken to the listing page. The status will now read + 'Missing: At least one iOS or Android application'. Add the companion app + with eithr the 'Add Android Companion' or 'Add iOS Companion' buttons (or + both!). + +7. Add the companion app's small icon, the name of the other appstore app's + name, as well as the direct link to it's location in the appropriate + appstore. If it has been compiled with a PebbleKit 3.0, check that box: + + ![companion-link](/images/guides/appstore-publishing/companion-link.png) + +8. Once the companion appstore link has been added, click 'Publish' or 'Publish + Privately' to make the app available only to those viewing it through the + direct link. + +9. After publishing, reload the page to get the public appstore link for social + sharing, as well as a deep link that can be used to directly open the + appstore in the mobile app. diff --git a/devsite/source/_guides/appstore-publishing/whitelisting.md b/devsite/source/_guides/appstore-publishing/whitelisting.md new file mode 100644 index 00000000..bf093fbd --- /dev/null +++ b/devsite/source/_guides/appstore-publishing/whitelisting.md @@ -0,0 +1,56 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: iOS App Whitelisting +description: | + Instructions to iOS developers to get their Pebble apps whitelisted. +guide_group: appstore-publishing +generate_toc: false +order: 4 +--- + +Pebble is part of the Made For iPhone program, a requirement that hardware +accessories must meet to interact with iOS apps. If an iOS app uses PebbleKit +iOS, it must be whitelisted **before** it can be submitted to the Apple App +Store for approval. + + +## Requirements + +* The iOS companion app must only start communication with a Pebble watch on + an explicit action in the UI. It cannot auto­start upon connection and it must + stop whenever the user stops using it. Refer to the + {% guide_link communication/using-pebblekit-ios %} guide for details. + +* `com.getpebble.public` is the only external accessory protocol that can be + used by 3rd party apps. Make sure this is listed in the `Info.plist` in the + `UISupportedExternalAccessoryProtocols` array. + +* Pebble may request a build of the iOS application. If this happens, the + developer will be supplied with UDIDs to add to the provisioning profile. + TestFlight/HockeyApp is the recommended way to share builds with Pebble. + +[Whitelist a New App >{center,bg-lightblue,fg-white}](http://pbl.io/whitelist) + +After whitelisting of the new app has been confirmed, add the following +information to the "Review Notes" section of the app's Apple app submission: + +
+ MFI PPID 126683­-0003 +
+ +> Note: An iOS app does not need to be re-whitelisted every time a new update is +> released. However, Pebble reserves the right to remove an application from the +> whitelist if it appears that the app no longer meets these requirements. diff --git a/devsite/source/_guides/best-practices/building-for-every-pebble.md b/devsite/source/_guides/best-practices/building-for-every-pebble.md new file mode 100644 index 00000000..788e441c --- /dev/null +++ b/devsite/source/_guides/best-practices/building-for-every-pebble.md @@ -0,0 +1,373 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Building for Every Pebble +description: How to write one app compatible with all Pebble smartwatches. +guide_group: best-practices +order: 0 +--- + +The difference in capabilities between the various Pebble hardware platforms are +listed in +{% guide_link tools-and-resources/hardware-information %}. For example, the +Basalt, Chalk and Emery platforms support 64 colors, whereas the Aplite and +Diorite platforms only support two colors. This can make developing apps with +rich color layouts difficult when considering compatibility with other non-color +hardware. Another example is using platform specific APIs such as Health or +Dictation. + +To make life simple for users, developers should strive to write one app that +can be used on all platforms. To help make this task simpler for developers, the +Pebble SDK provides numerous methods to accommodate different hardware +capabilities in code. + + +## Preprocessor Directives + +It is possible to specify certain blocks of code to be compiled for specific +purposes by using the `#ifdef` preprocessor statement. For example, the +``Dictation`` API should be excluded on platforms with no microphone: + +```c +#if defined(PBL_MICROPHONE) + // Start dictation UI + dictation_session_start(s_dictation_session); +#else + // Microphone is not available + text_layer_set_text(s_some_layer, "Dictation not available!"); +#endif +``` + +When designing UI layouts, any use of colors on compatible platforms can be +adapted to either black or white on non-color platforms. The `PBL_COLOR` and +`PBL_BW` symbols will be defined at compile time when appropriate capabilities +are available: + +```c +#if defined(PBL_COLOR) + text_layer_set_text_color(s_text_layer, GColorRed); + text_layer_set_background_color(s_text_layer, GColorChromeYellow); +#else + text_layer_set_text_color(s_text_layer, GColorWhite); + text_layer_set_background_color(s_text_layer, GColorBlack); +#endif +``` + +This is useful for blocks of multiple statements that change depending on the +availability of color support. For single statements, this can also be achieved +by using the ``PBL_IF_COLOR_ELSE()`` macro. + +```c +window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorJaegerGreen, GColorBlack)); +``` + +See below for a complete list of defines and macros available. + + +## Available Defines and Macros + +The tables below show a complete summary of all the defines and associated +macros available to conditionally compile or omit feature-dependant code. The +macros are well-suited for individual value selection, whereas the defines are +better used to select an entire block of code. + +| Define | MACRO |Available | +|--------|-------|----------| +| `PBL_BW` | `PBL_IF_BW_ELSE()` | Running on hardware that supports only black and white. | +| `PBL_COLOR` | `PBL_IF_COLOR_ELSE()` | Running on hardware that supports 64 colors. | +| `PBL_MICROPHONE` | `PBL_IF_MICROPHONE_ELSE()` | Running on hardware that includes a microphone. | +| `PBL_COMPASS` | None | Running on hardware that includes a compass. | +| `PBL_SMARTSTRAP` | `PBL_IF_SMARTSTRAP_ELSE()` | Running on hardware that includes a smartstrap connector, but does not indicate that the connector is capable of supplying power. | +| `PBL_SMARTSTRAP_POWER` | None | Running on hardware that includes a smartstrap connector capable of supplying power. | +| `PBL_HEALTH` | `PBL_IF_HEALTH_ELSE()` | Running on hardware that supports Pebble Health and the `HealthService` API. | +| `PBL_RECT` | `PBL_IF_RECT_ELSE()` | Running on hardware with a rectangular display. | +| `PBL_ROUND` | `PBL_IF_ROUND_ELSE()` | Running on hardware with a round display. | +| `PBL_DISPLAY_WIDTH` | None | Determine the screen width in pixels. | +| `PBL_DISPLAY_HEIGHT` | None | Determine the screen height in pixels. | +| `PBL_PLATFORM_APLITE` | None | Built for Pebble/Pebble Steel. | +| `PBL_PLATFORM_BASALT` | None | Built for Pebble Time/Pebble Time Steel. | +| `PBL_PLATFORM_CHALK` | None | Built for Pebble Time Round. | +| `PBL_PLATFORM_DIORITE` | None | Built for Pebble 2. | +| `PBL_PLATFORM_EMERY` | None | Built for Pebble Time 2. | +| `PBL_SDK_2` | None | Compiling with SDK 2.x (deprecated). | +| `PBL_SDK_3` | None | Compiling with SDK 3.x. or 4.x. | + +> Note: It is strongly recommended to conditionally compile code using +> applicable feature defines instead of `PBL_PLATFORM` defines to be as specific +> as possible. + +## API Detection + +In addition to platform and capabilities detection, we now provide API +detection to detect if a specific API method is available. This approach could +be considered future-proof, since platforms and capabilities may come and go. +Let's take a look at a simple example: + +```c +#if PBL_API_EXISTS(health_service_peek_current_value) + // Do something if specific Health API exists +#endif +``` + +## Avoid Hardcoded Layout Values + +With the multiple display shapes and resolutions available, developers should +try and avoid hardcoding layout values. Consider the example +below: + +```c +static void window_load(Window *window) { + // Create a full-screen Layer - BAD + s_some_layer = layer_create(GRect(0, 0, 144, 168)); +} +``` + +The hardcoded width and height of this layer will cover the entire screen on +Aplite, Basalt and Diorite, but not on Chalk or Emery. This kind of screen +size-dependant calculation should use the ``UnobstructedArea`` bounds of the +``Window`` itself: + +```c +static void window_load(Window *window) { + // Get the unobstructed bounds of the Window + Layer window_layer = window_get_root_layer(window); + GRect window_bounds = layer_get_unobstructed_bounds(window_layer); + + // Properly create a full-screen Layer - GOOD + s_some_layer = layer_create(window_bounds); +} +``` + +Another common use of this sort of construction is to make a ``Layer`` that is +half the unobstructed screen height. This can also be correctly achieved using +the ``Window`` unobstructed bounds: + +```c +GRect layer_bounds = window_bounds; +layer_bounds.size.h /= 2; + +// Create a Layer that is half the screen height +s_some_layer = layer_create(layer_bounds); +``` + +This approach is also advantageous in simplifying updating an app for a future +new screen size, as proportional layout values will adapt as appropriate when +the ``Window`` unobstructed bounds change. + + +## Screen Sizes + +To ease the introduction of the Emery platform, the Pebble SDK introduced new +compiler directives to allow developers to determine the screen width and +height. This is preferable to using platform detection, since multiple platforms +share the same screen width and height. + +```c +#if PBL_DISPLAY_HEIGHT == 228 + uint8_t offset_y = 100; +#elif PBL_DISPLAY_HEIGHT == 180 + uint8_t offset_y = 80; +#else + uint8_t offset_y = 60; +#endif +``` + +> Note: Although this method is preferable to platform detection, it is better +to dynamically calculate the display width and height based on the unobstructed +bounds of the root layer. + +## Pebble C WatchInfo + +The ``WatchInfo`` API can be used to determine exactly which Pebble model and +color an app is running on. Apps can use this information to dynamically +modify their layout or behavior depending on which Pebble the user is wearing. + +For example, the display on Pebble Steel is located at a different vertical +position relative to the buttons than on Pebble Time. Any on-screen button hints +can be adjusted to compensate for this using ``WatchInfoModel``. + +```c +static void window_load(Window *window) { + Layer window_layer = window_get_root_layer(window); + GRect window_bounds = layer_get_bounds(window_layer); + + int button_height, y_offset; + + // Conditionally set layout parameters + switch(watch_info_get_model()) { + case WATCH_INFO_MODEL_PEBBLE_STEEL: + y_offset = 64; + button_height = 44; + break; + case WATCH_INFO_MODEL_PEBBLE_TIME: + y_offset = 58; + button_height = 56; + break; + + /* Other cases */ + + default: + y_offset = 0; + button_height = 0; + break; + + } + + // Set the Layer frame + GRect layer_frame = GRect(0, y_offset, window_bounds.size.w, button_height); + + // Create the Layer + s_label_layer = text_layer_create(layer_frame); + layer_add_child(window_layer, text_layer_get_layer(s_label_layer)); + + /* Other UI code */ + +} +``` + +Developers can also use ``WatchInfoColor`` values to theme an app for each +available color of Pebble. + +```c +static void window_load(Window *window) { + GColor text_color, background_color; + + // Choose different theme colors per watch color + switch(watch_info_get_color()) { + case WATCH_INFO_COLOR_RED: + // Red theme + text_color = GColorWhite; + background_color = GColorRed; + break; + case WATCH_INFO_COLOR_BLUE: + // Blue theme + text_color = GColorBlack; + background_color = GColorVeryLightBlue; + break; + + /* Other cases */ + + default: + text_color = GColorBlack; + background_color = GColorWhite; + break; + + } + + // Use the conditionally set value + text_layer_set_text_color(s_label_layer, text_color); + text_layer_set_background_color(s_label_layer, background_color); + + /* Other UI code */ + +} +``` + + +## PebbleKit JS Watch Info + +Similar to [*Pebble C WatchInfo*](#pebble-c-watchinfo) above, the PebbleKit JS +``Pebble.getActiveWatchInfo()`` method allows developers to determine +which model and color of Pebble the user is wearing, as well as the firmware +version running on it. For example, to obtain the model of the watch: + +> Note: See the section below to avoid problem using this function on older app +> version. + +```js +// Get the watch info +var info = Pebble.getActiveWatchInfo(); + +console.log('Pebble model: ' + info.model); +``` + + +## Detecting Platform-specific JS Features + +A number of features in PebbleKit JS (such as ``Pebble.timelineSubscribe()`` and +``Pebble.getActiveWatchInfo()``) exist on SDK 3.x. If an app tries to use any of +these on an older Pebble mobile app version where they are not available, the JS +app will crash. + +To prevent this, be sure to check for the availability of the function before +calling it. For example, in the case of ``Pebble.getActiveWatchInfo()``: + +```js +if (Pebble.getActiveWatchInfo) { + // Available. + var info = Pebble.getActiveWatchInfo(); + + console.log('Pebble model: ' + info.model); +} else { + // Gracefully handle no info available + +} +``` + + +## Platform-specific Resources + +With the availability of color support on Basalt, Chalk and Emery, developers +may wish to include color versions of resources that had previously been +pre-processed for Pebble's black and white display. Including both versions of +the resource is expensive from a resource storage perspective, and lays the +burden of packing redundant color resources in an Aplite or Diorite app when +built for multiple platforms. + +To solve this problem, the Pebble SDK allows developers to specify which version +of an image resource is to be used for each display type, using `~bw` or +`~color` appended to a file name. Resources can also be bundled only with +specific platforms using the `targetPlatforms` property for each resource. + +For more details about packaging resources specific to each platform, as well as +more tags available similar to `~color`, read +{% guide_link app-resources/platform-specific %}. + + +## Multiple Display Shapes + +With the introduction of the Chalk platform, a new round display type is +available with increased pixel resolution. To distinguish between the two +possible shapes of display, developers can use defines to conditionally +include code segments: + +```c +#if defined(PBL_RECT) + printf("This is a rectangular display!"); +#elif defined(PBL_ROUND) + printf("This is a round display!"); +#endif +``` + +Another approach to this conditional compilation is to use the +``PBL_IF_RECT_ELSE()`` and ``PBL_IF_ROUND_ELSE()`` macros, allowing values to be +inserted into expressions that might otherwise require a set of `#define` +statements similar to the previous example. This would result in needless +verbosity of four extra lines of code when only one is actually needed. These +are used in the following manner: + +```c +// Conditionally print out the shape of the display +printf("This is a %s display!", PBL_IF_RECT_ELSE("rectangular", "round")); +``` + +This mechanism is best used with window bounds-derived layout size and position +value. See the [*Avoid Hardcoded Layout Values*](#avoid-hardcoded-layout-values) +section above for more information. Making good use of the builtin ``Layer`` +types will also help safeguard apps against display shape and size changes. + +Another thing to consider is rendering text on a round display. Due to the +rounded corners, each horizontal line of text will have a different available +width, depending on its vertical position. diff --git a/devsite/source/_guides/best-practices/conserving-battery-life.md b/devsite/source/_guides/best-practices/conserving-battery-life.md new file mode 100644 index 00000000..38f1eb39 --- /dev/null +++ b/devsite/source/_guides/best-practices/conserving-battery-life.md @@ -0,0 +1,241 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Conserving Battery Life +description: How to write an app to consume power as efficiently as possible. +guide_group: best-practices +order: 1 +related_docs: + - Animation + - Timer + - AccelerometerService + - BatteryStateService + - TickTimerService + - CompassService + - Vibes + - Light +related_examples: + - title: Pebble Glancing Demo + url: https://github.com/pebble-hacks/pebble_glancing_demo +--- + +One of Pebble's strengths is its long battery life. This is due in part to using +a low-power display technology, conservative use of the backlight, and allowing +the processor to sleep whenever possible. It therefore follows that apps which +misuse high-power APIs or prevent power-saving mechanisms from working will +detract from the user's battery life. Several common causes of battery drain in +apps are discussed in this guide, alongside suggestions to help avoid them. + + +## Battery Ratings + +Any app published in the [Developer Portal](https://dev-portal.getpebble.com) +will have a battery grade associated with it, once a minimum threshold of data +has been collected. This can be used to get a rough idea of how much battery +power the app consumes. For watchfaces and apps that will be launched for long +periods of time, making sure this grade is in the A - C range should be a +priority. Read {% guide_link appstore-publishing/appstore-analytics %} to learn +more about this rating system. + + +## Time Awake + +Because the watch tries to sleep as much as possible to conserve power, any app +that keeps the watch awake will incur significant a battery penalty. Examples of +such apps include those that frequently use animations, sensors, Bluetooth +communications, and vibrations. + + +### Animations and Display Updates + +A common cause of such a drain are long-running animations that cause frequent +display updates. For example, a watchface that plays a half-second ``Animation`` +for every second that ticks by will drain the battery faster than one that does +so only once per minute. The latter approach will allow a lot more time for the +watch to sleep. + +```c +static void tick_handler(struct tm *tick_time, TimeUnits changed) { + // Update time + set_time_digits(tick_time); + + // Only update once a minute + if(tick_time->tm_sec == 0) { + play_animation(); + } +} +``` + +This also applies to apps that make use of short-interval ``Timer``s, which is +another method of creating animations. Consider giving users the option to +reduce or disable animations to further conserve power, as well as removing or +shortening animations that are not essential to the app's function or aesthetic. + +However, not all animations are bad. Efficient use of the battery can be +maintained if the animations are played at more intelligent times. For example, +when the user is holding their arm to view the screen (see +[`pebble_glancing_demo`](https://github.com/pebble-hacks/pebble_glancing_demo)) +or only when a tap or wrist shake is detected: + +```c +static void accel_tap_handler(AccelAxisType axis, int32_t direction) { + // Animate when the user flicks their wrist + play_animation(); +} +``` + +```c +accel_tap_service_subscribe(tap_handler); +``` + + +### Tick Updates + +Many watchfaces unecessarily tick once a second by using the ``SECOND_UNIT`` +constant value with the ``TickTimerService``, when they only update the display +once a minute. By using the ``MINUTE_UNIT`` instead, the amount of times the +watch is woken up per minute is reduced. + +```c +// Only tick once a minute, much more time asleep +tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); +``` + +If possible, give users the choice to disable the second hand tick and/or +animation to further save power. Extremely minimal watchfaces may also use the +``HOUR_UNIT`` value to only be updated once per hour. + +This factor is especially important for Pebble Time Round users. On this +platform the reduced battery capacity means that a watchface with animations +that play every second could reduce this to one day or less. Consider offering +configuration options to reducing tick updates on this platform to save power +where it at a premium. + + +### Sensor Usage + +Apps that make frequent usage of Pebble's onboard accelerometer and compass +sensors will also prevent the watch from going to sleep and consume more battery +power. The ``AccelerometerService`` API features the ability to configure the +sampling rate and number of samples received per update, allowing batching of +data into less frequent updates. By receiving updates less frequently, the +battery will last longer. + +```c +// Batch samples into sets of 10 per callback +const uint32_t num_samples = 10; + +// Sample at 10 Hz +accel_service_set_sampling_rate(ACCEL_SAMPLING_10HZ); + +// With this combination, only wake up the app once per second! +accel_data_service_subscribe(num_samples, accel_data_handler); +``` + +Similarly, the ``CompassService`` API allows a filter to be set on the heading +updates, allowing an app to only be notified per every 45 degree angle change, +for example. + +```c +// Only update if the heading changes significantly +compass_service_set_heading_filter(TRIG_MAX_ANGLE / 36); +``` + +In addition, making frequent use of the ``Dictation`` API will also keep the +watch awake, and also incur a penalty for keeping the Bluetooth connection +alive. Consider using the ``Storage`` API to remember previous user input and +instead present a list of previous inputs if appropriate to reduce usage of this +API. + +```c +static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, + char *transcription, void *context) { + if(status == DictationSessionStatusSuccess) { + // Display the dictated text + snprintf(s_last_text, sizeof(s_last_text), "Transcription:\n\n%s", + transcription); + text_layer_set_text(s_output_layer, s_last_text); + + // Save for later! + const int last_text_key = 0; + persist_write_string(last_text_key, s_last_text); + } +} +``` + + +### Bluetooth Usage + +Hinted at above, frequent use of the ``AppMessage`` API to send and recieve data +will cause the Bluetooth connection to enter a more responsive state, which +consumes much more power. A small time after a message is sent, the connection +will return back to a low-power state. + +The 'sniff interval' determines how often the API checks for new messages from +the phone, and should be let in the default ``SNIFF_INTERVAL_NORMAL`` state as +much as possible. Consider how infrequent communication activities can be to +save power and maintain functionality, and how data obtained over the Bluetooth +connection can be cached using the ``Storage`` API to reduce the frequency of +updates (for example, weather information in watchface). + +If the reduced sniff state must be used to transfer large amounts of data +quickly, be sure to return to the low-power state as soon as the transfer is +complete: + +```c +// Return to low power Bluetooth state +app_comm_set_sniff_interval(SNIFF_INTERVAL_NORMAL); +``` + + +## Backlight Usage + +The backlight LED is another large consumer of battery power. System-level +backlight settings may see the backlight turn on for a few seconds every time a +button is pressed. While this setting is out of the hands of developers, apps +can work to reduce the backlight on-time by minimizing the number of button +presses required to operate them. For example, use an ``ActionBarLayer`` to +execute common actions with one button press instead of a long scrolling +``MenuLayer``. + +While the ``Light`` API is available to manually turn the backlight on, it +should not be used for more than very short periods, if at all. Apps that keep +the backlight on all the time will not last more than a few hours. If the +backlight must be kept on for an extended period, make sure to return to the +automatic mode as soon as possible: + +```c +// Return to automatic backlight control +light_enable(false); +``` + + +## Vibration Motor Usage + +As a physical converter of electrical to mechanical energy, the vibration motor +also consumes a lot of power. Users can elect to use Quiet Time or turn off +vibration for notifications to save power, but apps can also contribute to this +effort. Try and keep the use of the ``Vibes`` API to a minimum and giving user +the option to disable any vibrations the app emits. Another method to reduce +vibrator power consumtion is to shorten the length of any custom sequences used. + + +## Learn More + +To learn more about power consumtion on Pebble and how battery life can be +extended through app design choices, watch the presentation below given at the +2014 Developer Retreat. + +[EMBED](//www.youtube.com/watch?v=TS0FPfgxAso) diff --git a/devsite/source/_guides/best-practices/index.md b/devsite/source/_guides/best-practices/index.md new file mode 100644 index 00000000..686038b1 --- /dev/null +++ b/devsite/source/_guides/best-practices/index.md @@ -0,0 +1,39 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Best Practices +description: | + Information to help optimize apps and ensure a good user experience. +guide_group: best-practices +menu: false +permalink: /guides/best-practices/ +generate_toc: false +hide_comments: true +--- + +In order to get the most out of the Pebble SDK, there are numerous opportunities +for optimization that can allow apps to use power more efficiently, display +correctly on all display shapes and sizes, and help keep large projects +maintainable. + +Information on these topics is contained in this collection of guides. Pebble +recommends that developers try and incorporate as many of these practices into +their apps as possible, to give themselves and users the best experience of +their app. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/best-practices/modular-app-architecture.md b/devsite/source/_guides/best-practices/modular-app-architecture.md new file mode 100644 index 00000000..62a7a22a --- /dev/null +++ b/devsite/source/_guides/best-practices/modular-app-architecture.md @@ -0,0 +1,274 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Modular App Architecture +description: | + How to break up a complex app into smaller pieces for managablilty, modularity + and reusability. +guide_group: best-practices +order: 3 +related_examples: + - title: Modular App Example + url: https://github.com/pebble-examples/modular-app-example/ +--- + +Most Pebble projects (such as a simple watchface) work fine as a single-file +project. This means that all the code is located in one `.c` file. However, as +the size of a single-file Pebble project increases, it can become harder to keep +track of where all the different components are located, and to track down how +they interact with each other. For example, a hypothetical app may have many +``Window``s, perform communication over ``AppMessage`` with many types of data +items, store and persist a large number of data items, or include components +that may be valuable in other projects. + +As a first example, the Pebble SDK is already composed of separate modules such +as ``Window``, ``Layer``, ``AppMessage`` etc. The implementation of each is +separate from the rest and the interface for developers to use in each module is +clearly defined and will rarely change. + +This guide aims to provide techniques that can be used to break up such an app. +The advantages of a modular approach include: + +* App ``Window``s can be kept separate and are easier to work on. + +* A clearly defined interface between components ensures internal changes do not + affect other modules. + +* Modules can be re-used in other projects, or even made into sharable + libraries. + +* Inter-component variable dependencies do not occur, which can otherwise cause + problems if their type or size changes. + +* Sub-component complexity is hidden in each module. + +* Simpler individual files promote maintainability. + +* Modules can be more easily tested. + + +## A Basic Project + +A basic Pebble project starts life with the `new-project` command: + +```bash +$ pebble new-project modular-project +``` + +This new project will contain the following default file structure. The +`modular-project.c` file will contain the entire app, including `main()`, +`init()` and `deinit()`, as well as a ``Window`` and a child ``TextLayer``. + +```text +modular-project/ + resources/ + src/ + modular-project.c + package.json + wscript +``` + +For most projects, this structure is perfectly adequate. When the `.c` file +grows to several hundred lines long and incorporates several sub-components with +many points of interaction with each other through shared variables, the +complexity reaches a point where some new techniques are needed. + + +## Creating a Module + +In this context, a 'module' can be thought of as a C header and source file +'pair', a `.h` file describing the module's interface and a `.c` file containing +the actual logic and code. The header contains standard statements to prevent +redefinition from being `#include`d multiple times, as well as all the function +prototypes the module makes available for other modules to use. + +By making a sub-component of the app into a module, the need for messy global +variables is removed and a clear interface between them is defined. The files +themselves are located in a `modules` directory inside the project's main `src` +directory, keeping them in a separate location to other components of the app. +Thus the structure of the project with a `data` module added (and explained +below) is now this: + +```text +modular-project/ + resources/ + src/ + modules/ + data.h + data.c + modular-project.c + package.json + wscript +``` + +The example module's pair of files is shown below. It manages a dynamically +allocated array of integers, and includes an interface to setting and getting +values from the array. The array itself is private to the module thanks for the +[`static`](https://en.wikipedia.org/wiki/Static_(keyword)) keyword. This +technique allows other components of the app to call the 'getters' and 'setters' +with the correct parameters as per the module's interface, without worrying +about the implementation details. + +`src/modules/data.h` + +```c +#pragma once // Prevent errors by being included multiple times + +#include // Pebble SDK symbols + +void data_init(int array_length); + +void data_deinit(); + +void data_set_array_value(int index, int new_value); + +int data_get_array_value(int index); +``` + +`src/modules/data.c` + +```c +#include "data.h" + +static int* s_array; + +void data_init(int array_length) { + if(!s_array) { + s_array = (int*)malloc(array_length * sizeof(int)); + } +} + +void data_deinit() { + if(s_array) { + free(s_array); + s_array = NULL; + } +} + +void data_set_array_value(int index, int new_value) { + s_array[index] = new_value; +} + +int data_get_array_value(int index) { + return s_array[index]; +} +``` + + +## Keep Multiple Windows Separate + +The ``Window Stack`` lifecycle makes the task of keeping each ``Window`` +separate quite easy. Each one has a `.load` and `.unload` handler which should +be used to create and destroy its UI components and other data. + +The first step to modularizing the new app is to keep each ``Window`` in its own +module. The first ``Window``'s code can be moved out of `src/modular-project.c` +into a new module in `src/windows/` called 'main_window': + +`src/windows/main_window.h` + +```c +#pragma once + +#include + +void main_window_push(); +``` + +`src/windows/main_window.c` + +```c +#include "main_window.h" + +static Window *s_window; + +static void window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); +} + +static void window_unload(Window *window) { + window_destroy(s_window); +} + +void main_window_push() { + if(!s_window) { + s_window = window_create(); + window_set_window_handlers(s_window, (WindowHandlers) { + .load = window_load, + .unload = window_unload, + }); + } + window_stack_push(s_window, true); +} +``` + + +## Keeping Main Clear + +After moving the ``Window`` code out of the main `.c` file, it can be safely +renamed `main.c` to reflect its contents. This allows the main `.c` file to show +a high-level overview of the app as a whole. Simply `#include` the required +modules and windows to initialize and deinitialize the rest of the app as +necessary: + +`src/main.c` + +```c +#include + +#include "modules/data.h" +#include "windows/main_window.h" + +static void init() { + const int array_size = 16; + data_init(array_size); + + main_window_push(); +} + +static void deinit() { + data_deinit(); +} + +int main() { + init(); + app_event_loop(); + deinit(); +} +``` + +Thus the structure of the project is now: + +```text +modular-project/ + resources/ + src/ + modules/ + data.h + data.c + windows/ + main_window.h + main_window.c + main.c + package.json + wscript +``` + +With this structured approach to organizing the different functional components +of an app, the maintainability of the project will not suffer as it grows in +size and complexity. A useful module can even be shared and reused as a library, +which is preferrable to pasting chunks of code that may have other messy +dependencies elsewhere in the project. diff --git a/devsite/source/_guides/communication/advanced-communication.md b/devsite/source/_guides/communication/advanced-communication.md new file mode 100644 index 00000000..9b054199 --- /dev/null +++ b/devsite/source/_guides/communication/advanced-communication.md @@ -0,0 +1,657 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Advanced Communication +description: | + Details of communication tips and best practices for more advanced scenarios. +guide_group: communication +order: 0 +related_docs: + - AppMessage +related_examples: + - title: JS Ready Example + url: https://github.com/pebble-examples/js-ready-example + - title: List Items Example + url: https://github.com/pebble-examples/list-items-example + - title: Accel Data Stream + url: https://github.com/pebble-examples/accel-data-stream + - title: PNG Download Example + url: https://github.com/pebble-examples/png-download-example + - title: Pebble Faces + url: https://github.com/pebble-examples/pebble-faces +platform_choice: true +--- + +Many types of connected Pebble watchapps and watchfaces perform common tasks +such as the ones discussed here. Following these best practices can increase the +quality of the implementation of each one, and avoid common bugs. + + +## Waiting for PebbleKit JS + +Any app that wishes to send data from the watch to the phone via +{% guide_link communication/using-pebblekit-js "PebbleKit JS" %} **must** +wait until the JavaScript `ready` event has occured, indicating that the phone +has loaded the JavaScript component of the launching app. If this JavaScript +code implements the `appmessage` event listsner, it is ready to receive data. + +> An watchapp that only *receives* data from PebbleKit JS does not have to wait +> for the `ready` event. In addition, Android companion apps do not have to wait +> for such an event thanks to the `Intent` system. iOS companion apps must wait +> for `-watchDidConnect:`. + +
+{% markdown %} +A simple method is to define a key in `package.json` that will be interpreted by +the watchapp to mean that the JS environment is ready for exchange data: + +```js +"messageKeys": [ + "JSReady" +] +``` +{% endmarkdown %} +
+ +
+{% markdown %} +A simple method is to define a key in Settings that will be interpreted by +the watchapp to mean that the JS environment is ready for exchange data: + +* JSReady +{% endmarkdown %} +
+ +The watchapp should implement a variable that describes if the `ready` event has +occured. An example is shown below: + +```c +static bool s_js_ready; +``` + +This can be exported in a header file for other parts of the app to check. Any +parts of the app that are waiting should call this as part of a +[retry](#timeouts-and-retries) mechanism. + +```c +bool comm_is_js_ready() { + return s_js_ready; +} +``` + +The state of this variable will be `false` until set to `true` when the `ready` +event causes the key to be transmitted: + +```js +Pebble.addEventListener('ready', function() { + console.log('PebbleKit JS ready.'); + + // Update s_js_ready on watch + Pebble.sendAppMessage({'JSReady': 1}); +}); +``` + +This key should be interpreted in the app's ``AppMessageInboxReceived`` +implementation: + +```c +static void inbox_received_handler(DictionaryIterator *iter, void *context) { + Tuple *ready_tuple = dict_find(iter, MESSAGE_KEY_JSReady); + if(ready_tuple) { + // PebbleKit JS is ready! Safe to send messages + s_js_ready = true; + } +} +``` + + +## Timeouts and Retries + +Due to the wireless and stateful nature of the Bluetooth connection, some +messages sent between the watch and phone may fail. A tried-and-tested method +for dealing with these failures is to implement a 'timeout and retry' mechanism. +Under such a scheme: + +* A message is sent and a timer started. + +* If the message is sent successfully (and optionally a reply received), the + timer is cancelled. + +* If the timer elapses before the message can be sent successfully, the message + is reattempted. Depending on the nature of the failure, a suitable retry + interval (such as a few seconds) is used to avoid saturating the connection. + +The interval chosen before a timeout occurs and the message is resent may vary +depending on the circumstances. The first failure should be reattempted fairly +quickly (one second), with the interval increasing as successive failures +occurs. If the connection is not available the timer interval should be +[even longer](https://en.wikipedia.org/wiki/Exponential_backoff), or wait until +the connection is restored. + + +### Using a Timeout Timer + +The example below shows the sending of a message and scheduling a timeout timer. +The first step is to declare a handle for the timeout timer: + +```c +static AppTimer *s_timeout_timer; +``` + +When the message is sent, the timer should be scheduled: + +```c +static void send_with_timeout(int key, int value) { + // Construct and send the message + DitionaryIterator *iter; + if(app_message_outbox_begin(&iter) == APP_MSG_OK) { + dict_write_int(iter, key, &value, sizeof(int), true); + app_message_outbox_send(); + } + + // Schedule the timeout timer + const int interval_ms = 1000; + s_timout_timer = app_timer_register(interval_ms, timout_timer_handler, NULL); +} +``` + +If the ``AppMessageOutboxSent`` is called, the message was a success, and the +timer should be cancelled: + +```c +static void outbox_sent_handler(DictionaryIterator *iter, void *context) { + // Successful message, the timeout is not needed anymore for this message + app_timer_cancel(s_timout_timer); +} +``` + +### Retry a Failed Message + +However, if the timeout timer elapses before the message's success can be +determined or an expected reply is not received, the callback to +`timout_timer_handler()` should be used to inform the user of the failure, and +schedule another attempt and retry the message: + +```c +static void timout_timer_handler(void *context) { + // The timer elapsed because no success was reported + text_layer_set_text(s_status_layer, "Failed. Retrying..."); + + // Retry the message + send_with_timeout(some_key, some_value); +} +``` + +Alternatively, if the ``AppMessageOutboxFailed`` is called the message failed to +send, sometimes immediately. The timeout timer should be cancelled and the +message reattempted after an additional delay (the 'retry interval') to avoid +saturating the channel: + +```c +static void outbox_failed_handler(DictionaryIterator *iter, + AppMessageResult reason, void *context) { + // Message failed before timer elapsed, reschedule for later + if(s_timout_timer) { + app_timer_cancel(s_timout_timer); + } + + // Inform the user of the failure + text_layer_set_text(s_status_layer, "Failed. Retrying..."); + + // Use the timeout handler to perform the same action - resend the message + const int retry_interval_ms = 500; + app_timer_register(retry_interval_ms, timout_timer_handler, NULL); +} +``` + +> Note: All eventualities where a message fails must invoke a resend of the +> message, or the purpose of an automated 'timeout and retry' mechanism is +> defeated. However, the number of attempts made and the interval between them +> is for the developer to decide. + + +## Sending Lists + +Until SDK 3.8, the size of ``AppMessage`` buffers did not facilitate sending +large amounts of data in one message. With the current buffer sizes of up to 8k +for each an outbox the need for efficient transmission of multiple sequential +items of data is lessened, but the technique is still important. For instance, +to transmit sensor data as fast as possible requires careful scheduling of +successive messages. + +Because there is no guarantee of how long a message will take to transmit, +simply using timers to schedule multiple messages after one another is not +reliable. A much better method is to make good use of the callbacks provided by +the ``AppMessage`` API. + + +### Sending a List to the Phone + +For instance, the ``AppMessageOutboxSent`` callback can be used to safely +schedule the next message to the phone, since the previous one has been +acknowledged by the other side at that time. Here is an example array of items: + +```c +static int s_data[] = { 2, 4, 8, 16, 32, 64 }; + +#define NUM_ITEMS sizeof(s_data); +``` + +A variable can be used to keep track of the current list item index that should +be transmitted next: + +```c +static int s_index = 0; +``` + +When a message has been sent, this index is used to construct the next message: + +
+{% markdown %} +> Note: A useful key scheme is to use the item's array index as the key. For +> PebbleKit JS that number of keys will have to be declared in `package.json`, +> like so: `someArray[6]` +{% endmarkdown %} +
+
+{% markdown %} +> Note: A useful key scheme is to use the item's array index as the key. For +> PebbleKit JS that number of keys will have to be declared in the project's +> 'Settings' page, like so: `someArray[6]` +{% endmarkdown %} +
+ +```c +static void outbox_sent_handler(DictionaryIterator *iter, void *context) { + // Increment the index + s_index++; + + if(s_index < NUM_ITEMS) { + // Send the next item + DictionaryIterator *iter; + if(app_message_outbox_begin(&iter) == APP_MSG_OK) { + dict_write_int(iter, MESSAGE_KEY_someArray + s_index, &s_data[s_index], sizeof(int), true); + app_message_outbox_send(); + } + } else { + // We have reached the end of the sequence + APP_LOG(APP_LOG_LEVEL_INFO, "All transmission complete!"); + } +} +``` + +This results in a callback loop that repeats until the last data item has been +transmitted, and the index becomes equal to the total number of items. This +technique can be combined with a timeout and retry mechanism to reattempt a +particular item if transmission fails. This is a good way to avoid gaps in the +received data items. + +On the phone side, the data items are received in the same order. An analogous +`index` variable is used to keep track of which item has been received. This +process will look similar to the example shown below: + +```js +var NUM_ITEMS = 6; +var keys = require('message_keys'); + +var data = []; +var index = 0; + +Pebble.addEventListener('appmessage', function(e) { + // Store this data item + data[index] = e.payload[keys.someArray + index]; + + // Increment index for next message + index++; + + if(index == NUM_ITEMS) { + console.log('Received all data items!'); + } +}); +``` + + +### Sending a List to Pebble + +Conversely, the `success` callback of `Pebble.sendAppMessage()` in PebbleKit JS +is the equivalent safe time to send the next message to the watch. + +An example implementation that achieves this is shown below. After the message +is sent with `Pebble.sendAppMessage()`, the `success` callback calls the +`sendNextItem()` function repeatedly until the index is larger than that of the +last list item to be sent, and transmission will be complete. Again, an index +variable is maintained to keep track of which item is being transmitted: + +```js +var keys = require('message_keys'); +function sendNextItem(items, index) { + // Build message + var key = keys.someArray + index; + var dict = {}; + dict[key] = items[index]; + + // Send the message + Pebble.sendAppMessage(dict, function() { + // Use success callback to increment index + index++; + + if(index < items.length) { + // Send next item + sendNextItem(items, index); + } else { + console.log('Last item sent!'); + } + }, function() { + console.log('Item transmission failed at index: ' + index); + }); +} + +function sendList(items) { + var index = 0; + sendNextItem(items, index); +} + +function onDownloadComplete(responseText) { + // Some web response containing a JSON object array + var json = JSON.parse(responseText); + + // Begin transmission loop + sendList(json.items); +} +``` + +On the watchapp side, the items are received in the same order in the +``AppMessageInboxReceived`` handler: + +```c +#define NUM_ITEMS 6 + +static int s_index; +static int s_data[NUM_ITEMS]; +``` + +```c +static void inbox_received_handler(DictionaryIterator *iter, void *context) { + Tuple *data_t = dict_find(iter, MESSAGE_KEY_someArray + s_index); + if(data_t) { + // Store this item + s_data[index] = (int)data_t->value->int32; + + // Increment index for next item + s_index++; + } + + if(s_index == NUM_ITEMS) { + // We have reached the end of the sequence + APP_LOG(APP_LOG_LEVEL_INFO, "All transmission complete!"); + } +} +``` + +This sequence of events is demonstrated for PebbleKit JS, but the same technique +can be applied exactly to either and Android or iOS companion app wishing to +transmit many data items to Pebble. + + +Get the complete source code for this example from the +[`list-items-example`](https://github.com/pebble-examples/list-items-example) +repository on GitHub. + + +## Sending Image Data + +A common task developers want to accomplish is display a dynamically loaded +image resource (for example, showing an MMS photo or a news item thumbnail +pulled from a webservice). Because some images could be larger than the largest +buffer size available to the app, the techniques shown above for sending lists +also prove useful here, as the image is essentially a list of color byte values. + + +### Image Data Format + +There are two methods available for displaying image data downloaded from the +web: + +1. Download a `png` image, transmit the compressed data, and decompress using + ``gbitmap_create_from_png_data()``. This involves sending less data, but can + be prone to failure depending on the exact format of the image. The image + must be in a compatible palette (1, 2, 4, or 8-bit) and small enough such + that there is enough memory for a compessed copy, an uncompressed copy, and + ~2k overhead when it is being processed. + +2. Download a `png` image, decompress in the cloud or in PebbleKit JS into an + array of image pixel bytes, transmit the pixel data into a blank + ``GBitmap``'s `data` member. Each byte must be in the compatible Pebble color + format (2 bits per ARGB). This process can be simplified by pre-formatting + the image to be dowloaded, as resizing or palette-reduction is difficult to + do locally. + + +### Sending Compressed PNG Data + +As the fastest and least complex of the two methods described above, an example +of how to display a compressed PNG image will be discussed here. The image that +will be displayed is +[the HTML 5 logo](https://www.w3.org/html/logo/): + +![](http://developer.getpebble.com.s3.amazonaws.com/assets/other/html5-logo-small.png) + +> Note: The above image has been resized and palettized for compatibility. + +To download this image in PebbleKit JS, use an `XmlHttpRequest` object. It is +important to specify the `responseType` as 'arraybuffer' to obtain the image +data in the correct format: + +```js +function downloadImage() { + var url = 'http://developer.getpebble.com.s3.amazonaws.com/assets/other/html5-logo-small.png'; + + var request = new XMLHttpRequest(); + request.onload = function() { + processImage(this.response); + }; + request.responseType = "arraybuffer"; + request.open("GET", url); + request.send(); +} +``` + +When the response has been received, `processImage()` will be called. The +received data must be converted into an array of unsigned bytes, which is +achieved through the use of a `Uint8Array`. This process is shown below (see +the +[`png-download-example`](https://github.com/pebble-examples/png-download-example) +repository for the full example): + +```js +function processImage(responseData) { + // Convert to a array + var byteArray = new Uint8Array(responseData); + var array = []; + for(var i = 0; i < byteArray.byteLength; i++) { + array.push(byteArray[i]); + } + + // Send chunks to Pebble + transmitImage(array); +} +``` + +Now that the image data has been converted, the transmission to Pebble can +begin. At a high level, the JS side transmits the image data in chunks, using an +incremental array index to coordinate saving of data on the C side in a mirror +array. In downloading the image data, the following keys are used for the +specified purposes: + +| Key | Purpose | +|-----|---------| +| `Index` | The array index that the current chunk should be stored at. This gets larger as each chunk is transmitted. | +| `DataLength` | This length of the entire data array to be downloaded. As the image is compressed, this is _not_ the product of the width and height of the image. | +| `DataChunk` | The chunk's image data. | +| `ChunkSize` | The size of this chunk. | +| `Complete` | Used to signify that the image transfer is complete. | + +The first message in the sequence should tell the C side how much memory to +allocate to store the compressed image data: + +```js +function transmitImage(array) { + var index = 0; + var arrayLength = array.length; + + // Transmit the length for array allocation + Pebble.sendAppMessage({'DataLength': arrayLength}, function(e) { + // Success, begin sending chunks + sendChunk(array, index, arrayLength); + }, function(e) { + console.log('Failed to initiate image transfer!'); + }) +} +``` + +If this message is successful, the transmission of actual image data commences +with the first call to `sendChunk()`. This function calculates the size of the +next chunk (the smallest of either the size of the `AppMessage` inbox buffer, or +the remainder of the data) and assembles the dictionary containing the index in +the array it is sliced from, the length of the chunk, and the actual data +itself: + +```js +function sendChunk(array, index, arrayLength) { + // Determine the next chunk size + var chunkSize = BUFFER_SIZE; + if(arrayLength - index < BUFFER_SIZE) { + // Resize to fit just the remaining data items + chunkSize = arrayLength - index; + } + + // Prepare the dictionary + var dict = { + 'DataChunk': array.slice(index, index + chunkSize), + 'ChunkSize': chunkSize, + 'Index': index + }; + + // Send the chunk + Pebble.sendAppMessage(dict, function() { + // Success + index += chunkSize; + + if(index < arrayLength) { + // Send the next chunk + sendChunk(array, index, arrayLength); + } else { + // Complete! + Pebble.sendAppMessage({'Complete': 0}); + } + }, function(e) { + console.log('Failed to send chunk with index ' + index); + }); +} +``` + +After each chunk is sent, the index is incremented with the size of the chunk +that was just sent, and compared to the total length of the array. If the index +exceeds the size of the array, the loop has sent all the data (this could be +just a single chunk if the array is smaller than the maximum message size). The +`AppKeyComplete` key is sent to inform the C side that the image is complete and +ready for display. + + +### Receiving Compressed PNG Data + +In the previous section, the process for using PebbleKit JS to download and +transmit an image to the C side was discussed. The process for storing and +displaying this data is discussed here. Only when both parts work in harmony can +an image be successfully shown from the web. + +The majority of the process takes place within the watchapp's +``AppMessageInboxReceived`` handler, with the presence of each key being +detected and the appropriate actions taken to reconstruct the image. + +The first item expected is the total size of the data to be transferred. This is +recorded (for later use with ``gbitmap_create_from_png_data()``) and the buffer +used to store the chunks is allocated to this exact size: + +```c +static uint8_t *s_img_data; +static int s_img_size; +``` + +```c +// Get the received image chunk +Tuple *img_size_t = dict_find(iter, MESSAGE_KEY_DataLength); +if(img_size_t) { + s_img_size = img_size_t->value->int32; + + // Allocate buffer for image data + img_data = (uint8_t*)malloc(s_img_size * sizeof(uint8_t)); +} +``` + +When the message containing the data size is acknowledged, the JS side begins +sending chunks with `sendChunk()`. When one of these subsequent messages is +received, the three keys (`DataChunk`, `ChunkSize`, and +`Index`) are used to store that chunk of data at the correct offset in the +array: + +```c +// An image chunk +Tuple *chunk_t = dict_find(iter, MESSAGE_KEY_DataChunk); +if(chunk_t) { + uint8_t *chunk_data = chunk_t->value->data; + + Tuple *chunk_size_t = dict_find(iter, MESSAGE_KEY_ChunkSize); + int chunk_size = chunk_size_t->value->int32; + + Tuple *index_t = dict_find(iter, MESSAGE_KEY_Index); + int index = index_t->value->int32; + + // Save the chunk + memcpy(&s_img_data[index], chunk_data, chunk_size); +} +``` + +Finally, once the array index exceeds the size of the data array on the JS side, +the `AppKeyComplete` key is transmitted, triggering the data to be transformed +into a ``GBitmap``: + +```c +static BitmapLayer *s_bitmap_layer; +static GBitmap *s_bitmap; +``` + +```c +// Complete? +Tuple *complete_t = dict_find(iter, MESSAGE_KEY_Complete); +if(complete_t) { + // Create new GBitmap from downloaded PNG data + s_bitmap = gbitmap_create_from_png_data(s_img_data, s_img_size); + + // Show the image + if(s_bitmap) { + bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap); + } else { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error creating GBitmap from PNG data!"); + } +} +``` + +The final result is a compressed PNG image downloaded from the web displayed in +a Pebble watchapp. + +Get the complete source code for this example from the +[`png-download-example`](https://github.com/pebble-examples/png-download-example) +repository on GitHub. diff --git a/devsite/source/_guides/communication/datalogging.md b/devsite/source/_guides/communication/datalogging.md new file mode 100644 index 00000000..4c796891 --- /dev/null +++ b/devsite/source/_guides/communication/datalogging.md @@ -0,0 +1,259 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Datalogging +description: | + Information on how to collect data batches using the Datalogging API. +guide_group: communication +order: 1 +related_examples: + - title: Tricorder + - url: https://github.com/pebble-examples/tricorder +--- + +In addition to the more realtime ``AppMessage`` API, the Pebble SDK also +includes the ``Datalogging`` API. This is useful for applications where data can +be sent in batches at time intervals that make the most sense (for example, to +save battery power by allowing the watch to spend more time sleeping). + +Datalogging also allows upto 640 kB of data to be buffered on the watch until a +connection is available, instead of requiring a connection be present at all +times. If data is logged while the watch is disconnected, it will be transferred +to the Pebble mobile app in batches for processing at the next opportunity. The +data is then passed on to any +{% guide_link communication/using-pebblekit-android %} or +{% guide_link communication/using-pebblekit-ios %} companion +app that wishes to process it. + + +## Collecting Data + +Datalogging can capture any values that are compatible with one of the +``DataLoggingItemType`` `enum` (byte array, unsigned integer, and integer) +values, with common sources including accelerometer data or compass data. + + +### Creating a Session + +Data is logged to a 'session' with a unique identifier or tag, which allows a +single app to have multiple data logs for different types of data. First, define +the identifier(s) that should be used where appropriate: + +```c +// The log's ID. Only one required in this example +#define TIMESTAMP_LOG 1 +``` + +Next, a session must first be created before any data can be logged to it. This +should be done during app initialization, or just before the first time an app +needs to log some data: + +```c +// The session reference variable +static DataLoggingSessionRef s_session_ref; +``` + +```c +static void init() { + // Begin the session + s_session_ref = data_logging_create(TIMESTAMP_LOG, DATA_LOGGING_INT, sizeof(int), true); + + /* ... */ +} +``` + +> Note: The final parameter of ``data_logging_create()`` allows a previous log +> session to be continued, instead of starting from screatch on each app launch. + + +### Logging Data + +Once the log has been created or resumed, data collection can proceed. Each call +to ``data_logging_log()`` will add a new entry to the log indicated by the +``DataLoggingSessionRef`` variable provided. The success of each logging +operation can be checked using the ``DataLoggingResult`` returned: + +```c +const int value = 16; +const uint32_t num_values = 1; + +// Log a single value +DataLoggingResult result = data_logging_log(s_session_ref, &value, num_values); + +// Was the value successfully stored? If it failed, print the reason +if(result != DATA_LOGGING_SUCCESS) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error logging data: %d", (int)result); +} +``` + + +### Finishing a Session + +Once all data has been logged or the app is exiting, the session must be +finished to signify that the data is to be either transferred to the connected +phone (if available), or stored for later transmission. + +```c +// Finish the session and sync data if appropriate +data_logging_finish(s_session_ref); +``` + +> Note: Once a session has been finished, data cannot be logged to its +> ``DataLoggingSessionRef`` until it is resumed or began anew. + + +## Receiving Data + +> Note: Datalogging data cannot be received via PebbleKit JS. + +Data collected with the ``Datalogging`` API can be received and processed in a +mobile companion app using PebbleKit Android or PebbleKit iOS. This enables it +to be used in a wide range of general applications, such as detailed analysis of +accelerometer data for health research, or transmission to a third-party web +service. + + +### With PebbleKit Android + +PebbleKit Android allows collection of data by registering a +`PebbleDataLogReceiver` within your `Activity` or `Service`. When creating a +receiver, be careful to provide the correct UUID to match that of the watchapp +that is doing that data collection. For example: + +```java +// The UUID of the watchapp +private UUID APP_UUID = UUID.fromString("64fcb54f-76f0-418a-bd7d-1fc1c07c9fc1"); +``` + +Use the following overridden methods to collect data and determine when the +session has been finished by the watchapp. In the example below, each new +integer received represents the uptime of the watchapp, and is displayed in an +Android `TextView`: + +```java +// Create a receiver to collect logged data +PebbleKit.PebbleDataLogReceiver dataLogReceiver = + new PebbleKit.PebbleDataLogReceiver(APP_UUID) { + + @Override + public void receiveData(Context context, UUID logUuid, Long timestamp, + Long tag, int data) { + // super() (removed from IDE-generated stub to avoid exception) + + Log.i(TAG, "New data for session " + tag + "!"); + + // Cumulatively add the new data item to a TextView's current text + String current = dataView.getText().toString(); + current += timestamp.toString() + ": " + data + + "s since watchapp launch.\n"; + dataView.setText(current); + } + + @Override + public void onFinishSession(Context context, UUID logUuid, Long timestamp, + Long tag) { + Log.i(TAG, "Session " + tag + " finished!"); + } + +}; + +// Register the receiver +PebbleKit.registerDataLogReceiver(getApplicationContext(), dataLogReceiver); +``` + +
+{% markdown %} +**Important** + +If your Java IDE automatically adds a line of code to call super() when you +create the method, the code will result in an UnsupportedOperationException. +Ensure you remove this line to avoid the exception. +{% endmarkdown %} +
+ +Once the `Activity` or `Service` is closing, you should attempt to unregister +the receiver. However, this is not always required (and will cause an exception +to be thrown if invoked when not required), so use a `try, catch` statement: + +```java +@Override +protected void onPause() { + super.onPause(); + + try { + unregisterReceiver(dataLogReceiver); + } catch(Exception e) { + Log.w(TAG, "Receiver did not need to be unregistered"); + } +} +``` + + +### With PebbleKit iOS + +The process of collecing data via a PebbleKit iOS companion mobile app is +similar to that of using PebbleKit Android. Once your app is a delegate of +``PBDataLoggingServiceDelegate`` (see +{% guide_link communication/using-pebblekit-ios %} for details), +simply register the class as a datalogging delegate: + +```objective-c +// Get datalogging data by becoming the delegate +[[PBPebbleCentral defaultCentral] + dataLoggingServiceForAppUUID:myAppUUID].delegate = self; +``` + +Being a datalogging delegate allows the class to receive two additional +[callbacks](/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/) for when new data +is available, and when the session has been finished by the watch. Implement +these callbacks to read the new data: + +```objective-c +- (BOOL)dataLoggingService:(PBDataLoggingService *)service + hasSInt32s:(const SInt32 [])data + numberOfItems:(UInt16)numberOfItems + forDataLog:(PBDataLoggingSessionMetadata *)log { + NSLog(@"New data received!"); + + // Append newest data to displayed string + NSString *current = self.dataView.text; + NSString *newString = [NSString stringWithFormat:@"New item: %d", data[0]]; + current = [current stringByAppendingString:newString]; + self.dataView.text = current; + + return YES; +} + +- (void)dataLoggingService:(PBDataLoggingService *)service + logDidFinish:(PBDataLoggingSessionMetadata *)log { + NSLog(@"Finished data log: %@", log); +} +``` + + +### Special Considerations for iOS Apps + +* The logic to deal with logs with the same type of data (i.e., the same + tag/type) but from different sessions (different timestamps) must be created + by the developer using the delegate callbacks. + +* To check whether the data belongs to the same log or not, use `-isEqual:` on + `PBDataLoggingSessionMetadata`. For convenience, + `PBDataLoggingSessionMetadata` can be serialized using `NSCoding`. + +* Using multiple logs in parallel (for example to transfer different kinds of + information) will require extra logic to re-associate the data from these + different logs, which must also be implemented by the developer. + diff --git a/devsite/source/_guides/communication/index.md b/devsite/source/_guides/communication/index.md new file mode 100644 index 00000000..ea5fc266 --- /dev/null +++ b/devsite/source/_guides/communication/index.md @@ -0,0 +1,96 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Communication +description: | + How to talk to the phone via PebbleKit with JavaScript and on Android or iOS. +guide_group: communication +menu: false +permalink: /guides/communication/ +generate_toc: false +hide_comments: true +platform_choice: true +--- + +All Pebble watchapps and watchfaces have the ability to communicate with the +outside world through its connection to the user's phone. The PebbleKit +collection of libraries (see below) is available to facilitate this +communication between watchapps and phone apps. Examples of additional +functionality made possible through PebbleKit include, but are not limited to +apps that can: + +* Display weather, news, stocks, etc. + +* Communicate with other web services. + +* Read and control platform APIs and features of the connected phone. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} + + +## Communication Model + +Pebble communicates with the connected phone via the Bluetooth connection, which +is the same connection that delivers notifications and other alerts in normal +use. Developers can leverage this connection to send and receive arbitrary data +using the ``AppMessage`` API. + +Depending on the requirements of the app, there are three possible ways to +receive data sent from Pebble on the connected phone: + +* {% guide_link communication/using-pebblekit-js %} - A JavaScript + environment running within the official Pebble mobile app with web, + geolocation, and extended storage access. + +* {% guide_link communication/using-pebblekit-android %} - + A library available to use in Android companion apps that allows them to + interact with standard Android platform APIs. + +* {% guide_link communication/using-pebblekit-ios %} - + As above, but for iOS companion apps. + +
+{% markdown %} +**Important** + +PebbleKit JS cannot be used in conjunction with PebbleKit Android or PebbleKit +iOS. +{% endmarkdown %} +
+ +All messages sent from a Pebble watchapp or watchface will be delivered to the +appropriate phone app depending on the layout of the developer's project: + +
+{% markdown %} +* If at least an `index.js` file is present in `src/pkjs/`, the message will be + handled by PebbleKit JS. +{% endmarkdown %} +
+
+{% markdown %} +* If the project contains at least one JavaScript file, the message will be + handled by PebbleKit JS. +{% endmarkdown %} +
+ +* If there is no valid JS file present (at least an `index.js`) in the project, + the message will be delivered to the official Pebble mobile app. If there is a + companion app installed that has registered a listener with the same UUID as + the watchapp, the message will be forwarded to that app via PebbleKit + Android/iOS. diff --git a/devsite/source/_guides/communication/sending-and-receiving-data.md b/devsite/source/_guides/communication/sending-and-receiving-data.md new file mode 100644 index 00000000..f07b683f --- /dev/null +++ b/devsite/source/_guides/communication/sending-and-receiving-data.md @@ -0,0 +1,437 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Sending and Receiving Data +description: | + How to send and receive data between your watchapp and phone. +guide_group: communication +order: 5 +--- + +Before using ``AppMessage``, a Pebble C app must set up the buffers used for the +inbox and outbox. These are used to store received messages that have not yet +been processed, and sent messages that have not yet been transmitted. In +addition, callbacks may be registered to allow an app to respond to any success +or failure events that occur when dealing with messages. Doing all of this is +discussed in this guide. + + +## Message Structure + +Every message sent or received using the ``AppMessage`` API is stored in a +``DictionaryIterator`` structure, which is essentially a list of ``Tuple`` +objects. Each ``Tuple`` contains a key used to 'label' the value associated with +that key. + +When a message is sent, a ``DictionaryIterator`` is filled with a ``Tuple`` for +each item of outgoing data. Conversely, when a message is received the +``DictionaryIterator`` provided by the callback is examined for the presence of +each key. If a key is present, the value associated with it can be read. + + +## Data Types + +The [`Tuple.value`](``Tuple``) union allows multiple data types to be stored in +and read from each received message. These are detailed below: + +| Name | Type | Size in Bytes | Signed? | +|------|------|---------------|---------| +| uint8 | `uint8_t` | 1 | No | +| uint16 | `uint16_t` | 2 | No | +| uint32 | `uint32_t` | 4 | No | +| int8 | `int8_t` | 1 | Yes | +| int16 | `int16_t` | 2 | Yes | +| int32 | `int32_t` | 4 | Yes | +| cstring | `char[]` | Variable length array | N/A | +| data | `uint8_t[]` | Variable length array | N/A | + + +## Buffer Sizes + +The size of each of the outbox and inbox buffers must be set chosen such that +the largest message that the app will ever send or receive will fit. Incoming +messages that exceed the size of the inbox buffer, and outgoing messages that +exceed that size of the outbox buffer will be dropped. + +These sizes are specified when the ``AppMessage`` system is 'opened', allowing +communication to occur: + +```c +// Largest expected inbox and outbox message sizes +const uint32_t inbox_size = 64; +const uint32_t outbox_size = 256; + +// Open AppMessage +app_message_open(inbox_size, outbox_size); +``` + +Each of these buffers is allocated at this moment, and comes out of the app's +memory budget, so the sizes of the inbox and outbox should be conservative. +Calculate the size of the buffer you require by summing the sizes of all the +keys and values in the larges message the app will handle. For example, a +message containing three integer keys and values will work with a 32 byte buffer +size. + + +## Choosing Keys + +For each message sent and received, the contents are accessible using keys-value +pairs in a ``Tuple``. This allows each piece of data in the message to be +uniquely identifiable using its key, and also allows many different data types +to be stored inside a single message. + +Each possible piece of data that may be transmitted should be assigned a unique +key value, used to read the associated value when it is found in a received +message. An example for a weather app is shown below:: + +* Temperature +* WindSpeed +* WindDirection +* RequestData +* LocationName + +These values will be made available in any file that includes `pebble.h` prefixed +with `MESSAGE_KEY_`, such as `MESSAGE_KEY_Temperature` and `MESSAGE_KEY_WindSpeed`. + +Examples of how these key values would be used in the phone-side app are +shown in {% guide_link communication/using-pebblekit-js %}, +{% guide_link communication/using-pebblekit-ios %}, and +{% guide_link communication/using-pebblekit-android %}. + + +## Using Callbacks + +Like many other aspects of the Pebble C API, the ``AppMessage`` system makes +use of developer-defined callbacks to allow an app to gracefully handle all +events that may occur, such as successfully sent or received messages as well as +any errors that may occur. + +These callback types are discussed below. Each is used by first creating a +function that matches the signature of the callback type, and then registering +it with the ``AppMessage`` system to be called when that event type occurs. Good +use of callbacks to drive the app's UI will result in an improved user +experience, especially when errors occur that the user can be guided in fixing. + + +### Inbox Received + +The ``AppMessageInboxReceived`` callback is called when a new message has been +received from the connected phone. This is the moment when the contents can be +read and used to drive what the app does next, using the provided +``DictionaryIterator`` to read the message. An example is shown below under +[*Reading an Incoming Message*](#reading-an-incoming-message): + +```c +static void inbox_received_callback(DictionaryIterator *iter, void *context) { + // A new message has been successfully received + +} +``` + +Register this callback so that it is called at the appropriate time: + +```c +// Register to be notified about inbox received events +app_message_register_inbox_received(inbox_received_callback); +``` + + +### Inbox Dropped + +The ``AppMessageInboxDropped`` callback is called when a message was received, +but it was dropped. A common cause of this is that the message was too big for +the inbox. The reason for failure can be determined using the +``AppMessageResult`` provided by the callback: + +```c +static void inbox_dropped_callback(AppMessageResult reason, void *context) { + // A message was received, but had to be dropped + APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped. Reason: %d", (int)reason); +} +``` + +Register this callback so that it is called at the appropriate time: + +```c +// Register to be notified about inbox dropped events +app_message_register_inbox_dropped(inbox_dropped_callback); +``` + + +### Outbox Sent + +The ``AppMessageOutboxSent`` callback is called when a message sent from Pebble +has been successfully delivered to the connected phone. The provided +``DictionaryIterator`` can be optionally used to inspect the contents of the +message just sent. + +> When sending multiple messages in a short space of time, it is **strongly** +> recommended to make use of this callback to wait until the previous message +> has been sent before sending the next. + +```c +static void outbox_sent_callback(DictionaryIterator *iter, void *context) { + // The message just sent has been successfully delivered + +} +``` + +Register this callback so that it is called at the appropriate time: + +```c +// Register to be notified about outbox sent events +app_message_register_outbox_sent(outbox_sent_callback); +``` + + +### Outbox Failed + +The ``AppMessageOutboxFailed`` callback is called when a message just sent +failed to be successfully delivered to the connected phone. The reason can be +determined by reading the value of the provided ``AppMessageResult``, and the +contents of the failed message inspected with the provided +``DictionaryIterator``. + +Use of this callback is strongly encouraged, since it allows an app to detect a +failed message and either retry its transmission, or inform the user of the +failure so that they can attempt their action again. + +```c +static void outbox_failed_callback(DictionaryIterator *iter, + AppMessageResult reason, void *context) { + // The message just sent failed to be delivered + APP_LOG(APP_LOG_LEVEL_ERROR, "Message send failed. Reason: %d", (int)reason); +} +``` + +Register this callback so that it is called at the appropriate time: + +```c +// Register to be notified about outbox failed events +app_message_register_outbox_failed(outbox_failed_callback); +``` + + +## Constructing an Outgoing Message + +A message is constructed and sent from the C app via ``AppMessage`` using a +``DictionaryIterator`` object and the ``Dictionary`` APIs. Ensure that +``app_message_open()`` has been called before sending or receiving any messages. + +The first step is to begin an outgoing message by preparing a +``DictionaryIterator`` pointer, used to keep track of the state of the +dictionary being constructed: + +```c +// Declare the dictionary's iterator +DictionaryIterator *out_iter; + +// Prepare the outbox buffer for this message +AppMessageResult result = app_message_outbox_begin(&out_iter); +``` + +The ``AppMessageResult`` should be checked to make sure the outbox was +successfully prepared: + +```c +if(result == APP_MSG_OK) { + // Construct the message + +} else { + // The outbox cannot be used right now + APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result); +} +``` + +If the result is ``APP_MSG_OK``, the message construction can continue. Data is +now written to the dictionary according to data type using the ``Dictionary`` +APIs. An example from the hypothetical weather app is shown below: + +```c +if(result == APP_MSG_OK) { + // A dummy value + int value = 0; + + // Add an item to ask for weather data + dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true); +} +``` + +After all desired data has been written to the dictionary, the message may be +sent: + +```c +// Send this message +result = app_message_outbox_send(); + +// Check the result +if(result != APP_MSG_OK) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result); +} +``` + +
+{% markdown %} +**Important** + +Any app that wishes to send data from the watch to the phone via PebbleKit JS +must wait until the `ready` event has occured, indicating that the phone has +loaded the JavaScript for the app and it is ready to receive data. See +[*Advanced Communication*](/guides/communication/advanced-communication#waiting-for-pebblekit-js) +for more information. +{% endmarkdown %} +
+ + +Once the message send operation has been completed, either the +``AppMessageOutboxSent`` or ``AppMessageOutboxFailed`` callbacks will be called +(if they have been registered), depending on either a success or failure +outcome. + + +### Example Outgoing Message Construction + +A complete example of assembling an outgoing message is shown below: + +```c +// Declare the dictionary's iterator +DictionaryIterator *out_iter; + +// Prepare the outbox buffer for this message +AppMessageResult result = app_message_outbox_begin(&out_iter); +if(result == APP_MSG_OK) { + // Add an item to ask for weather data + int value = 0; + dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true); + + // Send this message + result = app_message_outbox_send(); + if(result != APP_MSG_OK) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result); + } +} else { + // The outbox cannot be used right now + APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result); +} +``` + + +## Reading an Incoming Message + +When a message is received from the connected phone the +``AppMessageInboxReceived`` callback is called, and the message's contents can +be read using the provided ``DictionaryIterator``. This should be done by +looking for the presence of each expectd `Tuple` key value, and using the +associated value as required. + +Most apps will deal with integer values or strings to pass signals or some +human-readable information respectively. These common use cases are discussed +below. + + +### Reading an Integer + +**From JS** + +```js +var dict = { + 'Temperature': 29 +}; +``` + +**In C** + +```c +static void inbox_received_callback(DictionaryIterator *iter, void *context) { + // A new message has been successfully received + + // Does this message contain a temperature value? + Tuple *temperature_tuple = dict_find(iter, MESSAGE_KEY_Temperature); + if(temperature_tuple) { + // This value was stored as JS Number, which is stored here as int32_t + int32_t temperature = temperature_tuple->value->int32; + } +} +``` + + +### Reading a String + +A common use of transmitted strings is to display them in a ``TextLayer``. Since +the displayed text is required to be long-lived, a `static` `char` buffer can be +used when the data is received: + +**From JS** + +```js +var dict = { + 'LocationName': 'London, UK' +}; +``` + +**In C** + +```c +static void inbox_received_callback(DictionaryIterator *iter, void *context) { + // Is the location name inside this message? + Tuple *location_tuple = dict_find(iter, MESSAGE_KEY_LocationName); + if(location_tuple) { + // This value was stored as JS String, which is stored here as a char string + char *location_name = location_tuple->value->cstring; + + // Use a static buffer to store the string for display + static char s_buffer[MAX_LENGTH]; + snprintf(s_buffer, sizeof(s_buffer), "Location: %s", location_name); + + // Display in the TextLayer + text_layer_set_text(s_text_layer, s_buffer); + } +} +``` + + +### Reading Binary Data + +Apps that deal in packed binary data can send this data and pack/unpack as +required on either side: + +**From JS** + +```js +var dict = { + 'Data': [1, 2, 4, 8, 16, 32, 64] +}; +``` + +**In C** + +```c +static void inbox_received_callback(DictionaryIterator *iter, void *context) { + // Expected length of the binary data + const int length = 32; + + // Does this message contain the data tuple? + Tuple *data_tuple = dict_find(iter, MESSAGE_KEY_Data); + if(data_tuple) { + // Read the binary data value + uint8_t *data = data_tuple->value->data; + + // Inspect the first byte, for example + uint8_t byte_zero = data[0]; + + // Store into an app-defined buffer + memcpy(s_buffer, data, length); + } +``` diff --git a/devsite/source/_guides/communication/using-pebblekit-android.md b/devsite/source/_guides/communication/using-pebblekit-android.md new file mode 100644 index 00000000..1e3d87ce --- /dev/null +++ b/devsite/source/_guides/communication/using-pebblekit-android.md @@ -0,0 +1,222 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PebbleKit Android +description: How to use PebbleKit to communicate with a watchapp on Android. +guide_group: communication +order: 2 +--- + +[PebbleKit Android](https://github.com/pebble/pebble-android-sdk/) is a Java +library that works with the Pebble SDK and can be embedded in any Android +application. Using the classes and methods in this library, an Android companion +app can find and exchange data with a Pebble watch. + +This section assumes that the reader is familiar with basic Android development +and Android Studio as an integrated development environment. Refer to the +[Android Documentation](http://developer.android.com/sdk/index.html) for more +information on installing the Android SDK. + +Most PebbleKit Android methods require a `Context` parameter. An app can use +`getApplicationContext()`, which is available from any `Activity` +implementation. + + +### Setting Up PebbleKit Android + +Add PebbleKit Android to an Android Studio project in the +`app/build.gradle` file: + +``` +dependencies { + compile 'com.getpebble:pebblekit:{{ site.data.sdk.pebblekit-android.version }}' +} +``` + + +### Sending Messages from Android + +Since Android apps are built separately from their companion Pebble apps, there is +no way for the build system to automatically create matching appmessage keys. +You must therefore manually specify them in `package.json`, like so: + +```js +{ + "ContactName": 0, + "Age": 1 +} +``` + +These numeric values can then be used as appmessage keys in your Android app. + +Messages are constructed with the `PebbleDictionary` class and sent to a C +watchapp or watchface using the `PebbleKit` class. The first step is to create a +`PebbleDictionary` object: + +```java +// Create a new dictionary +PebbleDictionary dict = new PebbleDictionary(); +``` + +Data items are added to the +[`PebbleDictionary`](/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary) +using key-value pairs with the methods made available by the object, such as +`addString()` and `addInt32()`. An example is shown below: + +```java +// The key representing a contact name is being transmitted +final int AppKeyContactName = 0; +final int AppKeyAge = 1; + +// Get data from the app +final String contactName = getContact(); +final int age = getAge(); + +// Add data to the dictionary +dict.addString(AppKeyContactName, contactName); +dict.addInt32(AppKeyAge, age); +``` + +Finally, the dictionary is sent to the C app by calling `sendDataToPebble()` +with a UUID matching that of the C app that will receive the data: + +```java +final UUID appUuid = UUID.fromString("EC7EE5C6-8DDF-4089-AA84-C3396A11CC95"); + +// Send the dictionary +PebbleKit.sendDataToPebble(getApplicationContext(), appUuid, dict); +``` + +Once delivered, this dictionary will be available in the C app via the +``AppMessageInboxReceived`` callback, as detailed in +{% guide_link communication/sending-and-receiving-data#inbox-received %}. + + +### Receiving Messages on Android + +Receiving messages from Pebble in a PebbleKit Android app requires a listener to +be registered in the form of a `PebbleDataReceiver` object, which extends +`BroadcastReceiver`: + +```java +// Create a new receiver to get AppMessages from the C app +PebbleDataReceiver dataReceiver = new PebbleDataReceiver(appUuid) { + + @Override + public void receiveData(Context context, int transaction_id, + PebbleDictionary dict) { + // A new AppMessage was received, tell Pebble + PebbleKit.sendAckToPebble(context, transaction_id); + } + +}; +``` + +
+{% markdown %} +**Important** + +PebbleKit apps **must** manually send an acknowledgement (Ack) to Pebble to +inform it that the message was received successfully. Failure to do this will +cause timeouts. +{% endmarkdown %} +
+ +Once created, this receiver should be registered in `onResume()`, overridden +from `Activity`: + +```java +@Override +public void onResume() { + super.onResume(); + + // Register the receiver + PebbleKit.registerReceivedDataHandler(getApplicationContext(), dataReceiver); +} +``` + +> Note: To avoid getting callbacks after the `Activity` or `Service` has exited, +> apps should attempt to unregister the receiver in `onPause()` with +> `unregisterReceiver()`. + +With a receiver in place, data can be read from the provided +[`PebbleDictionary`](/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary) +using analogous methods such as `getString()` and `getInteger()`. Before reading +the value of a key, the app should first check that it exists using a `!= null` +check. + +The example shown below shows how to read an integer from the message, in the +scenario that the watch is sending an age value to the Android companion app: + +```java +@Override +public void receiveData(Context context, int transaction_id, + PebbleDictionary dict) { + // If the tuple is present... + Long ageValue = dict.getInteger(AppKeyAge); + if(ageValue != null) { + // Read the integer value + int age = ageValue.intValue(); + } +} +``` + + +### Other Capabilities + +In addition to sending and receiving messages, PebbleKit Android also allows +more intricate interactions with Pebble. See the +[PebbleKit Android Documentation](/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit/) +for a complete list of available methods. Some examples are shown below of what +is possible: + +* Checking if the watch is connected: + + ```java + boolean connected = PebbleKit.isWatchConnected(getApplicationContext()); + ``` + +* Registering for connection events with `registerPebbleConnectedReceiver()` and + `registerPebbleDisconnectedReceiver()`, and a suitable `BroadcastReceiver`. + + ```java + PebbleKit.registerPebbleConnectedReceiver(getApplicationContext(), + new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { } + + }); + ``` + +* Registering for Ack/Nack events with `registerReceivedAckHandler()` and + `registerReceivedNackHandler()`. + + ```java + PebbleKit.registerReceivedAckHandler(getApplicationContext(), + new PebbleKit.PebbleAckReceiver(appUuid) { + + @Override + public void receiveAck(Context context, int i) { } + + }); + ``` + +* Launching and killing the watchapp with `startAppOnPebble()` and + `closeAppOnPebble()`. + + ```java + PebbleKit.startAppOnPebble(getApplicationContext(), appUuid); + ``` diff --git a/devsite/source/_guides/communication/using-pebblekit-ios.md b/devsite/source/_guides/communication/using-pebblekit-ios.md new file mode 100644 index 00000000..bed7f5fe --- /dev/null +++ b/devsite/source/_guides/communication/using-pebblekit-ios.md @@ -0,0 +1,289 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PebbleKit iOS +description: How to use PebbleKit to communicate with a watchapp on iOS. +guide_group: communication +order: 3 +--- + +[PebbleKit iOS](https://github.com/pebble/pebble-ios-sdk/) is an Objective-C +framework that works with the Pebble SDK and can be embedded in any iOS +application for **iOS 7.1** and above. Using the classes and methods in this +framework, an iOS app can find and exchange data with a Pebble watch. + +This section assumes that the reader has a basic knowledge of Objective-C, Xcode +as an IDE, and the delegate and block patterns. + +> PebbleKit iOS should be compatible if your app uses Swift. The framework +> itself is written in Objective-C to avoid the requirement of the Swift runtime +> in pure Objective-C apps, and to improve the backwards and forwards +> compatibility. + +### Setting Up PebbleKit iOS + +If the project is using [CocoaPods](http://cocoapods.org/) (which is the +recommended approach), just add `pod 'PebbleKit'` to the `Podfile` and execute +`pod install`. + +After installing PebbleKit iOS in the project, perform these final steps: + +* If the iOS app needs to run in the background, you should update your target’s + “Capabilities” in Xcode. Enable “Background Modes” and select both “Uses + Bluetooth LE accessories” and “Acts as a Bluetooth LE accessory”. This should + add the keys `bluetooth-peripheral` (“App shares data using CoreBluetooth”) + and `bluetooth-central` (“App communicates using CoreBluetooth”) to your + target’s `Info.plist` file. +* If you are using Xcode 8 or greater (and recommended for previous versions), + you must also add the key `NSBluetoothPeripheralUsageDescription` (“Privacy - + Bluetooth Peripheral Usage Description”) to your `Info.plist`. + +> To add PebbleKit iOS manually, or some other alternatives follow the steps in +> the [repository](https://github.com/pebble/pebble-ios-sdk/). The documentation +> might also include more information that might be useful. Read it carefully. + +### Targeting a Companion App + +Before an iOS companion app can start communicating or exchange messages with a +watchapp on Pebble, it needs to give PebbleKit a way to identify the watchapp. +The UUID of your watchapp is used for this purpose. + +Set the app UUID associated with the PBPebbleCentral instance. A simple way to +create a UUID in standard representation to `NSUUID` is shown here: + +```objective-c +// Set UUID of watchapp +NSUUID *myAppUUID = + [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"]; +[PBPebbleCentral defaultCentral].appUUID = myAppUUID; +``` + +If you are trying to communicate with the built-in Sports or Golf apps, their +UUID are available as part of PebbleKit with ``PBSportsUUID`` and +``PBGolfUUID``. You must register those UUID if you intend to communicate with +those apps. + +### Becoming a Delegate + +To communicate with a Pebble watch, the class must implement +`PBPebbleCentralDelegate`: + +```objective-c +@interface ViewController () +``` + +The `PBPebbleCentral` class should not be instantiated directly. Instead, always +use the singleton provided by `[PBPebbleCentral defaultCentral]`. An example is +shown below, with the Golf app UUID: + +```objective-c +central = [PBPebbleCentral defaultCentral]; +central.appUUID = myAppUUID; +[central run]; +``` + +Once this is done, set the class to be the delegate: + +```objective-c +[PBPebbleCentral defaultCentral].delegate = self; +``` + +This delegate will get two callbacks: `pebbleCentral:watchDidConnect:isNew:` and +`pebbleCentral:watchDidDisconnect:` every time a Pebble connects or disconnects. +The app won't get connection callbacks if the Pebble is already connected when +the delegate is set. + +Implement these to receive the associated connection/disconnection events: + +```objective-c +- (void)pebbleCentral:(PBPebbleCentral *)central watchDidConnect:(PBWatch *)watch isNew:(BOOL)isNew { + NSLog(@"Pebble connected: %@", watch.name); + + // Keep a reference to this watch + self.connectedWatch = watch; +} + +- (void)pebbleCentral:(PBPebbleCentral *)central watchDidDisconnect:(PBWatch *)watch { + NSLog(@"Pebble disconnected: %@", watch.name); + + // If this was the recently connected watch, forget it + if ([watch isEqual:self.connectedWatch]) { + self.connectedWatch = nil; + } +} +``` + + +### Initiating Bluetooth Communication + +Once the iOS app is correctly set up to communicate with Pebble, the final step +is to actually begin communication. No communication can take place until the +following is called: + +```objective-c +[[PBPebbleCentral defaultCentral] run]; +``` + +> Once this occurs, the user _may_ be shown a dialog asking for confirmation +> that they want the app to communicate. This means the app should not call +> `run:` until the appropriate moment in the UI. + + +### Sending Messages from iOS + +Since iOS apps are built separately from their companion Pebble apps, there is +no way for the build system to automatically create matching app message keys. +You must therefore manually specify them in `package.json`, like so: + +```js +{ + "Temperature": 0, + "WindSpeed": 1, + "WindDirection": 2, + "RequestData": 3, + "LocationName": 4 +} +``` + +These numeric values can then be used as app message keys in your iOS app. + + +Messages are constructed with the `NSDictionary` class and sent to the C +watchapp or watchface by the `PBPebbleCentralDelegate` when the +`appMessagesPushUpdate:` function is invoked. + +To send a message, prepare an `NSDictionary` object with the data to be sent to +the C watchapp. Data items are added to the +[`NSDictionary`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/) +using key-value pairs of standard data types. An example containing a string and +an integer is shown below: + +```objective-c +NSDictionary *update = @{ @(0):[NSNumber pb_numberWithUint8:42], + @(1):@"a string" }; +``` + +Send this dictionary to the watchapp using `appMessagesPushUpdate:`. The first +argument is the update dictionary to send and the second argument is a callback +block that will be invoked when the data has been acknowledged by the watch (or +if an error occurs). + +```objective-c +[self.connectedWatch appMessagesPushUpdate:update onSent:^(PBWatch *watch, NSDictionary *update, NSError *error) { + if (!error) { + NSLog(@"Successfully sent message."); + } else { + NSLog(@"Error sending message: %@", error); + } +}]; +``` + +Once delivered, this dictionary will be available in the C app via the +``AppMessageInboxReceived`` callback, as detailed in +{% guide_link communication/sending-and-receiving-data#inbox-received %}. + + +### Receiving Messages on iOS + +To receive messages from a watchapp, register a receive handler (a block) +with `appMessagesAddReceiveUpdateHandler:`. This block will be invoked with two +parameters - a pointer to a `PBWatch` object describing the Pebble that sent the +message and an `NSDictionary` with the message received. + +```objective-c +[self.connectedWatch appMessagesAddReceiveUpdateHandler:^BOOL(PBWatch *watch, NSDictionary *update) { + NSLog(@"Received message: %@", update); + + // Send Ack to Pebble + return YES; +}]; +``` + +> Always return `YES` in the handler. This instructs PebbleKit to automatically +> send an ACK to Pebble, to avoid the message timing out. + +Data can be read from the `NSDictionary` by first testing for each key's +presence using a `!= nil` check, and reading the value if it is present: + +```objective-c +NSNumber *key = @1; + +// If the key is present in the received dictionary +if (update[key]) { + // Read the integer value + int value = [update[key] intValue]; +} +``` + + +### Other Capabilities + +In addition to sending and receiving messages, PebbleKit iOS also allows +more intricate interactions with Pebble. See the +[PebbleKit iOS Documentation](/docs/pebblekit-ios/) +for more information. Some examples are shown below of what is possible: + +* Checking if the watch is connected using the `connected` property of a + `PBWatch`. + + ```objective-c + BOOL isConnected = self.watch.connected; + ``` + +* Receiving `watchDidConnect` and `watchDidDisconnect` events through being a + `PBDataloggingServiceDelegate`. + + +### Limitations of PebbleKit on iOS + +The iOS platform imposes some restrictions on what apps can do with accessories. +It also limits the capabilities of apps that are in the background. It is +critical to understand these limitations when developing an app that relies on +PebbleKit iOS. + +On iOS, all communication between a mobile app and Pebble is managed through a +communication session. This communication session is a protocol specific to iOS, +with notable limitations that the reader should know and understand when +developing an iOS companion app for Pebble. + + +#### Bluetooth Low Energy (BLE) Connections + +For Pebble apps that communicate with Pebble in BLE mode, a session can be +created for each app that requires one. This removes the 'one session only' +restriction, but only for these BLE apps. Currently, there are several +BLE only devices, such as Pebble Time Round, and Pebble 2, but all the devices +using a firmware 3.8 or greater can use BLE to communicate with PebbleKit. + +For BLE apps, the 'phone must launch' restriction is removed. The iOS +companion app can be restarted by the watchapp if it stops working if user +force-quits iOS app, or it crashes. Note that the app will not work after +rebooting iOS device, which requires it be launched by the iPhone user once +after boot. + + +#### Communication with firmware older than 3.0 + +PebbleKit iOS 3.1.1 is the last PebbleKit that supports communication with +firmwares older than 3.0. PebbleKit iOS 4.0.0 can only communicate with Pebble +devices with firmware newer than 3.0. + +For newer devices like Pebble Time, Pebble Time Steel, Pebble Time Round, and +Pebble 2 there should be no problem. For previous generation devices like Pebble +and Pebble Steel it means that their users should upgrade their firmware to the +latest firmware available for their devices using the new apps. + +This change allows better compatibility and new features to be developed by 3rd +parties. diff --git a/devsite/source/_guides/communication/using-pebblekit-js.md b/devsite/source/_guides/communication/using-pebblekit-js.md new file mode 100644 index 00000000..57e9eb22 --- /dev/null +++ b/devsite/source/_guides/communication/using-pebblekit-js.md @@ -0,0 +1,500 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PebbleKit JS +description: | + How to use PebbleKit JS to communicate with the connected phone's JS + environment. +guide_group: communication +order: 4 +platform_choice: true +--- + +PebbleKit JS allows a JavaScript component (run in a sandbox inside the official +Pebble mobile app) to be added to any watchapp or watchface in order to extend +the functionality of the app beyond what can be accomplished on the watch +itself. + +Extra features available to an app using PebbleKit JS include: + +* Access to extended storage with [`localStorage`](#using-localstorage). + +* Internet access using [`XMLHttpRequest`](#using-xmlhttprequest). + +* Location data using [`geolocation`](#using-geolocation). + +* The ability to show a configuration page to allow users to customize how the + app behaves. This is discussed in detail in + {% guide_link user-interfaces/app-configuration %}. + + +## Setting Up + +^LC^ PebbleKit JS can be set up by creating the `index.js` file in the project's +`src/pkjs/` directory. Code in this file will be executed when the associated +watchapp is launched, and will stop once that app exits. + +^CP^ PebbleKit JS can be set up by clicking 'Add New' in the Source Files +section of the sidebar. Choose the 'JavaScript file' type and choose a file name +before clicking 'Create'. Code in this file will be executed when the associated +watchapp is launched, and will stop once that app exits. + +The basic JS code required to begin using PebbleKit JS is shown below. An event +listener is created to listen for the `ready` event - fired when the watchapp +has been launched and the JS environment is ready to receive messages. This +callback must return within a short space of time (a few seconds) or else it +will timeout and be killed by the phone. + +```js +Pebble.addEventListener('ready', function() { + // PebbleKit JS is ready! + console.log('PebbleKit JS ready!'); +}); +``` + +
+{% markdown %} +**Important** + +A watchapp or watchface **must** wait for the `ready` event before attempting to +send messages to the connected phone. See +[*Advanced Communication*](/guides/communication/advanced-communication#waiting-for-pebblekit-js) +to learn how to do this. +{% endmarkdown %} +
+ + +## Defining Keys + +^LC^ Before any messages can be sent or received, the keys to be used to store the +data items in the dictionary must be declared. The watchapp side uses +exclusively integer keys, whereas the JavaScript side may use the same integers +or named string keys declared in `package.json`. Any string key not declared +beforehand will not be transmitted to/from Pebble. + +^CP^ Before any messages can be sent or received, the keys to be used to store +the data items in the dictionary must be declared. The watchapp side uses +exclusively integer keys, whereas the JavaScript side may use the same integers +or named string keys declared in the 'PebbleKit JS Message Keys' section of +'Settings'. Any string key not declared beforehand will not be transmitted +to/from Pebble. + +> Note: This requirement is true of PebbleKit JS **only**, and not PebbleKit +> Android or iOS. + +^LC^ Keys are declared in the project's `package.json` file in the `messageKeys` +object, which is inside the `pebble` object. Example keys are shown as equivalents +to the ones used in the hypothetical weather app example in +{% guide_link communication/sending-and-receiving-data#choosing-key-values %}. + +
+{% highlight {} %} +"messageKeys": [ + "Temperature", + "WindSpeed", + "WindDirection", + "RequestData", + "LocationName" +] +{% endhighlight %} +
+ +^CP^ Keys are declared individually in the 'PebbleKit JS Message Keys' section +of the 'Settings' page. Enter the 'Key Name' of each key that will be used by +the app. + +The names chosen here will be injected into your C code prefixed with `MESSAGE_KEY_`, +like `MESSAGE_KEY_Temperature`. As such, they must be legal C identifiers. + +If you want to emulate an array by attaching multiple "keys" to a name, you can +specify the size of the array by adding it in square brackets: for instance, +`"LapTimes[10]`" would create a key called `LapTimes` and leave nine empty keys +after it which can be accessed by arithmetic, e.g. `MESSAGE_KEY_LapTimes + 3`. + + +## Sending Messages from JS + +Messages are sent to the C watchapp or watchface using +`Pebble.sendAppMessage()`, which accepts a standard JavaScript object containing +the keys and values to be transmitted. The keys used **must** be identical to +the ones declared earlier. + +An example is shown below: + +```js +// Assemble data object +var dict = { + 'Temperature': 29, + 'LocationName': 'London, UK' +}; + +// Send the object +Pebble.sendAppMessage(dict, function() { + console.log('Message sent successfully: ' + JSON.stringify(dict)); +}, function(e) { + console.log('Message failed: ' + JSON.stringify(e)); +}); +``` + +It is also possible to read the numeric values of the keys by `require`ing +`message_keys`, which is necessary to use the array feature. For instance: + +```js +// Require the keys' numeric values. +var keys = require('message_keys'); + +// Build a dictionary. +var dict = {} +dict[keys.LapTimes] = 42 +dict[keys.LapTimes+1] = 51 + +// Send the object +Pebble.sendAppMessage(dict, function() { + console.log('Message sent successfully: ' + JSON.stringify(dict)); +}, function(e) { + console.log('Message failed: ' + JSON.stringify(e)); +}); +``` + + +### Type Conversion + +Depending on the type of the item in the object to be sent, the C app will be +able to read the value (from the +[`Tuple.value` union](/guides/communication/sending-and-receiving-data#data-types)) +according to the table below: + +| JS Type | Union member | +|---------|--------------| +| String | cstring | +| Number | int32 | +| Array | data | +| Boolean | int16 | + + +## Receiving Messages in JS + +When a message is received from the C watchapp or watchface, the `appmessage` +event is fired in the PebbleKit JS app. To receive these messages, register the +appropriate event listener: + +```js +// Get AppMessage events +Pebble.addEventListener('appmessage', function(e) { + // Get the dictionary from the message + var dict = e.payload; + + console.log('Got message: ' + JSON.stringify(dict)); +}); +``` + +Data can be read from the dictionary by reading the value if it is present. A +suggested best practice involves first checking for the presence of each key +within the callback using an `if()` statement. + +```js +if(dict['RequestData']) { + // The RequestData key is present, read the value + var value = dict['RequestData']; +} +``` + + +## Using LocalStorage + +In addition to the storage available on the watch itself through the ``Storage`` +API, apps can take advantage of the larger storage on the connected phone +through the use of the HTML 5 [`localStorage`](http://www.w3.org/TR/webstorage/) +API. Data stored here will persist across app launches, and so can be used to +persist latest data, app settings, and other data. + +PebbleKit JS `localStorage` is: + +* Associated with the application UUID and cannot be shared between apps. + +* Persisted when the user uninstalls and then reinstalls an app. + +* Persisted when the user upgrades an app. + +To store a value: + +```js +var color = '#FF0066'; + +// Store some data +localStorage.setItem('backgroundColor', color); +``` + +To read the data back: + +```js +var color = localStorage.getItem('backgroundColor'); +``` + +> Note: Keys used with `localStorage` should be Strings. + + +## Using XMLHttpRequest + +A PebbleKit JS-equipped app can access the internet and communicate with web +services or download data using the standard +[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) +object. + +To communicate with the web, create an `XMLHttpRequest` object and send it, +specifying the HTTP method and URL to be used, as well as a callback for when it +is successfully completed: + +```js +var method = 'GET'; +var url = 'http://example.com'; + +// Create the request +var request = new XMLHttpRequest(); + +// Specify the callback for when the request is completed +request.onload = function() { + // The request was successfully completed! + console.log('Got response: ' + this.responseText); +}; + +// Send the request +request.open(method, url); +request.send(); +``` + +If the response is expected to be in the JSON format, data items can be easily +read after the `responseText` is converted into a JSON object: + +```js +request.onload = function() { + try { + // Transform in to JSON + var json = JSON.parse(this.responseText); + + // Read data + var temperature = json.main.temp; + } catch(err) { + console.log('Error parsing JSON response!'); + } +}; +``` + + +## Using Geolocation + +PebbleKit JS provides access to the location services provided by the phone +through the +[`navigator.geolocation`](http://dev.w3.org/geo/api/spec-source.html) object. + +^CP^ Declare that the app will be using the `geolocation` API by checking the +'Uses Location' checkbox in the 'Settings' screen. + +^LC^ Declare that the app will be using the `geolocation` API by adding the +string `location` in the `capabilities` array in `package.json`: + +
+{% highlight {} %} +"capabilities": [ "location" ] +{% endhighlight %} +
+ +Below is an example showing how to get a single position value from the +`geolocation` API using the +[`getCurrentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) +method: + +```js +function success(pos) { + console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude); +} + +function error(err) { + console.log('location error (' + err.code + '): ' + err.message); +} + +/* ... */ + +// Choose options about the data returned +var options = { + enableHighAccuracy: true, + maximumAge: 10000, + timeout: 10000 +}; + +// Request current position +navigator.geolocation.getCurrentPosition(success, error, options); +``` + +Location permission is given by the user to the Pebble application for all +Pebble apps. The app should gracefully handle the `PERMISSION DENIED` error and +fallback to a default value or manual configuration when the user has denied +location access to Pebble apps. + +```js +function error(err) { + if(err.code == err.PERMISSION_DENIED) { + console.log('Location access was denied by the user.'); + } else { + console.log('location error (' + err.code + '): ' + err.message); + } +} + +``` + +The `geolocation` API also provides a mechanism to receive callbacks when the +user's position changes to avoid the need to manually poll at regular intervals. +This is achieved by using +[`watchPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition) +in a manner similar to the example below: + +```js +// An ID to store to later clear the watch +var watchId; + +function success(pos) { + console.log('Location changed!'); + console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude); +} + +function error(err) { + console.log('location error (' + err.code + '): ' + err.message); +} + +/* ... */ + +var options = { + enableHighAccuracy: true, + maximumAge: 0, + timeout: 5000 +}; + +// Get location updates +watchId = navigator.geolocation.watchPosition(success, error, options); +``` + +To cancel the update callbacks, use the `watchId` variable received when the +watch was registered with the +[`clearWatch()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch) +method: + +```js +// Clear the watch and stop receiving updates +navigator.geolocation.clearWatch(watchId); +``` + + +## Account Token + +PebbleKit JS provides a unique account token that is associated with the Pebble +account of the current user, accessible using `Pebble.getAccountToken()`: + +```js +// Get the account token +console.log('Pebble Account Token: ' + Pebble.getAccountToken()); +``` + +The token is a string with the following properties: + +* From the developer's perspective, the account token of a user is identical + across platforms and across all the developer's watchapps. + +* If the user is not logged in, the token will be an empty string (''). + + +## Watch Token + +PebbleKit JS also provides a unique token that can be used to identify a Pebble +device. It works in a similar way to `Pebble.getAccountToken()`: + +```js +// Get the watch token +console.log('Pebble Watch Token: ' + Pebble.getWatchToken()); +``` + +The token is a string that is unique to the app and cannot be used to track +Pebble devices across applications. + +
+{% markdown %} +**Important** + +The watch token is dependent on the watch's serial number, and therefore +**should not** be used to store sensitive user information in case the watch +changes ownership. If the app wishes to track a specific user _and_ watch, use a +combination of the watch and account token. +{% endmarkdown %} +
+ + +## Showing a Notification + +A PebbleKit JS app can send a notification to the watch. This uses the standard +system notification layout with customizable `title` and `body` fields: + +```js +var title = 'Update Available'; +var body = 'Version 1.5 of this app is now available from the appstore!'; + +// Show the notification +Pebble.showSimpleNotificationOnPebble(title, body); +``` + +> Note: PebbleKit Android/iOS applications cannot directly invoke a +> notification, and should instead leverage the respective platform notification +> APIs. These will be passed on to Pebble unless the user has turned them off in +> the mobile app. + + +## Getting Watch Information + +Use `Pebble.getActiveWatchInfo()` to return an object of data about the +connected Pebble. + +
+{% markdown %} +This API is currently only available for SDK 3.0 and above. Do not assume that +this function exists, so test that it is available before attempting to use it +using the code shown below. +{% endmarkdown %} +
+ +```js +var watch = Pebble.getActiveWatchInfo ? Pebble.getActiveWatchInfo() : null; + +if(watch) { + // Information is available! + +} else { + // Not available, handle gracefully + +} +``` + +> Note: If there is no active watch available, `null` will be returned. + +The table below details the fields of the returned object and the information +available. + +| Field | Type | Description | Values | +|-------|------|-------------|--------| +| `platform` | String | Hardware platform name. | `aplite`, `basalt`, `chalk`. | +| `model` | String | Watch model name including color. | `pebble_black`, `pebble_grey`, `pebble_white`, `pebble_red`, `pebble_orange`, `pebble_blue`, `pebble_green`, `pebble_pink`, `pebble_steel_silver`, `pebble_steel_black`, `pebble_time_red`, `pebble_time_white`, `pebble_time_black`, `pebble_time_steel_black`, `pebble_time_steel_silver`, `pebble_time_steel_gold`, `pebble_time_round_silver_14mm`, `pebble_time_round_black_14mm`, `pebble_time_round_rose_gold_14mm`, `pebble_time_round_silver_20mm`, `pebble_time_round_black_20mm`, `qemu_platform_aplite`, `qemu_platform_basalt`, `qemu_platform_chalk`. | +| `language` | String | Language currently selected on the watch. | E.g.: `en_GB`. See the {% guide_link tools-and-resources/internationalization#locales-supported-by-pebble %} for more information. | +| `firmware` | Object | The firmware version running on the watch. | See below for sub-fields. | +| `firmware.major` | Number | Major firmware version. | E.g.: `2` | +| `firmware.minor` | Number | Minor firmware version. | E.g.: `8` | +| `firmware.patch` | Number | Patch firmware version. | E.g.: `1` | +| `firmware.suffix` | String | Any additional firmware versioning. | E.g.: `beta3` | diff --git a/devsite/source/_guides/communication/using-the-sports-api.md b/devsite/source/_guides/communication/using-the-sports-api.md new file mode 100644 index 00000000..92c49f58 --- /dev/null +++ b/devsite/source/_guides/communication/using-the-sports-api.md @@ -0,0 +1,314 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Sports API +description: | + How to use the PebbleKit Sports API to integrate your mobile sports + app with Pebble. +guide_group: communication +order: 6 +related_examples: + - title: PebbleKit Sports API Demos + - url: https://github.com/pebble-examples/pebblekit-sports-api-demo +--- + +Every Pebble watch has two built-in system watchapps called the Sports app, and +the Golf app. These apps are hidden from the launcher until launched via +PebbleKit Android or PebbleKit iOS. + +Both are designed to be generic apps that display sports-related data in common +formats. The goal is to allow fitness and golf mobile apps to integrate with +Pebble to show the wearer data about their activity without needing to create +and maintain an additional app for Pebble. An example of a popular app that uses +this approach is the +[Runkeeper](http://apps.getpebble.com/en_US/application/52e05bd5d8561de307000039) +app. + +The Sports and Golf apps are launched, closed, and controlled by PebbleKit in an +Android or iOS app, shown by example in each section below. In both cases, the +data fields that are available to be populated are different, but data is pushed +in the same way. + + +## Available Data Fields + +### Sports + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/sports.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The sports app displays activity duration and distance which can apply to a wide +range of sports, such as cycling or running. A configurable third field is also +available that displays pace or speed, depending on the app's preference. The +Sports API also allows the app to be configured to display the labels of each +field in metric (the default) or imperial units. + +The action bar is used to prompt the user to use the Select button to pause and +resume their activity session. The companion app is responsible for listening +for these events and implementing the pause/resume operation as appropriate. + + +### Golf + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/golf.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The Golf app is specialized to displaying data relevant to golf games, including +par and hole numbers, as well as front, mid, and rear yardage. + +Similar to the Sports app, the action bar is used to allow appropriate feedback +to the companion app. In this case the actions are an 'up', 'ball' and 'down' +events which the companion should handle as appropriate. + + +## With PebbleKit Android + +Once an Android app has set up +{% guide_link communication/using-pebblekit-android %}, the Sports and Golf apps +can be launched and customized as appropriate. + + +### Launching Sports and Golf + +To launch one of the Sports API apps, simply call `startAppOnPebble()` and +supply the UUID from the `Constants` class: + +```java +// Launch the Sports app +PebbleKit.startAppOnPebble(getApplicationContext(), Constants.SPORTS_UUID); +``` + + +### Customizing Sports + +To choose which unit type is used, construct and send a `PebbleDictionary` +containing the desired value from the `Constants` class. Either +`SPORTS_UNITS_IMPERIAL` or `SPORTS_UNITS_METRIC` can be used: + +```java +PebbleDictionary dict = new PebbleDictionary(); + +// Display imperial units +dict.addUint8(Constants.SPORTS_UNITS_KEY, Constants.SPORTS_UNITS_IMPERIAL); + +PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict); +``` + +To select between 'pace' or 'speed' as the label for the third field, construct +and send a `PebbleDictionary` similar to the example above. This can be done in +the same message as unit selection: + +```java +PebbleDictionary dict = new PebbleDictionary(); + +// Display speed instead of pace +dict.addUint8(Constants.SPORTS_LABEL_KEY, Constants.SPORTS_DATA_SPEED); + +PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict); +``` + +> Note: The Golf app does not feature any customizable fields. + + +### Displaying Data + +Data about the current activity can be sent to either of the Sports API apps +using a `PebbleDictionary`. For example, to show a value for duration and +distance in the Sports app: + +```java +PebbleDictionary dict = new PebbleDictionary(); + +// Show a value for duration and distance +dict.addString(Constants.SPORTS_TIME_KEY, "12:52"); +dict.addString(Constants.SPORTS_DISTANCE_KEY, "23.8"); + +PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict); +``` + +Read the [`Constants`](/docs/pebblekit-android/com/getpebble/android/kit/Constants) +documentation to learn about all the available parameters that can be used for +customization. + + +### Handling Button Events + +When a button event is generated from one of the Sports API apps, a message is +sent to the Android companion app, which can be processed using a +`PebbleDataReceiver`. For example, to listen for a change in the state of the +Sports app, search for `Constants.SPORTS_STATE_KEY` in the received +`PebbleDictionary`. The user is notified in the example below through the use of +an Android +[`Toast`](http://developer.android.com/guide/topics/ui/notifiers/toasts.html): + +```java +// Create a receiver for when the Sports app state changes +PebbleDataReceiver reciever = new PebbleKit.PebbleDataReceiver( + Constants.SPORTS_UUID) { + + @Override + public void receiveData(Context context, int id, PebbleDictionary data) { + // Always ACKnowledge the last message to prevent timeouts + PebbleKit.sendAckToPebble(getApplicationContext(), id); + + // Get action and display as Toast + Long value = data.getUnsignedIntegerAsLong(Constants.SPORTS_STATE_KEY); + if(value != null) { + int state = value.intValue(); + String text = (state == Constants.SPORTS_STATE_PAUSED) + ? "Resumed!" : "Paused!"; + Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show(); + } + } + +}; + +// Register the receiver +PebbleKit.registerReceivedDataHandler(getApplicationContext(), receiver); +``` + + +## With PebbleKit iOS + +Once an iOS app has set up {% guide_link communication/using-pebblekit-ios %}, +the Sports and Golf apps can be launched and customized as appropriate. The +companion app should set itself as a delegate of `PBPebbleCentralDelegate`, and +assign a `PBWatch` property once `watchDidConnect:` has fired. This `PBWatch` +object will then be used to manipulate the Sports API apps. + +Read *Becoming a Delegate* in the +{% guide_link communication/using-pebblekit-ios %} guide to see how this is +done. + + +### Launching Sports and Golf + +To launch one of the Sports API apps, simply call `sportsAppLaunch:` or +`golfAppLaunch:` as appropriate: + +```objective-c +[self.watch sportsAppLaunch:^(PBWatch * _Nonnull watch, + NSError * _Nullable error) { + NSLog(@"Sports app was launched"); +}]; +``` + + +### Customizing Sports + +To choose which unit type is used, call `sportsAppSetMetric:` with the desired +`isMetric` `BOOL`: + +```objective-c +BOOL isMetric = YES; + +[self.watch sportsAppSetMetric:isMetric onSent:^(PBWatch * _Nonnull watch, + NSError * _Nonnull error) { + if (!error) { + NSLog(@"Successfully sent message."); + } else { + NSLog(@"Error sending message: %@", error); + } +}]; +``` + +To select between 'pace' or 'speed' as the label for the third field, call +`sportsAppSetLabel:` with the desired `isPace` `BOOL`: + +```objective-c +BOOL isPace = YES; + +[self.watch sportsAppSetLabel:isPace onSent:^(PBWatch * _Nonnull watch, + NSError * _Nullable error) { + if (!error) { + NSLog(@"Successfully sent message."); + } else { + NSLog(@"Error sending message: %@", error); + } +}]; +``` + +> Note: The Golf app does not feature any customizable fields. + + +### Displaying Data + +Data about the current activity can be sent to either the Sports or Golf app +using `sportsAppUpdate:` or `golfAppUpdate:`. For example, to show a value for +duration and distance in the Sports app: + +```objective-c +// Construct a dictionary of data +NSDictionary *update = @{ PBSportsTimeKey: @"12:34", + PBSportsDistanceKey: @"6.23" }; + +// Send the data to the Sports app +[self.watch sportsAppUpdate:update onSent:^(PBWatch * _Nonnull watch, + NSError * _Nullable error) { + if (!error) { + NSLog(@"Successfully sent message."); + } else { + NSLog(@"Error sending message: %@", error); + } +}]; +``` + +Read the [`PBWatch`](/docs/pebblekit-ios/Classes/PBWatch/) documentation to learn about all +the available methods and values for customization. + + +### Handling Button Events + +When a button event is generated from one of the Sports API apps, a message is +sent to the Android companion app, which can be processed using +`sportsAppAddReceiveUpdateHandler` and supplying a block to be run when a +message is received. For example, to listen for change in state of the Sports +app, check the value of the provided `SportsAppActivityState`: + +```objective-c +// Register to get state updates from the Sports app +[self.watch sportsAppAddReceiveUpdateHandler:^BOOL(PBWatch *watch, + SportsAppActivityState state) { + // Display the new state of the watchapp + switch (state) { + case SportsAppActivityStateRunning: + NSLog(@"Watchapp now running."); + break; + case SportsAppActivityStatePaused: + NSLog(@"Watchapp now paused."); + break; + default: break; + } + + // Finally + return YES; +}]; +``` \ No newline at end of file diff --git a/devsite/source/_guides/debugging/common-runtime-errors.md b/devsite/source/_guides/debugging/common-runtime-errors.md new file mode 100644 index 00000000..a964dbd5 --- /dev/null +++ b/devsite/source/_guides/debugging/common-runtime-errors.md @@ -0,0 +1,219 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Common Runtime Errors +description: | + Examples of commonly encountered runtime problems that cannot be detected at + compile time and can usually be fixed by logical thought and experimentation. +guide_group: debugging +order: 3 +--- + +Whether just beginning to create apps for the Pebble platform or are creating a +more complex app, the output from app logs can be very useful in tracking down +problems with an app's code. Some examples of common problems are explored here, +including some examples to help gain familiarity with compiler output. + +In contrast with syntactical errors in written code (See +{% guide_link debugging/common-syntax-errors %}), +there can also be problems that only occur when the app is actually run on a +Pebble. The reason for this is that perfectly valid C code can sometimes cause +improper behavior that is incompatible with the hardware. + +These problems can manifest themselves as an app crashing and very little other +information available as to what the cause was, which means that they can take +an abnormally long time to diagnose and fix. + +One option to help track down the offending lines is to begin at the start of +app initialization and use a call to ``APP_LOG()`` to establish where execution +stops. If the message in the log call appears, then execution has at least +reached that point. Move the call further through logical execution until it +ceases to appear, as it will then be after the point where the app crashes. + + +## Null Pointers + +The Pebble SDK uses a dynamic memory allocation model, meaning that all the SDK +objects and structures available for use by developers are allocated as and when +needed. This model has the advantage that only the immediately needed data and +objects can be kept in memory and unloaded when they are not needed, increasing +the scale and capability of apps that can be created. + +In this paradigm a structure is first declared as a pointer (which may be given +an initial value of `NULL`) before being fully allocated a structure later in +the app's initialization. Therefore one of the most common problems that can +arise is that of the developer attempting to use an unallocated structure or +data item. + +For example, the following code segment will cause a crash: + +```c +Window *main_window; + +static void init() { + // Attempting to push an uninitialized Window! + window_stack_push(main_window, true); +} +``` + +The compiler will not report this, but when run the app will crash before the +``Window`` can be displayed, with an error message sent to the console output +along the following lines: + +```nc|text +[INFO ] E ault_handling.c:77 App fault! {f23aecb8-bdb5-4d6b-b270-602a1940575e} PC: 0x8016716 LR: 0x8016713 +[WARNING ] Program Counter (PC): 0x8016716 ??? +[WARNING ] Link Register (LR): 0x8016713 ??? +``` + +When possible, the pebble tool will tell the developer the PC (Program Counter, +or which statement is currently being executed) and LR (Link Register, address +to return to when the current function scope ends) addresses and line numbers at +the time of the crash, which may help indicate the source of the problem. + +This problem can be fixed by ensuring that any data structures declared as +pointers are properly allocated using the appropriate `_create()` SDK functions +before they are used as arguments: + +```c +Window *main_window; + +static void init(void) { + main_window = window_create(); + window_stack_push(main_window, true); +} +``` + +In situations where available heap space is limited, `_create()` functions may +return `NULL`, and the object will not be allocated. Apps can detect this +situation as follows: + +```c +Window *main_window; + +static void init(void) { + main_window = window_create(); + + if(main_window != NULL) { + // Allocation was successful! + window_stack_push(main_window, true); + } else { + // The Window could not be allocated! + // Tell the user that the operation could not be completed + text_layer_set_text(s_output_layer, + "Unable to use this feature at the moment."); + } +} +``` + +This `NULL` pointer error can also occur to any dynamically allocated structure +or variable of the developer's own creation outside the SDK. For example, a +typical dynamically allocated array will cause a crash if it is used before it +is allocated: + +```c +char *array; + +// Array is still NULL! +array[0] = 'a'; +``` + +This problem can be fixed in a similar manner as before by making sure the array +is properly allocated before it is used: + +```c +char *array = (char*)malloc(8 * sizeof(char)); +array[0] = 'a'; +``` + +As mentioned above for ``window_create()``, be sure also check the +[return value](http://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html) +of `malloc()` to determine whether the memory allocation requested was completed +successfully: + +```c +array = (char*)malloc(8 * sizeof(char)); + +// Check the malloc() was successful +if(array != NULL) { + array[0] = 'a'; +} else { + // Gracefully handle the failed situation + +} +``` + + +## Outside Array Bounds + +Another problem that can look OK to the compiler, but cause an error at runtime +is improper use of arrays, such as attempting to access an array index outside +the array's bounds. This can occur when a loop is set up to iterate through an +array, but the size of the array is reduced or the loop conditions change. + +For example, the array iteration below will not cause a crash, but includes the +use of 'magic numbers' that can make a program brittle and prone to errors when +these numbers change: + +```c +int *array; + +static void init(void) { + array = (int*)malloc(8 * sizeof(int)); + + for(int i = 0; i < 8; i++) { + array[i] = i * i; + } +} +``` + +If the size of the allocated array is reduced, the app will crash when the +iterative loop goes outside the array bounds: + +```c +int *array; + +static void init(void) { + array = (int*)malloc(4 * sizeof(int)); + + for(int i = 0; i < 8; i++) { + array[i] = i * i; + + // Crash when i == 4! + } +} +``` + +Since the number of loop iterations is linked to the size of the array, this +problem can be avoided by defining the size of the array in advance in one +place, and then using that value everywhere the size of the array is needed: + +```c +#define ARRAY_SIZE 4 + +int *array; + +static void init(void) { + array = (int*)malloc(ARRAY_SIZE * sizeof(int)); + + for(int i = 0; i < ARRAY_SIZE; i++) { + array[i] = i * i; + } +} +``` + +An alternative solution to the above is to use either the `ARRAY_LENGTH()` macro +or the `sizeof()` function to programmatically determine the size of the array +to be looped over. diff --git a/devsite/source/_guides/debugging/common-syntax-errors.md b/devsite/source/_guides/debugging/common-syntax-errors.md new file mode 100644 index 00000000..54632e3f --- /dev/null +++ b/devsite/source/_guides/debugging/common-syntax-errors.md @@ -0,0 +1,189 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Common Syntax Errors +description: | + Details of common problems encountered when writing C apps for Pebble, and how + to resolve them. +guide_group: debugging +order: 2 +--- + +If a developer is relatively new to writing Pebble apps (or new to the C +language in general), there may be times when problems with an app's code will +cause compilation errors. Some types of errors with the code itself can be +detected by the compiler and this helps reduce the number that cause problems +when the code is run on Pebble. + +These are problems with how app code is written, as opposed to runtime errors +(discussed in {% guide_link debugging/common-runtime-errors %}), which may +include breaking the rules of the C language or bad practices that the compiler +is able to detect and show as an error. The following are some examples. + + +### Undeclared Variables + +This error means that a variable that has been referenced is not available in +the current scope. + +```nc|text +../src/main.c: In function 'toggle_logging': +../src/main.c:33:6: error: 'is_now_logging' undeclared (first use in this function) + if(is_now_logging == true) { + ^ +``` + +In the above example, the symbol `is_now_logging` has been used in the +`toggle_logging` function, but it was not first declared there. This could be +because the declaring line has been deleted, or it was expected to be available +globally, but isn't. + +To fix this, consider where else the symbol is required. If it is needed in +other functions, move the declaration to a global scope (outside any function). +If it is needed only for this function, declare it before the offending line +(here line `33`). + + +### Undeclared Functions + +Another variant of the above problem can occur when declaring new functions in a +code file. Due to the nature of C compilation, any function a +developer attempts to call must have been previously encountered by the compiler +in order to be visible. This can be done through +[forward declaration](http://en.wikipedia.org/wiki/Forward_declaration). + +For example, the code segment below will not compile: + +```c +static void window_load(Window *window) { + my_function(); +} + +void my_function() { + // Some code here + +} +``` + +The compiler will report this with an 'implicit declaration' error, as the app +has implied the function's existence by calling it, even though the compiler has +not seen it previously: + +```nc|text +../src/function-visibility.c: In function 'window_load': +../src/function-visibility.c:6:3: error: implicit declaration of function 'my_function' [-Werror=implicit-function-declaration] + my_function(); + ^ +``` + +This is because the *declaration* of `my_function()` occurs after it is called +in `window_load()`. There are two options to fix this. + +* Move the function declaration above any calls to it, so it has been + encountered by the compiler: + +```c +void my_function() { + // Some code here + +} + +static void window_load(Window *window) { + my_function(); +} +``` + +* Declare the function by prototype before it is called, and provide the + implementation later: + +```c +void my_function(); + +static void window_load(Window *window) { + my_function(); +} + +void my_function() { + // Some code here + +} +``` + + +### Too Few Arguments + +When creating functions with argument lists, sometimes the requirements of the +function change and the developer forgets to update the places where it is +called. + +```nc|text +../src/main.c: In function 'select_click_handler': +../src/main.c:57:3: error: too few arguments to function 'toggle_logging' + toggle_logging(); + ^ +../src/main.c:32:13: note: declared here + static void toggle_logging(bool will_log) { + ^ +``` + +The example above reports that the app tried to call the `toggle_logging()` +function in `select_click_handler()` on line 57, but did not supply enough +arguments. The argument list expected in the function definition is shown in the +second part of the output message, which here exists on line 32 and expects an +extra value of type `bool`. + +To fix this, establish which version of the function is required, and update +either the calls or the declaration to match. + + +### Incorrect Callback Implementations + +In the Pebble SDK there are many instances where the developer must implement a +function signature required for callbacks, such as for a ``WindowHandlers`` +object. This means that when implementing the handler the developer-defined +callback must match the return type and argument list specified in the API +documentation. + +For example, the ``WindowHandler`` callback (used for the `load` and `unload` +events in a ``Window``'s lifecycle) has the following signature: + +```c +typedef void(* WindowHandler)(struct Window *window) +``` + +This specifies a return type of `void` and a single argument: a pointer of type +``Window``. Therefore the implemented callback should look like this: + +```c +void window_load(Window *window) { + +} +``` + +If the developer does not specify the correct return type and argument list in +their callback implementation, the compiler will let them know with an error +like the following, stating that the type of function passed by the developer +does not match that which is expected: + +```nc|text +../src/main.c: In function 'init': +../src/main.c:82:5: error: initialization from incompatible pointer type [-Werror] + .load = main_window_load, + ^ +../src/main.c:82:5: error: (near initialization for '(anonymous).load') [-Werror] +``` + +To fix this, double check that the implementation provided has the same return +type and argument list as specified in the API documentation. diff --git a/devsite/source/_guides/debugging/debugging-with-app-logs.md b/devsite/source/_guides/debugging/debugging-with-app-logs.md new file mode 100644 index 00000000..7682edc1 --- /dev/null +++ b/devsite/source/_guides/debugging/debugging-with-app-logs.md @@ -0,0 +1,144 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Debugging with App Logs +description: | + How to use the app logs to debug problems with an app, as well as tips on + interpreting common run time errors. +guide_group: debugging +order: 1 +related_docs: + - Logging +platform_choice: true +--- + + +When apps in development do not behave as expected the developer can use app +logs to find out what is going wrong. The C SDK and PebbleKit JS can both output +messages and values to the console to allow developers to get realtime +information on the state of their app. + +This guide describes how to log information from both the C and JS parts of a +watchapp or watchface and also how to read that information for debugging +purposes. + + +## Logging in C + +The C SDK includes the ``APP_LOG()`` macro function which allows an app to +log a string containing information to the console: + +```c +static int s_buffer[5]; +for(int i = 0; i < 10; i++) { + // Store loop value in array + s_buffer[i] = i; + + APP_LOG(APP_LOG_LEVEL_DEBUG, "Loop index now %d", i); +} +``` + +This will result in the following output before crashing: + +```nc|text +[INFO ] D main.c:20 Loop index now 0 +[INFO ] D main.c:20 Loop index now 1 +[INFO ] D main.c:20 Loop index now 2 +[INFO ] D main.c:20 Loop index now 3 +[INFO ] D main.c:20 Loop index now 4 +``` + +In this way it will be possible to tell the state of the loop index value if the +app encounters a problem and crashes (such as going out of array bounds in the +above example). + + +## Logging in JS + +Information can be logged in PebbleKit JS and Pebble.js using the standard +JavaScript console, which will then be passed on to the log output view. An +example of this is to use the optional callbacks when using +`Pebble.sendAppMessage()` to know if a message was sent successfully to the +watch: + +```js +console.log('Sending data to Pebble...'); + +Pebble.sendAppMessage({'KEY': value}, function(e) { + console.log('Send successful!'); + }, function(e) { + console.log('Send FAILED!'); + } +); +``` + + +## Viewing Log Data + +When viewing app logs, both the C and JS files' output are shown in the same +view. + +^CP^ To view app logs in CloudPebble, open a project and navigate to the +'Compilation' screen. Click 'View App Logs' and run an app that includes log +output calls to see the output appear in this view. + +^LC^ The `pebble` {% guide_link tools-and-resources/pebble-tool %} will +output any logs from C and JS files after executing the `pebble logs` command +and supplying the phone's IP address: + +
+{% markdown %} +```text +pebble logs --phone=192.168.1.25 +``` + +> Note: You can also use `pebble install --logs' to combine both of these +> operations into one command. +{% endmarkdown %} +
+ + +## Memory Usage Information + +In addition to the log output from developer apps, statistics about memory +usage are also included in the C app logs when an app exits: + +```nc|text +[INFO] process_manager.c:289: Heap Usage for App compass-ex: Total Size <22980B> Used <164B> Still allocated <0B> +``` + +This piece of information reports the total heap size of the app, the amount of +memory allocated as a result of execution, and the amount of memory still +allocated when it exited. This last number can alert any forgotten deallocations +(for example, forgetting ``window_destroy()`` after ``window_create()``). A +small number such as `28B` is acceptable, provided it remains the same after +subsequent executions. If it increases after each app exit it may indicate a +memory leak. + +For more information on system memory usage, checkout the +[Size presentation from the 2014 Developer Retreat](https://www.youtube.com/watch?v=8tOhdUXcSkw). + + +## Avoid Excessive Logging + +As noted in the [API documentation](``Logging``), logging over +Bluetooth can be a power-hungry operation if an end user has the Developer +Connection enabled and is currently viewing app logs. + +In addition, frequent (multiple times per second) logging can interfere with +frequent use of ``AppMessage``, as the two mechanisms share the same channel for +communication. If an app is logging sent/received AppMessage events or values +while doing this sending, it could experience slow or dropped messages. Be sure +to disable this logging when frequently sending messages. diff --git a/devsite/source/_guides/debugging/debugging-with-gdb.md b/devsite/source/_guides/debugging/debugging-with-gdb.md new file mode 100644 index 00000000..3a0ef3c5 --- /dev/null +++ b/devsite/source/_guides/debugging/debugging-with-gdb.md @@ -0,0 +1,311 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Debugging with GDB +description: | + How to use GDB to debug a Pebble app in the emulator. +guide_group: debugging +order: 0 +--- + +As of SDK 3.10 (and [Pebble Tool](/guides/tools-and-resources/pebble-tool) 4.2), +developers can use the powerful [GDB](https://www.gnu.org/software/gdb/) +debugging tool to find and fix errors in Pebble apps while they are running in +an emulator. GDB allows the user to observe the state of the app at any point in +time, including the value of global and local variables, as well as current +function parameters and a backtrace. Strategically placing breakpoints and +observing these values can quickly reveal the source of a bug. + +{% alert notice %} +GDB cannot be used to debug an app running on a real watch. +{% endalert %} + + +## Starting GDB + +To begin using GDB, start an emulator and install an app: + +```text +$ pebble install --emulator basalt +``` + +Once the app is installed, begin using GDB: + +```text +$ pebble gdb --emulator basalt +``` + +Once the `(gdb)` prompt appears, the app is paused by GDB for observation. To +resume execution, use the `continue` (or `c`) command. Similarly, the app can be +paused for debugging by pressing `control + c`. + +```text +(gdb) c +Continuing. +``` + +A short list of useful commands (many more are available) can be also be +obtained from the `pebble` tool. Read the +[*Emulator Interaction*](/guides/tools-and-resources/pebble-tool/#gdb) +section of the {% guide_link tools-and-resources/pebble-tool %} guide for more +details on this list. + +```text +$ pebble gdb --help +``` + + +## Observing App State + +To see the value of variables and parameters at any point, set a breakpoint +by using the `break` (or `b`) command and specifying either a function name, or +file name with a line number. For example, the snippet below shows a typical +``TickHandler`` implementation with line numbers in comments: + +```c +/* 58 */ static void tick_handler(struct tm *tick_time, TimeUnits changed) { +/* 59 */ int hours = tick_time->tm_hour; +/* 60 */ int mins = tick_time->tm_min; +/* 61 */ +/* 62 */ if(hours < 10) { +/* 63 */ /* other code */ +/* 64 */ } +/* 65 */ } +``` + +To observe the values of `hours` and `mins`, a breakpoint is set in this file at +line 61: + +```text +(gdb) b main.c:61 +Breakpoint 2 at 0x200204d6: file ../src/main.c, line 61. +``` + +> Use `info break` to see a list of all breakpoints currently registered. Each +> can be deleted with `delete n`, where `n` is the breakpoint number. + +With this breakpoint set, use the `c` command to let the app continue until it +encounters the breakpoint: + +```text +$ c +Continuing. +``` + +When execution arrives at the breakpoint, the next line will be displayed along +with the state of the function's parameters: + +```text +Breakpoint 2, tick_handler (tick_time=0x20018770, units_changed=(SECOND_UNIT | MINUTE_UNIT)) + at ../src/main.c:62 +62 if(hours < 10) { +``` + +The value of `hours` and `mins` can be found using the `info locals` command: + +```text +(gdb) info locals +hours = 13 +mins = 23 +``` + +GDB can be further used here to view the state of variables using the `p` +command, such as other parts of the `tm` object beyond those being used to +assign values to `hours` and `mins`. For example, the day of the month: + +```text +(gdb) p tick_time->tm_mday +$2 = 14 +``` + +A backtrace can be generated that describes the series of function calls that +got the app to the breakpoint using the `bt` command: + +```text +(gdb) bt +#0 segment_logic (this=0x200218a0) at ../src/drawable/segment.c:18 +#1 0x2002033c in digit_logic (this=0x20021858) at ../src/drawable/digit.c:141 +#2 0x200204c4 in pge_logic () at ../src/main.c:29 +#3 0x2002101a in draw_frame_update_proc (layer=, ctx=) + at ../src/pge/pge.c:190 +#4 0x0802627c in ?? () +#5 0x0805ecaa in ?? () +#6 0x0801e1a6 in ?? () +#7 0x0801e24c in app_event_loop () +#8 0x2002108a in main () at ../src/pge/pge.c:34 +#9 0x080079de in ?? () +#10 0x00000000 in ?? () +``` + +> Lines that include '??' denote a function call in the firmware. Building the +> app with `pebble build --debug` will disable some optimizations and can +> produce more readable output from GDB. However, this can increase code size +> which may break apps that are pushing the heap space limit. + + +## Fixing a Crash + +When an app is paused for debugging, the developer can manually advance each +statement and precisely follow the path taken through the code and observe how +the state of each variable changes over time. This is very useful for tracking +down bugs caused by unusual input to functions that do not adequately check +them. For example, a `NULL` pointer. + +The app code below demonstrates a common cause of an app crash, caused by a +misunderstanding of how the ``Window`` stack works. The ``TextLayer`` is created +in the `.load` handler, but this is not called until the ``Window`` is pushed +onto the stack. The attempt to set the time to the ``TextLayer`` by calling +`update_time()` before it is displayed will cause the app to crash. + +```c +#include + +static Window *s_window; +static TextLayer *s_time_layer; + +static void window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + s_time_layer = text_layer_create(bounds); + text_layer_set_text(s_time_layer, "00:00"); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); +} + +static void update_time() { + time_t now = time(NULL); + struct tm *tick_time = localtime(&now); + + static char s_buffer[8]; + strftime(s_buffer, sizeof(s_buffer), "%H:%M", tick_time); + text_layer_set_text(s_time_layer, s_buffer); +} + +static void init() { + s_window = window_create(); + window_set_window_handlers(s_window, (WindowHandlers) { + .load = window_load + }); + + update_time(); + + window_stack_push(s_window, true); +} + +static void deinit() { + window_destroy(s_window); +} + +int main() { + init(); + app_event_loop(); + deinit(); +} +``` + +Supposing the cause of this crash was not obvious from the order of execution, +GDB can be used to identify the cause of the crash with ease. It is known that +the app crashes on launch, so the first breakpoint is placed at the beginning of +`init()`. After continuing execution, the app will pause at this location: + +```text +(gdb) b init +Breakpoint 2 at 0x2002010c: file ../src/main.c, line 26. +(gdb) c +Continuing. + +Breakpoint 2, main () at ../src/main.c:41 +41 init(); +``` + +Using the `step` command (or Enter key), the developer can step through all the +statements that occur during app initialization until the crash is found (and +the `app_crashed` breakpoint is encountered. Alternatively, `bt full` can be +used after the crash occurs to inspect the local variables at the time of the +crash: + +```text +(gdb) c +Continuing. + +Breakpoint 1, 0x0804af6c in app_crashed () +(gdb) bt full +#0 0x0804af6c in app_crashed () +No symbol table info available. +#1 0x0800bfe2 in ?? () +No symbol table info available. +#2 0x0800c078 in ?? () +No symbol table info available. +#3 0x0804c306 in ?? () +No symbol table info available. +#4 0x080104f0 in ?? () +No symbol table info available. +#5 0x0804c5c0 in ?? () +No symbol table info available. +#6 0x0805e6ea in text_layer_set_text () +No symbol table info available. +#7 0x20020168 in update_time () at ../src/main.c:22 + now = 2076 + tick_time = + s_buffer = "10:38\000\000" +#8 init () at ../src/main.c:31 +No locals. +#9 main () at ../src/main.c:41 +No locals. +#10 0x080079de in ?? () +No symbol table info available. +#11 0x00000000 in ?? () +No symbol table info available. +``` + +The last statement to be executed before the crash is a call to +`text_layer_set_text()`, which implies that one of its input variables was bad. +It is easy to determine which by printing local variable values with the `p` +command: + +```text +Breakpoint 4, update_time () at ../src/main.c:22 +22 text_layer_set_text(s_time_layer, s_buffer); +(gdb) p s_time_layer +$1 = (TextLayer *) 0x0 <__pbl_app_info> +``` + +In this case, GDB displays `0x0` (`NULL`) for the value of `s_time_layer`, which +shows it has not yet been allocated, and so will cause `text_layer_set_text()` +to crash. And thus, the source of the crash has been methodically identified. A +simple fix here is to swap `update_time()` and ``window_stack_push()`` around so +that `init()` now becomes: + +```c +static void init() { + // Create a Window + s_window = window_create(); + window_set_window_handlers(s_window, (WindowHandlers) { + .load = window_load + }); + + // Display the Window + window_stack_push(s_window, true); + + // Set the time + update_time(); +} +``` + +In this new version of the code the ``Window`` will be pushed onto the stack, +calling its `.load` handler in the process, and the ``TextLayer`` will be +allocated and available for use once execution subsequently reaches +`update_time()`. diff --git a/devsite/source/_guides/debugging/index.md b/devsite/source/_guides/debugging/index.md new file mode 100644 index 00000000..682f01f5 --- /dev/null +++ b/devsite/source/_guides/debugging/index.md @@ -0,0 +1,41 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Debugging +description: | + How to find and fix common compilation and runtime problems in apps. +guide_group: debugging +permalink: /guides/debugging/ +menu: false +generate_toc: false +related_docs: + - Logging +hide_comments: true +--- + +When writing apps, everyone makes mistakes. Sometimes a simple typo or omission +can lead to all kinds of mysterious behavior or crashes. The guides in this +section are aimed at trying to help developers identify and fix a variety of +issues that can arise when writing C code (compile-time) or running the compiled +app on Pebble (runtime). + +There are also a few strategies outlined here, such as app logging and other +features of the `pebble` {% guide_link tools-and-resources/pebble-tool %} that +can indicate the source of a problem in the vast majority of cases. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/design-and-interaction/benefits.md b/devsite/source/_guides/design-and-interaction/benefits.md new file mode 100644 index 00000000..2f0fa34a --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/benefits.md @@ -0,0 +1,129 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Benefits of Design Guidelines +description: | + Learn the main concepts of design guidelines, why they are needed, and how + they can help developers. +guide_group: design-and-interaction +menu: true +permalink: /guides/design-and-interaction/benefits/ +generate_toc: true +order: 0 +--- + +## What are Design Guidelines? + +Design guidelines are a set of concepts and rules used to create an app's user +interface. These define how the layout on-screen at any one time should be used +to maximize the efficiency of presenting data to the user, as well as quickly +informing them how to choose their next action. An app creator may look to other +popular apps to determine how they have helped their users understand their +app's purpose, either through the use of iconography or text highlighting. They +may then want to use that inspiration to enable users of the inspiring app to +easily use their own app. If many apps use the same visual cues, then future +users will already be trained in their use when they discover them. + + +## What are Interaction Patterns? + +Similar to design guidelines, interaction patterns define how to implement app +interactivity to maximize its efficiency. If a user can predict how the app will +behave when they take a certain action, or be able to determine which action +fits that which they want to achieve without experimentation, then an intuitive +and rewarding experience will result. + +In addition to purely physical actions such as button presses and accelerometer +gestures, the virtual navigation flow should also be considered at the design +stage. It should be intuitive for the user to move around the app screens to +access the information or execute the commands as they would expect on a first +guess. An easy way to achieve this is to use a menu with the items clearly +labelling their associated actions. An alternative is to use explicit icons to +inform the user implicitly of their purpose without needing to label them all. + + +## Why are They Needed? + +Design guidelines and interaction patterns exist to help the developer help the +user by ensuring user interface consistency across applications on the platform. +It is often the case that the developer will have no problem operating their own +watchapp because they have been intimately familiar with how it is supposed to +work since its inception. When such an app is given to users, they may receive +large amounts of feedback from confused users who feel they do not know if an +app supports the functionality they though it did, or even how to find it. By +considering a novice user from the beginning of the UI design and +implementation, this problem can be avoided. + +A Pebble watchapp experience is at its best when it can be launched, used for +its purpose in the smallest amount of time, and then closed back to the +watchface. If the user must spend a long time navigating the app's UI to get to +the information they want, or the information takes a while to arrive on every +launch, the app efficiency suffers. To avoid this problem, techniques such as +implementing a list of the most commonly used options in an app (according to +the user or the whole user base) to aid fast navigation, or caching remotely +fetched data which may still be relevant from the last update will improve the +user experience. + +From an interaction pattern point of view, a complex layout filled with abstract +icons may confuse a first-time user as to what each of them represents. Apps can +mitigate this problem by using icons that have pre-established meanings across +languages, such as the 'Play/Pause' icon or the 'Power' icon, seen on many forms +of devices. + + +## What Are the Benefits? + +The main benefits of creating and following design guidelines and common +interaction patterns are summarized as follows: + +* User interface consistency, which breeds familiarity and predictability. + +* Clarity towards which data is most important and hence visible and usable. + +* Reduced user confusion and frustration, leading to improved perception of + apps. + +* No need to include explicit usage instructions in every app to explain how it + must be used. + +* Apps that derive design from the system apps can benefit from any learned + behavior all Pebble users may develop in using their watches out the box. + +* Clearer, more efficient and better looking apps! + + +## Using Existing Affordances + +Developers can use concepts and interaction patterns already employed in system +apps and popular 3rd party apps to lend those affordances to your own apps. An +example of this in mobile apps is the common 'swipe down to refresh' action. By +using this action in their app, many mobile app makers can benefit from users +who have already been trained to perform this action, and can free up their +app's UI for a cleaner look, or use the space that would have been used by a +'refresh' button to add an additional feature. + +In a similar vein, knowing that the Back button always exits the current +``Window`` in a Pebble app, a user does not have to worry about knowing how to +navigate out of it. Similarly, developers do not have to repeatedly implement +exiting an app, as this action is a single, commonly understood pattern - just +press the Back button! On the other hand, if a developer overrides this action a +user may be confused or frustrated when the app fails to exit as they would +expect, and this could mean a negative opinion that could have been avoided. + + +## What's Next? + +Read {% guide_link design-and-interaction/core-experience %} to learn how design +guidelines helped shape the core Pebble system experience. diff --git a/devsite/source/_guides/design-and-interaction/core-experience.md b/devsite/source/_guides/design-and-interaction/core-experience.md new file mode 100644 index 00000000..f6d1a1f4 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/core-experience.md @@ -0,0 +1,263 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Core Experience Design +description: | + How design guidelines shape the core Pebble app experience. +guide_group: design-and-interaction +menu: true +permalink: /guides/design-and-interaction/core-experience/ +generate_toc: true +order: 1 +--- + +The core Pebble experience includes several built-in system apps that use +repeatable design and interaction concepts in their implementation. These +are: + +* Apps are designed with a single purpose in mind, and they do it well. + +* Fast animations are used to draw attention to changing or updating + information. + +* Larger, bolder fonts to highlight important data. + +* A preference for displaying multiple data items in a paginated format rather + than many 'menus within menus'. This is called the 'card' pattern and is + detail in + {% guide_link design-and-interaction/recommended#display-data-sets-using-cards "Display Data Sets Using Cards" %}. + +* Colors are used to enhance the app's look and feel, and are small in number. + Colors are also sometimes used to indicate state, such as the temperature in a + weather app, or to differentiate between different areas of a layout. + + +## System Experience Design + +The core system design and navigation model is a simple metaphor for time on the +user's wrist - a linear representation of the past, present, and future. The +first two are presented using the timeline design, with a press of the Up button +displaying past events (also known as pins), and a press of the Down button +displaying future events. As was the case for previous versions of the system +experience, pressing the Select button opens the app menu. This contains the +system apps, such as Music and Notifications, as well as all the 3rd party apps +the user has installed in their locker. + +![system-navigation](/images/guides/design-and-interaction/system-navigation.png) + +Evidence of the concepts outlined above in action can be seen within the core +system apps in firmware 3.x. These are described in detail in the sections +below. + + +### Music + +![music](/images/guides/design-and-interaction/music.png) + +The Music app is designed with a singular purpose in mind - display the current +song and allow control over playback. To achieve this, the majority of the +screen space is devoted to the most important data such as the song title and +artist. The remainder of the space is largely used to display the most immediate +controls for ease of interaction in an action bar UI element. + +The 'previous track' and 'next track' icons on the action bar are ones with +pre-existing affordances which do not require specific instruction for new users +thanks to their universal usaging other media applications. The use of the '...' +icon is used as an additional commonly understood action to indicate more +functionality is available. By single-pressing this action, the available +actions change from preview/next to volume up/volume down, reverting on a +timeout. This is preferable to a long-press, which is typically harder to +discover without an additional prompt included in the UI. + +A press of the Back button returns the user to the appface menu, where the Music +appface displays the name and artist of the currently playing track, in a +scrolling 'marquee' manner. If no music is playing, no information is shown +here. + + +### Notifications + +![notifications](/images/guides/design-and-interaction/notifications.png) + +The system Notifications app allows a user to access all their past received +notifications in one place. Due to the fact that the number of notifications +received can be either small or large, the main view of the app is implemented +as a menu, with each item showing each notification's icon, title and the first +line of the body content. In this way it is easy for a user to quickly scroll +down the list and identify the notification they are looking for based on these +first hints. + +The first item in the menu is a 'Clear All' option, which when selected prompts +the user to confirm this action using a dialog. This dialog uses the action bar +component to give the user the opportunity to confirm this action with the +Select button, or to cancel it with the Back button. + +Once the desired item has been found, a press of the Select button opens a more +detailed view, where the complete notification content can be read, scrolling +down if needed. The fact that there is more content available to view is hinted +at using the arrow marker and overlapping region at the bottom of the layout. + + +### Alarms + +![alarms](/images/guides/design-and-interaction/alarms.png) + +The Alarms app is the most complex of the system apps, with multiple screens +dedicated to input collection from the user. Like the Watchfaces and Music apps, +the appface in the system menu shows the time of the next upcoming scheduled +alarm, if any. Also in keeping with other system apps, the main screen is +presented using a menu, with each item representing a scheduled alarm. Each +alarm is treated as a separate item, containing different settings and values. + +A press of the Select button on an existing item will open the action menu +containing a list of possible actions, such as 'Delete' or 'Disable'. Pressing +Select on the top item ('+') will add a new item to the list, using multiple +subsequent screens to collect data about the alarm the user wishes to schedule. +Using multiple screens avoids the need for one screen to contain a lot of input +components and clutter up the display. In the time selection screen, the current +selection is marked using a green highlight. The Up and Down buttons are used to +increase and decrease the currently selected field respectively. + +Once a time and recurring frequency has been chosen by the user, the new alarm +is added to the main menu list. The default state is enabled, marked by the word +'ON' to the right hand side, but can be disabled in which case 'OFF' is +displayed instead. + + +### Watchfaces + +![watchfaces](/images/guides/design-and-interaction/watchfaces.png) + +The Watchfaces system app is similar to the Notifications app, in that it uses a +menu as its primary means of navigation. Each watchface available in the user's +locker is shown as a menu item, with a menu icon if one has been included by the +watchface developer. The currently active watchface is indicated by the presence +of 'Active' as that item's subtitle. + +Once the user has selected a new watchface, they are shown a confirmation dialog +to let them know their choice was successful. If the watchface is not currently +loaded on the watch, a progress bar is shown briefly while the data is loaded. +Once this is done the newly chosen watchface is displayed. + + +### Settings + +![settings](/images/guides/design-and-interaction/settings.png) + +The Settings app uses the system appface to display the date, the battery charge +level, and the Bluetooth connection without the need to open the app proper. If +the user does open the app, they are greeted with a menu allowing a choice of +settings category. This approach saves the need for a single long list of +settings that would require a lot of scrolling. + +Once a category has been chosen, the app displays another menu filled with +interactive menu rows that change various settings. Each item shows the name of +the setting in bold as the item title, with the current state of the setting +shown as the subtitle. + +When the user presses Select, the state of the currently selected setting is +changed, usually in a binary rotation of On -> Off states. If the setting does +not operate with a binary state (two states), or has more than two options, an +action menu window is displayed with the available actions, allowing the user to +select one with the Select button. + +A press of the Back button from a category screen returns the user to the +category list, where they can make another selection, or press Back again to +return to the app menu. + + +### Sports API + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/sports.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The Sports API app is designed around displaying the most immediate relevant +data of a particular sporting activity, such as running or cycling. A suitable +Android or iOS companion app pushes data to this app using the +{% guide_link communication/using-the-sports-api "PebbleKit Sports API" %} +at regular intervals. This API enables third-party sports app developers to +easily add support for Pebble without needing to create and maintain their own +watchapp. + +The high contrast choice of colors makes the information easy to read at a +glance in a wide variety of lighting conditions, ideal for use in outdoor +activities. The action bar is also used to present the main action available to +the user - the easily recognizable 'pause' action to suspend the current +activity for a break. This is replaced by the equally recognizable 'play' icon, +the action now used to resume the activity. + +This API also contains a separate Golf app for PebbleKit-compatible apps to +utilize in tracking the user's golf game. + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/golf.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The Golf app uses a similar design style with larger fonts for important numbers +in the center of the layout, with the action bar reserved for additional input, +such as moving between holes. Being an app that is intended to be in use for +long periods of time, the status bar is used to display the current time for +quick reference without needing to exit back to the watchface. + + +## Timeline Experience Design + +Evidence of guided design can also be found in other aspects of the system, most +noticeably the timeline view. With pins containing possibly a large variety of +types of information, it is important to display a view which caters for as many +types as possible. As detailed in +{% guide_link pebble-timeline/pin-structure "Creating Pins" %}, +pins can be shown in two different ways; the event time, icon and title on two +lines or a single line, depending on how the user is currently navigating the +timeline. When a pin is highlighted, as much information is shown to the user as +possible. + +![timeline](/images/guides/design-and-interaction/timeline.png) + +Users access the timeline view using Up and Down buttons from the watchface to +go into the past and future respectively. The navigation of the timeline also +uses animations for moving elements that gives life to the user interface, and +elements such as moving the pin icon between windows add cohesion between the +different screens. A press of the Select button opens the pin to display all the +information it contains, and is only one click away. + +A further press of the Select button opens the pin's action menu, containing a +list of all the actions a user may take. These actions are directly related to +the pin, and can be specified when it is created. The system provides two +default actions: 'Remove' to remove the pin from the user's timeline, and 'Mute +[Name]' to mute all future pins from that source. This gives the user control +over which pins they see in their personal timeline. Mute actions can be +reversed later in the mobile app's 'Apps/Timeline' screen. + + +## What's Next? + +Read {% guide_link design-and-interaction/recommended %} for tips on creating an +intuitive app experience. diff --git a/devsite/source/_guides/design-and-interaction/implementation.md b/devsite/source/_guides/design-and-interaction/implementation.md new file mode 100644 index 00000000..fe1f0f83 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/implementation.md @@ -0,0 +1,80 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Example Implementations +description: | + Resources and code samples to implement common design and UI patterns. +guide_group: design-and-interaction +menu: true +permalink: /guides/design-and-interaction/implementation/ +generate_toc: true +order: 4 +--- + +This guide contains resources and links to code examples that may help +developers implement UI designs and interaction patterns recommended in the +other guides in this section. + + +## UI Components and Patterns + +Developers can make use of the many UI components available in SDK 3.x in +combination with the +{% guide_link design-and-interaction/recommended#common-design-styles "Common Design Styles" %} +to ensure the user experience is consistent and intuitive. The following +components and patterns are used in the Pebble experience, and listed in the +table below. Some are components available for developers to use in the SDK, or +are example implementations designed for adaptation and re-use. + +| Pattern | Screenshot | Description | +|---------|------------|-------------| +| [`Menu Layer`](``MenuLayer``) | ![](/images/guides/design-and-interaction/menulayer.png) | Show many items in a list, allow scrolling between them, and choose an option. | +| [`Status Bar`](``StatusBarLayer``) | ![](/images/guides/design-and-interaction/alarm-list~basalt.png) | Display the time at the top of the Window, optionally extended with additional data. | +| [`Radio Button List`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/radio_button_window.c) | ![](/images/guides/design-and-interaction/radio-button.png) | Allow the user to specify one choice out of a list. | +| [`Checkbox List`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/checkbox_window.c) | ![](/images/guides/design-and-interaction/checkbox-list.png) | Allow the user to choose multiple different options from a list. | +| [`List Message`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/list_message_window.c) | ![](/images/guides/design-and-interaction/list-message.png) | Provide a hint to help the user choose from a list of options. | +| [`Message Dialog`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/dialog_message_window.c) | ![](/images/guides/design-and-interaction/dialog-message.gif) | Show an important message using a bold fullscreen alert. | +| [`Choice Dialog`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/dialog_choice_window.c) | ![](/images/guides/design-and-interaction/dialog-choice-patterns.png) | Present the user with an important choice, using the action bar and icons to speed up decision making. | +| [`PIN Entry`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/pin_window.c) | ![](/images/guides/design-and-interaction/pin.png) | Enable the user to input integer data. | +| [`Text Animation`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/text_animation_window.c) | ![](/images/guides/design-and-interaction/text-change-anim.gif) | Example animation to highlight a change in a text field. | +| [`Progress Bar`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/progress_bar_window.c) | ![](/images/guides/design-and-interaction/progress-bar.gif) | Example progress bar implementation on top of a ``StatusBarLayer``. | +| [`Progress Layer`]({{site.links.examples_org}}/ui-patterns/blob/master/src/windows/progress_layer_window.c) | ![](/images/guides/design-and-interaction/progresslayer.gif) | Example implementation of the system progress bar layer. | + + +## Example Apps + +Developers can look at existing apps to begin to design (or improve) their user +interface and interaction design. Many of these apps can be found on the +appstore with links to their source code, and can be used as inspiration. + + +### Cards Example (Weather) + +The weather [`cards-example`]({{site.links.examples_org}}/cards-example) +embodies the 'card' design pattern. Consisting of a single layout, it displays +all the crucial weather-related data in summary without the need for further +layers of navigation. Instead, the buttons are reserved for scrolling between +whole sets of data pertaining to different cities. The number of 'cards' is +shown in the top-right hand corner to let the user know that there is more data +present to be scrolled through, using the pre-existing Up and Down button action +affordances the user has already learned. This helps avoid implementing a novel +navigation pattern, which saves time for both the user and the developer. + +![weather >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/design-and-interaction/weather.gif) + +When the user presses the appropriate buttons to scroll through sets of data, +the changing information is animated with fast, snappy, and highly visible +animations to reinforce the idea of old data moving out of the layout and being +physically replaced by new data. diff --git a/devsite/source/_guides/design-and-interaction/in-the-round.md b/devsite/source/_guides/design-and-interaction/in-the-round.md new file mode 100644 index 00000000..cbf19244 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/in-the-round.md @@ -0,0 +1,123 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Round App Design +description: | + Tips and advice for designing apps that take advantage of the Pebble Time + Round display +guide_group: design-and-interaction +menu: true +permalink: /guides/design-and-interaction/in-the-round/ +generate_toc: true +order: 3 +--- + +> This guide is about designing round apps. For advice on implementing a round +> design in code, read {% guide_link user-interfaces/round-app-ui %}. + +With the release of the Chalk [platform](/faqs#pebble-sdk), developers must take +new features and limitations into account when designing their apps. New and +existing apps that successfully adapt their layout and colors for both Aplite +and Basalt should also endeavor to do so for the Chalk platform. + + +## Minor Margins + +The Pebble Time Round display requires a small two pixel border on each edge, to +compensate for the bezel design. To this end, it is highly encouraged to allow +for this in an app's design. This may involve stretching a background color to +all outer edges, or making sure that readable information cannot be displayed in +this margin, or else it may not be visible. + +Avoid thin rings around the edge of the display, even after accounting for the +two pixel margin as manufacturing variations may cause them to be visibly +off-center. Instead use thick rings, or inset them significantly from the edge +of the screen. + + +## Center of Attention + +With the round Chalk display, apps no longer have the traditional constant +amount of horizontal space available. This particularly affects the use of the +``MenuLayer``. To compensate for this, menus are now always centered on the +highlighted item. Use this to display additional information in the cell with +the most space available, while showing reduced content previews in the +unhighlighted cells. + +![centered >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/design-and-interaction/center-layout~chalk.png) + +Menus built using the standard cell drawing functions will automatically adopt +this behavior. If performing custom cell drawing, new APIs are available to +help implement this behavior. For more information, look at the ``Graphics`` +documentation, as well as the ``menu_layer_set_center_focused()`` and +``menu_layer_is_index_selected()`` to help with conditional drawing. + + +## Pagination + +Another key concept to bear in mind when designing for a round display is text +flow. In traditional Pebble apps, text in ``ScrollLayer`` or ``TextLayer`` +elements could be freely moved and scrolled with per-pixel increments without +issue. However, with a round display each row of text can have a different +width, depending on its vertical position. If such text was reflowed while +moving smoothly down the window, the layout would reflow so often the text would +be very difficult to read. + +![center-layout >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/design-and-interaction/scrolling-with-text-flow.gif) + +The solution to this problem is to scroll through text in pages, a technique +known as pagination. By moving through the text in discrete sections, the text +is only reflowed once per 'page', and remains easily readable as the user is +navigating through it. The ``ScrollLayer`` has been updated to implement this +on Chalk. + +To inform the user that more content is available, the Chalk platform allows use +of the ``ContentIndicator`` UI component. This facilitates the display of two +arrows at the top and bottom of the display, similar to those seen in the +system UI. + +![content-indicator >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/design-and-interaction/content-indicator.png) + +A ``ContentIndicator`` can be created from scratch and manually managed to +determine when the arrows should be shown, or a built-in instance can be +obtained from a ``ScrollLayer``. + + + +## Platform-Specific Designs + +Sometimes a design that made sense on a rectangular display does not make sense +on a circular one, or could be improved. Be open to creating a new UI for the +Chalk platform, and selecting which to use based on the display shape. + +For example, in the screenshot below the linear track display was incompatible +with the round display and center-focused menus, leading to a completely +different design on Chalk that shows the same information. + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/caltrain-stops.png", + "platforms": [ + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## What's Next? + +Read {% guide_link design-and-interaction/implementation %} to learn how to use +and implement the UI components and patterns encouraged in SDK 3.x apps. diff --git a/devsite/source/_guides/design-and-interaction/index.md b/devsite/source/_guides/design-and-interaction/index.md new file mode 100644 index 00000000..5af5ef48 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/index.md @@ -0,0 +1,56 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Design and Interaction +description: | + How to design apps to maximise engagement, satisfaction, efficiency and + overall user experience. +guide_group: design-and-interaction +menu: false +permalink: /guides/design-and-interaction/ +generate_toc: false +hide_comments: true +--- + +Interaction guides are intended to help developers design their +apps to maximize user experience through effective, consistent visual design and +user interactions on the Pebble platform. Readers can be non-programmers and +programmers alike: All material is explained conceptually and no code must be +understood. For code examples, see +{% guide_link design-and-interaction/implementation %}. + +By designing apps using a commonly understood and easy to understand visual +language users can get the best experience with the minimum amount of effort +expended - learning how they work, how to operate them or what other behavior +is required. This can help boost how efficiently any given app is used as well +as help reinforce the underlying patterns for similar apps. For example, the +layout design should make it immediately obvious which part of the UI contains +the vital information the user should glance at first. + +In addition to consistent visual design, implementing a common interaction +pattern helps an app respond to users as they would expect. This allows them to +correctly predict how an app will respond to their input without having to +experiment to find out. + +To get a feel for how to approach good UI design for smaller devices, read other +examples of developer design guidelines such as Google's +[Material Design](http://www.google.com/design/spec/material-design/introduction.html) +page or Apple's +[iOS Human Interface Guidelines](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/). + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/design-and-interaction/one-click-actions.md b/devsite/source/_guides/design-and-interaction/one-click-actions.md new file mode 100644 index 00000000..c8a3fab1 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/one-click-actions.md @@ -0,0 +1,260 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: One Click Actions +description: | + Details about how to create One Click Action watchapps +guide_group: design-and-interaction +menu: true +order: 2 +related_docs: + - AppExitReason + - AppGlanceSlice + - AppMessage +related_examples: + - title: One Click Action Example + url: https://github.com/pebble-examples/one-click-action-example +--- + +One click actions are set to revolutionize the way users interact with their +Pebble by providing instant access to their favorite one click watchapps, +directly from the new system launcher. Want to unlock your front door? +Call an Uber? Or perhaps take an instant voice note? With one click actions, +the user is able to instantly perform a single action by launching an app, and +taking no further action. + +![Lockitron >{pebble-screenshot,pebble-screenshot--time-black}](/images/guides/design-and-interaction/lockitron.png) + +### The One Click Flow + +It’s important to develop your one click application with a simple and elegant +flow. You need to simplify the process of your application by essentially +creating an application which serves a single purpose. + +The typical flow for a one click application would be as follows: + +1. Application is launched +2. Application performs action +3. Application displays status to user +4. Application automatically exits to watchface if the action was successful, +or displays status message and does not exit if the action failed + +If we were creating an instant voice note watchapp, the flow could be as +follows: + +1. Application launched +2. Application performs action (take a voice note) + 1. Start listening for dictation + 2. Accept dictation response +3. Application displays a success message +4. Exit to watchface + +In the case of a one click application for something like Uber, we would need to +track the state of any existing booking to prevent ordering a second car. We +would also want to update the ``App Glance`` +as the status of the booking changes. + +1. Application launched +2. If a booking exists: + 1. Refresh booking status + 2. Update ``App Glance`` with new status + 3. Exit to watchface +3. Application performs action (create a booking) + 1. Update AppGlance: “Your Uber is on it’s way” + 2. Application displays a success message + 3. Exit to watchface + +### Building a One Click Application + +For this example, we’re going to build a one click watchapp which will lock or +unlock the front door of our virtual house. We’re going to use a virtual +[Lockitron](https://lockitron.com/), or a real one if you’re lucky enough to +have one. + +Our flow will be incredibly simple: + +1. Launch the application +2. Take an action (toggle the state of the lock) +3. Update the ``App Glance`` to indicate the new lock state +4. Display a success message +5. Exit to watchface + +For the sake of simplicity in our example, we will not know if someone else has +locked or unlocked the door using a different application. You can investigate +the [Lockitron API](http://api.lockitron.com) if you want to develop this idea +further. + +In order to control our Lockitron, we need the UUID of the lock and an access +key. You can generate your own virtual lockitron UUID and access code on the +[Lockitron website](https://api.lockitron.com/v1/getting_started/virtual_locks). + +```c +#define LOCKITRON_LOCK_UUID "95c22a11-4c9e-4420-adf0-11f1b36575f2" +#define LOCKITRON_ACCESS_TOKEN "99e75a775fe737bb716caf88f161460bb623d283c3561c833480f0834335668b" +``` + +> Never publish your actual Lockitron access token in the appstore, unless you +want strangers unlocking your door! Ideally you would make these fields +configurable using [Clay for Pebble](https://github.com/pebble/clay). + +We’re going to need a simple enum for the state of our lock, where 0 is +unlocked, 1 is locked and anything else is unknown. + +```c +typedef enum { + LOCKITRON_UNLOCKED, + LOCKITRON_LOCKED, + LOCKITRON_UNKNOWN +} LockitronLockState; +``` + +We’re also going to use a static variable to keep track of the state of our +lock. + +```c +static LockitronLockState s_lockitron_state; +``` + +When our application launches, we’re going to initialize ``AppMessage`` and +then wait for PebbleKit JS to tell us it’s ready. + +```c +static void prv_init(void) { + app_message_register_inbox_received(prv_inbox_received_handler); + app_message_open(256, 256); + s_window = window_create(); + window_stack_push(s_window, false); +} + +static void prv_inbox_received_handler(DictionaryIterator *iter, void *context) { + Tuple *ready_tuple = dict_find(iter, MESSAGE_KEY_APP_READY); + if (ready_tuple) { + // PebbleKit JS is ready, toggle the Lockitron! + prv_lockitron_toggle_state(); + return; + } + // ... +} +``` + +In order to toggle the state of the Lockitron, we’re going to send an +``AppMessage`` to PebbleKit JS, containing our UUID and our access key. + +```c +static void prv_lockitron_toggle_state() { + DictionaryIterator *out; + AppMessageResult result = app_message_outbox_begin(&out); + dict_write_cstring(out, MESSAGE_KEY_LOCK_UUID, LOCKITRON_LOCK_UUID); + dict_write_cstring(out, MESSAGE_KEY_ACCESS_TOKEN, LOCKITRON_ACCESS_TOKEN); + result = app_message_outbox_send(); +} +``` + +PebbleKit JS will handle this request and make the relevant ajax request to the +Lockitron API. It will then return the current state of the lock and tell our +application to exit back to the default watchface using +``AppExitReason``. See the +[full example](https://github.com/pebble-examples/one-click-action-example) for +the actual Javascript implementation. + +```c +static void prv_inbox_received_handler(DictionaryIterator *iter, void *context) { + // ... + Tuple *lock_state_tuple = dict_find(iter, MESSAGE_KEY_LOCK_STATE); + if (lock_state_tuple) { + // Lockitron state has changed + s_lockitron_state = (LockitronLockState)lock_state_tuple->value->int32; + // App will exit to default watchface + app_exit_reason_set(APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY); + // Exit the application by unloading the only window + window_stack_remove(s_window, false); + } +} +``` + +Before our application terminates, we need to update the +``App Glance`` with the current state +of our lock. We do this by passing our current lock state into the +``app_glance_reload`` method. + +```c +static void prv_deinit(void) { + window_destroy(s_window); + // Before the application terminates, setup the AppGlance + app_glance_reload(prv_update_app_glance, &s_lockitron_state); +} +``` + +We only need a single ``AppGlanceSlice`` for our ``App Glance``, but it’s worth +noting you can have multiple slices with varying expiration times. + +```c +static void prv_update_app_glance(AppGlanceReloadSession *session, size_t limit, void *context) { + // Check we haven't exceeded system limit of AppGlances + if (limit < 1) return; + + // Retrieve the current Lockitron state from context + LockitronLockState *lockitron_state = context; + + // Generate a friendly message for the current Lockitron state + char *str = prv_lockitron_status_message(lockitron_state); + APP_LOG(APP_LOG_LEVEL_INFO, "STATE: %s", str); + + // Create the AppGlanceSlice (no icon, no expiry) + const AppGlanceSlice entry = (AppGlanceSlice) { + .layout = { + .template_string = str + }, + .expiration_time = time(NULL)+3600 + }; + + // Add the slice, and check the result + const AppGlanceResult result = app_glance_add_slice(session, entry); + if (result != APP_GLANCE_RESULT_SUCCESS) { + APP_LOG(APP_LOG_LEVEL_ERROR, "AppGlance Error: %d", result); + } +} +``` + +### Handling Launch Reasons + +In the example above, we successfully created an application that will +automatically execute our One Click Action when the application is launched. +But we also need to be aware of some additional launch reasons where it would +not be appropriate to perform the action. + +By using the ``launch_reason()`` method, we can detect why our application was +started and prevent the One Click Action from firing unnecessarily. + +A common example, would be to detect if the application was actually started by +the user, from either the launcher, or quick launch. + +```c + if(launch_reason() == APP_LAUNCH_USER || launch_reason() == APP_LAUNCH_QUICK_LAUNCH) { + // Perform One Click + } else { + // Display a message + } +``` + +### Conclusion + +As you can see, it’s a relatively small amount of code to create one click +watchapps and we hope this inspires you to build your own! + +We recommend that you check out the complete +[Lockitron sample](https://github.com/pebble-examples/one-click-action-example) +application and also the ``App Glance`` and ``AppExitReason`` guides for further +information. diff --git a/devsite/source/_guides/design-and-interaction/recommended.md b/devsite/source/_guides/design-and-interaction/recommended.md new file mode 100644 index 00000000..b027bd94 --- /dev/null +++ b/devsite/source/_guides/design-and-interaction/recommended.md @@ -0,0 +1,405 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Recommended Guidelines and Patterns +description: | + Pebble's recommended guidelines for creating awesome app experiences. +guide_group: design-and-interaction +menu: true +permalink: /guides/design-and-interaction/recommended/ +generate_toc: true +order: 2 +--- + +This page contains recommendations for things to consider when designing an +app's visual styles and interaction patterns. The aim here is to encourage +efficiency through optional conformity to common ideas and concepts, and to +breed a consistent experience for users across apps. Developers can also find +suggested interface styles to use when building the navigation of apps to best +display the information contained within. + + +## Tips for UI Design + +To achieve an effective, clear, and intuitive design developers should: + +* Keep layouts **simple**, with only as much information displayed as is + **immediately required**. This encourages quick usage of the app, distracting + the user for the minimal amount of time necessary for the app to achieve its + goals. + +* Give priority to the **most commonly used/main purpose** functionality of the + app in the UI. Make it easy to use the biggest features, but still easy to + find additional functionality that would otherwise be hidden. + +* Use **larger fonts** to highlight the **most important data** to be read at a + glance. Consider font size 28 for larger items, and a minimum of 18 for + smaller ones. + +* Take advantage of colors to convey additional **information without any text** + if they are already associated as such, such as green for a task complete. + +* Try to avoid any colors used in places they may have a **pre-conceived + meaning** which does not apply, such as red text when there are no errors. + +* Use animations to embody the layout with **character** and flourish, as well + as to **draw the eye** to updated or changing information. + +* Ensure the UI gives **direct feedback** to the user's input, or else they may + think their button presses are having no effect. + + +## Tips for UI Interaction + +* Avoid using the Pebble buttons for actions **not** already associated with + them, unless clearly marked in the UI using an ``ActionBarLayer`` or similar + method. When using the Up and Down buttons for 'previous item' and 'next item' + respectively, there may be no need for a visual cue. + +* Use iconography with **pre-existing visual associations** (such as a 'stop' + icon) to take advantage of the inherent behavior the user will have when + seeing it. This can avoid the need for explicit instructions to train the user + for an app's special case. + +* Ensure the navigation between app ``Window``s is **logical** with the input + given and the information displayed. For example, an app showing bus + timetables should use higher level screens for quickly navigating to a + particular area/station, with lower level views reserved for scrolling through + the actual timetable data. + +* If possible, **preserve the state of the app** and/or previous navigation if + the app is commonly used for a repetitive task. In the bus timetable example, + the user may only look up two stations for a to-from work journey. Learn this + behavior and store it with the [Persistent Storage API](``Storage``) to + intelligently adapt the UI on subsequent launches to show the relevant + information. This helps the user avoid navigating through multiple menus every + time they launch the app. + + +## Common Design Styles + +The following are common design styles that have been successfully used in +system and 3rd party apps, and are recommended for use in the correct manner. + + +### Display Data Sets Using Cards + +![card >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/design-and-interaction/card.gif) + +The 'card' style aims to reduce the number of menu levels needed to access as +much relevant information as possible. Instead of a menu to select a data set +leading to a menu to explore each item in that set, a single ``Window`` is +designed that displays an entire data set. This view then uses the Pebble Up and +Down buttons to scroll through complete data sets in an array of many sets. + +An example of this is the +{% guide_link design-and-interaction/implementation#cards-example-weather "cards-example" %} +example app, which displays all weather data in a single view and pages through +sets of data for separate locations with the Up and Down buttons. This style of +UI design allows access to lots of information without navigating through +several menus to view it. + + +### List Options with a Menu + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/list.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The style is one of the most basic, tried and true styles. Using the +``MenuLayer`` UI component, the user may choose between multiple app functions +by scrolling with the Up and Down buttons, an interaction pattern afforded to +the developer by the core system experience. Using a menu, a user can navigate +straight to the part of the app or specific action they want. + + +### Execute Actions with an ActionBarLayer + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/actionbar.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The ``ActionBarLayer`` allows easy association of app functionality with the +Pebble buttons. By setting icons to each of the three positions, a user can see +which actions they can perform at a glance and execture them with a single +button press. When pressed, the icon is animated to provide immediate visual +feedback. + +An example of this is the system Music app, that uses the ``ActionBarLayer`` to +inform the user that the Up and Down buttons skip tracks. In this case, the +Select button is displayed with elipses, indicating further actions are +available. A press of this button changes the set of actions on the Up and Down +buttons, enabling them to modify the playback volume instead. + +A collection of icons for common actions is available for use by developers, and +can be found in the {% guide_link app-resources/app-assets %} guide. + + +### Allow Extended Options with an Action Menu + +![actionmenu](/images/guides/design-and-interaction/actionmenu.png) + +If an app screen demands a larger range of available actions than the +``ActionBarLayer`` will allow, present these as a list that slides into +view with a press of the Select button using an action menu. This menu contains +all the available options, and can contain multiple sub-menus, providing levels. +The user can keep track of which level they are currently looking at using the +breadcrumb dots on the left-hand side of the screen when the action menu is +displayed. + +Once an action has been chosen, the user should be informed of the success or +failure of their choice using a new alert dialog window. In the system action +menus, these screens use an eye-catching animation and bold label to convey the +result of the action. This feedback is important to prevent the user from +getting frustrated if they perceive their input has no result, as well as to +reassure them that their action has succeeded without a problem. + + +### Get User Input with a Form + +![list](/images/guides/design-and-interaction/alarm-list-config.png) + +Apps such as the system Alarm app make use of a list of configurable items, with +each active alarm treated as a menu item with properties. The status of each +item is displayed in a menu, with the Select button initiating configuration of +that item. + +When an item is being configured, the data requried to create the item should be +obtained from the user through the use of a form, with manipulable elements. In +the Alarms example, each integer required to schedule an alarm is obtained with +a number field that can have its value incrememted or decremented using the +intuitive Up and Down buttons. The current form element is highlighted with +color, and advancing to the next element is done with the Select button, +doubling as a 'Confirm' action when the end of the form is reached. + + +### Prompting User Action on the Phone + +In some applications, user input is required in the app's configuration page (as +detailed in {% guide_link user-interfaces/app-configuration %}) before the app +can perform its task. An example of this is a feed reader app, that will need +the user to input the URL of their preferred news feed before it can fetch the +feed content. In this case, the watchapp should display a prominent (full- +screen if possible) dialog telling the user that input to the phone app for +configuration is required. + +![action-required >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/design-and-interaction/action-required.png) + +Once the user has performed the required action on the phone, the +[`webviewclosed`](/guides/communication/using-pebblekit-js/) +event should signify that the app can proceed, and that the required data is now +available. + +It should not be the case that this action is required every time the app is +started. In most cases, the input data from the user can be stored with +[Peristent Storage](``Storage``) on the watch, or +[`localStorage`](/guides/communication/using-pebblekit-js/) +on the phone. If the app must get input on every launch (such as a mode +selection), this should be done through a form or menu on the watch, so as to +avoid needing to use the phone. + + +### Show Time and Other Data with the Status Bar + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/alarm-list.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +Under SDK 2.x, the status bar was displayed to users in all menus and watchapps +except watchfaces, or where the developer had explicitly disabled it. This was +useful for showing the time and battery level, but arguably not essential all +the time. + +In SDK 3.x, only apps that are designed to be running for extended periods of +time (such as Music and the Sports API app) show the time, using the +``StatusBarLayer`` UI component. The battery level can easily be seen from the +Settings appface, and so it not necessary to be always visible. Another instance +where the status bar is neccessary is in the Alarms app (shown above), where the +user may need to compare with the current time when setting an alarm. + +If a constant, minimalistic display of app data is required, the +``StatusBarLayer`` can be used to perform this task. It provides a choice of +separator mode and foreground/background colors, and can also be made +transparent. Since is it just another ``Layer``, it can be easily extended with +additional text, icons, or other data. + +For example, the +[`cards-example`]({{site.links.examples_org}}/cards-example) app uses an +extention of the status bar to display the currently selected 'card' (a set of +displayed data). Another example is the progress bar component example from the +[`ui-patterns`]({{site.links.examples_org}}/ui-patterns) app, which +builds upon the dotted separator mode to become a thin progress bar. + +When used in conjunction with the ``ActionBarLayer`` (for example, in the Music +system app), the width of the underlying layer should be adjusted such that the +time displayed is shown in the new center of the app area (excluding that taken +up by the action bar itself). + + +### Show Alerts and Get Decisions with Modal Windows + +![dialog-message >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/design-and-interaction/dialog-message.gif) + +When a significant event occurs while using an app, it should be made visible to +the user through the use of a full-screen model dialog. In a similar way that +notifications and reminders alert the user to events, these layouts consist of +only the important information and an associated icon telling the user the +source of the alert, or the reason for its occurrence. This pattern should also +be used to quickly and efficently tell the user that an app-related error has +occured, including steps on how to fix any potential problems. + +These alerts can also take the form of requests for important decisions to be +made by the user, such as to remember a choice as the default preference: + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/dialog-choice-window.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +In this way the decision can be passed to the user with an immediately obvious +and actionable set of choices. One the choice has been made, the modal window is +dismissed, and a confirmation of the choice displayed. The user should then be +returned to the previous window to resume their use of the app where they left +off. + + +### Using Vibrations and Haptic Feedback + +The Pebble SDK allows the use of the vibration motor to deliver haptic feedback +to the user. This can take the form of short, long, double pulses or more +detailed vibration sequences, allowing a lot of customization as well as +variation between apps. + +To encourage a consistent experience for users, the ``Vibes`` API should be used +with the following points in mind: + +* A short pulse should be used to alert the user to the end of a long-running + in-app event, such as a download completing, preferably when they are not + looking at the watch. + +* A long pulse should be used to alert the user to a failure or error that + requires attention and some interaction. + +* Custom vibration patterns should be used to allow the user to customize haptic + feedback for different events inside the app. + +When the app is open and being actively interacted with no vibration or haptic +feedback should be neccessary on top of the existing visual feedback. However, +some exceptions may occur, such as for visually-impaired users. In these cases +haptic feedback may be very useful in boosting app accessibility. + + +### Handling Connection Problems + +When a watchapp is running, there is no guarantee that the phone connection will +be available at any one time. Most apps will function without this connection, +but if PebbleKit JS, Android, or iOS is required, the user must be informed of +the reason for failure so that they can correct the problem. This check can be +performed at any time using ``connection_service_peek_pebble_app_connection()``. + +An example alert layout is shown below. + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/no-bt-connection.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +A similar situation arises if an app that requires information or responses from +a remote web service attempts to do so, but the phone has no Internet +connection. This may be because the user has opted to disable their data +connections, or they may be out of range. + +Another example alert layout is shown below for this situation. + +{% screenshot_viewer %} +{ + "image": "/images/guides/design-and-interaction/no-inet-connection.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +If these kinds of situations can cause problems for the operation of the app, +consider using the [`Persistent Storage`](``Storage``) API to cache the most +recently loaded data (such as weather, sports scores or news items) from the +last successful launch, and display this to the user (while making them aware of +the data's age) until new data can be obtained. + + +### Hiding Timeline-only Apps + +Watchapps that consist only of a {% guide_link pebble-timeline "Pebble timeline" %} +experience will only need to be launched when configured by the user to select +topic subscriptions. In these cases, developers should hide their app from the +launcher menu to prevent the user needlessly launching it. + +To find out how to do this using the `hiddenApp` property, see +{% guide_link tools-and-resources/app-metadata %}. + + +## Consistent App Configuration + +Watchapps and watchfaces that include user configuration normally include a web +page hosted by the app developer, allowing the user to choose from a set of +options and apply them to the app. Such options include aesthetic options such +as color schemes, larger font sizes, replacement images, data source choices, +and others. Traditionally the design of these pages has been left entirely to +the developer on a per-app basis, and this is reflected in the resulting design +consistency. + +Read {% guide_link user-interfaces/app-configuration %} to learn more about +configuration page design and implementation. + + +## What's Next? + +Read {% guide_link design-and-interaction/in-the-round %} to read tips and +guidance on designing apps that work well on a round display. diff --git a/devsite/source/_guides/events-and-services/accelerometer.md b/devsite/source/_guides/events-and-services/accelerometer.md new file mode 100644 index 00000000..a60b7ba0 --- /dev/null +++ b/devsite/source/_guides/events-and-services/accelerometer.md @@ -0,0 +1,154 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Accelerometer +description: | + How to use data and simple tap gestures from the onboard accelerometer. +guide_group: events-and-services +order: 0 +related_docs: + - AccelerometerService +related_examples: + - title: Feature Accel Discs + url: https://github.com/pebble-examples/feature-accel-discs +--- + +The acceleromter sensor is included in every Pebble watch, and allows collection +of acceleration and orientation data in watchapps and watchfaces. Data is +available in two ways, each suitable to different types of watchapp: + +* Taps events - Fires an event whenever a significant tap or shake of the watch + occurs. Useful to 'shake to view' features. + +* Data batches - Allows collection of data in batches at specific intervals. + Useful for general accelerometer data colleciton. + +As a significant source of regular callbacks, the accelerometer should be used +as sparingly as possible to allow the watch to sleep and conserve power. For +example, receiving data in batches once per second is more power efficient than +receiving a single sample 25 times per second. + + +## About the Pebble Accelerometer + +The Pebble accelerometer is oriented according to the diagram below, showing the +direction of each of the x, y, and z axes. + +![accel-axes](/images/guides/pebble-apps/sensors/accel.png) + +In the API, each axis value contained in an ``AccelData`` sample is measured in +milli-Gs. The accelerometer is calibrated to measure a maximum acceleration of +±4G. Therefore, the range of possible values for each axis is -4000 to +4000. + +The ``AccelData`` sample object also contains a `did_vibrate` field, set to +`true` if the vibration motor was active during the sample collection. This +could possibly contaminate those samples due to onboard vibration, so they +should be discarded. Lastly, the `timestamp` field allows tracking of obtained +accelerometer data over time. + + +## Using Taps + +Adding a subscription to tap events allows a developer to react to any time the +watch is tapped or experiences a shake along one of three axes. Tap events are +received by registering an ``AccelTapHandler`` function, such as the one below: + +```c +static void accel_tap_handler(AccelAxisType axis, int32_t direction) { + // A tap event occured + +} +``` + +The `axis` parameter describes which axis the tap was detected along. The +`direction` parameter is set to `1` for the positive direction, and `-1` for the +negative direction. + +A subscription can be added or removed at any time. While subscribed, +`accel_tap_handler` will be called whenever a tap event is fired by the +accelerometer. Adding a subscription is simple: + +```c +// Subscribe to tap events +accel_tap_service_subscribe(accel_tap_handler); +``` + +```c +// Unsubscribe from tap events +accel_tap_service_unsubscribe(); +``` + + +## Using Data Batches + +Accelerometer data can be received in batches at a chosen sampling rate by +subscribing to the Accelerometer Data Service at any time: + +```c +uint32_t num_samples = 3; // Number of samples per batch/callback + +// Subscribe to batched data events +accel_data_service_subscribe(num_samples, accel_data_handler); +``` + +The ``AccelDataHandler`` function (called `accel_data_handler` in the example +above) is called when a new batch of data is ready for consumption by the +watchapp. The rate at which these occur is dictated by two things: + +* The sampling rate - The number of samples the accelerometer device measures + per second. One value chosen from the ``AccelSamplingRate`` `enum`. + +* The number of samples per batch. + +Some simple math will determine how often the callback will occur. For example, +at the ``ACCEL_SAMPLING_50HZ`` sampling rate, and specifying 10 samples per +batch will result in five calls per second. + +When an event occurs, the acceleromater data can be read from the ``AccelData`` +pointer provided in the callback. An example reading the first set of values is +shown below: + +```c +static void accel_data_handler(AccelData *data, uint32_t num_samples) { + // Read sample 0's x, y, and z values + int16_t x = data[0].x; + int16_t y = data[0].y; + int16_t z = data[0].z; + + // Determine if the sample occured during vibration, and when it occured + bool did_vibrate = data[0].did_vibrate; + uint64_t timestamp = data[0].timestamp; + + if(!did_vibrate) { + // Print it out + APP_LOG(APP_LOG_LEVEL_INFO, "t: %llu, x: %d, y: %d, z: %d", + timestamp, x, y, z); + } else { + // Discard with a warning + APP_LOG(APP_LOG_LEVEL_WARNING, "Vibration occured during collection"); + } +} +``` + +The code above will output the first sample in each batch to app logs, which +will look similar to the following: + +```nc|text +[15:33:18] -data-service.c:21> t: 1449012797098, x: -111, y: -280, z: -1153 +[15:33:18] -data-service.c:21> t: 1449012797305, x: -77, y: 40, z: -1014 +[15:33:18] -data-service.c:21> t: 1449012797507, x: -60, y: 4, z: -1080 +[15:33:19] -data-service.c:21> t: 1449012797710, x: -119, y: -55, z: -921 +[15:33:19] -data-service.c:21> t: 1449012797914, x: 628, y: 64, z: -506 +``` \ No newline at end of file diff --git a/devsite/source/_guides/events-and-services/background-worker.md b/devsite/source/_guides/events-and-services/background-worker.md new file mode 100644 index 00000000..fda06613 --- /dev/null +++ b/devsite/source/_guides/events-and-services/background-worker.md @@ -0,0 +1,248 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Background Worker +description: | + Using the Background Worker to do work in the background, such as activity + tracking. +guide_group: events-and-services +order: 1 +related_docs: + - Worker +related_examples: + - title: Background Counter + url: https://github.com/pebble-examples/feature-background-counter + - title: Background Worker Communication + url: https://github.com/pebble-examples/feature-worker-message +platform_choice: true +--- + +In addition to the main foreground task that every Pebble app implements, a +second background worker task can also be created. This worker is capable of +running even when the foreground task is closed, and is useful for tasks that +must continue for long periods of time. For example, apps that log sensor data. + +There are several important points to note about the capabilities of this +worker when compared to those of the foreground task: + +* The worker is constrained to 10.5 kB of memory. + +* Some APIs are not available to the worker. See the + [*Available APIs*](#available-apis) section below for more information. + +* There can only be one background worker active at a time. In the event that a + second one attempts to launch from another watchapp, the user will be asked to + choose whether the new worker can replace the existing one. + +* The user can determine which app's worker is running by checking the + 'Background App' section of the Settings menu. Workers can also be launched + from there. + +* The worker can launch the foreground app using ``worker_launch_app()``. This + means that the foreground app should be prepared to be launched at any time + that the worker is running. + +> Note: This API should not be used to build background timers; use the +> ``Wakeup`` API instead. + + +## Adding a Worker + +^CP^ The background worker's behavior is determined by code written in a +separate C file to the foreground app. Add a new source file and set the +'Target' field to 'Background Worker'. + +^LC^ The background worker's behavior is determined by code written in a +separate C file to the foreground app, created in the `/worker_src` project +directory. + +
+{% markdown %} +This project structure can also be generated using the +[`pebble` tool](/guides/tools-and-resources/pebble-tool/) with the `--worker` +flag as shown below: + +```bash +$ pebble new-project --worker project_name +``` +{% endmarkdown %} +
+ +The worker C file itself has a basic structure similar to a regular Pebble app, +but with a couple of minor changes, as shown below: + +```c +#include + +static void prv_init() { + // Initialize the worker here +} + +static void prv_deinit() { + // Deinitialize the worker here +} + +int main(void) { + prv_init(); + worker_event_loop(); + prv_deinit(); +} +``` + + +## Launching the Worker + +To launch the worker from the foreground app, use ``app_worker_launch()``: + +```c +// Launch the background worker +AppWorkerResult result = app_worker_launch(); +``` + +The ``AppWorkerResult`` returned will indicate any errors encountered as a +result of attempting to launch the worker. Possible result values include: + +| Result | Value | Description | +|--------|-------|:------------| +| ``APP_WORKER_RESULT_SUCCESS`` | `0` | The worker launch was successful, but may not start running immediately. Use ``app_worker_is_running()`` to determine when the worker has started running. | +| ``APP_WORKER_RESULT_NO_WORKER`` | `1` | No worker found for the current app. | +| ``APP_WORKER_RESULT_ALREADY_RUNNING`` | `4` | The worker is already running. | +| ``APP_WORKER_RESULT_ASKING_CONFIRMATION`` | `5` | The user will be asked for confirmation. To determine whether the worker was given permission to launch, use ``app_worker_is_running()`` for a short period after receiving this result. | + + +## Communicating Between Tasks + +There are three methods of passing data between the foreground and background +worker tasks: + +* Save the data using the ``Storage`` API, then read it in the other task. + +* Send the data to a companion phone app using the ``DataLogging`` API. Details + on how to do this are available in {% guide_link communication/datalogging %}. + +* Pass the data directly while the other task is running, using an + ``AppWorkerMessage``. These messages can be sent bi-directionally by creating + an `AppWorkerMessageHandler` in each task. The handler will fire in both the + foreground and the background tasks, so you must identify the source + of the message using the `type` parameter. + + ```c + // Used to identify the source of a message + #define SOURCE_FOREGROUND 0 + #define SOURCE_BACKGROUND 1 + ``` + + **Foreground App** + + ```c + static int s_some_value = 1; + static int s_another_value = 2; + + static void worker_message_handler(uint16_t type, + AppWorkerMessage *message) { + if(type == SOURCE_BACKGROUND) { + // Get the data, only if it was sent from the background + s_some_value = message->data0; + s_another_value = message->data1; + } + } + + // Subscribe to get AppWorkerMessages + app_worker_message_subscribe(worker_message_handler); + + + // Construct a message to send + AppWorkerMessage message = { + .data0 = s_some_value, + .data1 = s_another_value + }; + + // Send the data to the background app + app_worker_send_message(SOURCE_FOREGROUND, &message); + + ``` + + **Worker** + + ```c + static int s_some_value = 3; + static int s_another_value = 4; + + // Construct a message to send + AppWorkerMessage message = { + .data0 = s_some_value, + .data1 = s_another_value + }; + + static void worker_message_handler(uint16_t type, + AppWorkerMessage *message) { + if(type == SOURCE_FOREGROUND) { + // Get the data, if it was sent from the foreground + s_some_value = message->data0; + s_another_value = message->data1; + } + } + + // Subscribe to get AppWorkerMessages + app_worker_message_subscribe(worker_message_handler); + + // Send the data to the foreground app + app_worker_send_message(SOURCE_BACKGROUND, &message); + ``` + + +## Managing the Worker + +The current running state of the background worker can be determined using the +``app_worker_is_running()`` function: + +```c +// Check to see if the worker is currently active +bool running = app_worker_is_running(); +``` + +The user can tell whether the worker is running by checking the system +'Background App' settings. Any installed workers with be listed there. + +The worker can be stopped using ``app_worker_kill()``: + +```c +// Stop the background worker +AppWorkerResult result = app_worker_kill(); +``` + +Possible `result` values when attempting to kill the worker are as follows: + +| Result | Value | Description | +|--------|-------|:------------| +| ``APP_WORKER_RESULT_SUCCESS`` | `0` | The worker launch was killed successfully. | +| ``APP_WORKER_RESULT_DIFFERENT_APP`` | `2` | A worker from a different app is running, and cannot be killed by this app. | +| ``APP_WORKER_RESULT_NOT_RUNNING`` | `3` | The worker is not currently running. | + + +## Available APIs + +Background workers do not have access to the UI APIs. They also cannot use the +``AppMessage`` API or load resources. Most other APIs are available including +(but not limited to) ``AccelerometerService``, ``CompassService``, +``DataLogging``, ``HealthService``, ``ConnectionService``, +``BatteryStateService``, ``TickTimerService`` and ``Storage``. + +^LC^ The compiler will throw an error if the developer attempts to use an API +unsupported by the worker. For a definitive list of available APIs, check +`pebble_worker.h` in the SDK bundle for the presence of the desired API. + +^CP^ CloudPebble users will be notified by the editor and compiler if they +attempt to use an unavailable API. diff --git a/devsite/source/_guides/events-and-services/buttons.md b/devsite/source/_guides/events-and-services/buttons.md new file mode 100644 index 00000000..73bbb736 --- /dev/null +++ b/devsite/source/_guides/events-and-services/buttons.md @@ -0,0 +1,214 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Buttons +description: | + How to react to button presses in your app. +guide_group: events-and-services +order: 2 +related_docs: + - Clicks + - ClickHandler +related_examples: + - title: App Font Browser + url: https://github.com/pebble-examples/app-font-browser/blob/master/src/app_font_browser.c#L168 +--- + +Button [`Clicks`](``Clicks``) are the primary input method on Pebble. All Pebble +watches come with the same buttons available, shown in the diagram below for +Pebble Time: + +![button-layout](/images/guides/sensors-and-input/button-layout.png) + +These buttons are used in a logical fashion throughout the system: + +* Back - Navigates back one ``Window`` until the watchface is reached. + +* Up - Navigates to the previous item in a list, or opens the past timeline when + pressed from the watchface. + +* Select - Opens the app launcher from the watchface, accepts a selected option + or list item, or launches the next ``Window``. + +* Down - Navigates to the next item in a list, or opens the future timeline when + pressed from the watchface. + +Developers are highly encouraged to follow these patterns when using button +clicks in their watchapps, since users will already have an idea of what each +button will do to a reasonable degree, thus avoiding the need for lengthy usage +instructions for each app. Watchapps that wish to use each button for a specific +action should use the ``ActionBarLayer`` or ``ActionMenu`` to give hints about +what each button will do. + + +## Listening for Button Clicks + +Button clicks are received via a subscription to one of the types of button +click events listed below. Each ``Window`` that wishes to receive button click +events must provide a ``ClickConfigProvider`` that performs these subscriptions. + +The first step is to create the ``ClickConfigProvider`` function: + +```c +static void click_config_provider(void *context) { + // Subcribe to button click events here + +} +``` + +The second step is to register the ``ClickConfigProvider`` with the current +``Window``, typically after ``window_create()``: + +```c +// Use this provider to add button click subscriptions +window_set_click_config_provider(window, click_config_provider); +``` + +The final step is to write a ``ClickHandler`` function for each different type +of event subscription required by the watchapp. An example for a single click +event is shown below: + +```c +static void select_click_handler(ClickRecognizerRef recognizer, void *context) { + // A single click has just occured + +} +``` + + +## Types of Click Events + +There are five types of button click events that apps subscribe to, enabling +virtually any combination of up/down/click events to be utilized in a watchapp. +The usage of each of these is explained below: + + +### Single Clicks + +Most apps will use this type of click event, which occurs whenever the button +specified is pressed and then immediately released. Use +``window_single_click_subscribe()`` from a ``ClickConfigProvider`` function, +supplying the ``ButtonId`` value for the chosen button and the name of the +``ClickHandler`` that will receive the events: + +```c +static void click_config_provider(void *context) { + ButtonId id = BUTTON_ID_SELECT; // The Select button + + window_single_click_subscribe(id, select_click_handler); +} +``` + + +### Single Repeating Clicks + +Similar to the single click event, the single repeating click event allows +repeating events to be received at a specific interval if the chosen button +is held down for a longer period of time. This makes the task of scrolling +through many list items or incrementing a value significantly easier for the +user, and uses fewer button clicks. + +```c +static void click_config_provider(void *context) { + ButtonId id = BUTTON_ID_DOWN; // The Down button + uint16_t repeat_interval_ms = 200; // Fire every 200 ms while held down + + window_single_repeating_click_subscribe(id, repeat_interval_ms, + down_repeating_click_handler); +} +``` + +After an initial press (but not release) of the button `id` subscribed to, the +callback will be called repeatedly with an interval of `repeat_interval_ms` +until it is then released. + +Developers can determine if the button is still held down after the first +callback by using ``click_recognizer_is_repeating()``, as well as get the number +of callbacks counted so far with ``click_number_of_clicks_counted()``: + +```c +static void down_repeating_click_handler(ClickRecognizerRef recognizer, + void *context) { + // Is the button still held down? + bool is_repeating = click_recognizer_is_repeating(recognizer); + + // How many callbacks have been recorded so far? + uint8_t click_count = click_number_of_clicks_counted(recognizer); +} +``` + +> Single click and single repeating click subscriptions conflict, and cannot be +> registered for the same button. + + +### Multiple Clicks + +A multi click event will call the ``ClickHandler`` after a specified number of +single clicks has been recorded. A good example of usage is to detect a double +or triple click gesture: + +```c +static void click_config_provider(void *context) { + ButtonId id = BUTTON_ID_SELECT; // The Select button + uint8_t min_clicks = 2; // Fire after at least two clicks + uint8_t max_clicks = 3; // Don't fire after three clicks + uint16_t timeout = 300; // Wait 300ms before firing + bool last_click_only = true; // Fire only after the last click + + window_multi_click_subscribe(id, min_clicks, max_clicks, timeout, + last_click_only, multi_select_click_handler); +} +``` + +Similar to the single repeating click event, the ``ClickRecognizerRef`` can be +used to determine how many clicks triggered this multi click event using +``click_number_of_clicks_counted()``. + + +### Long Clicks + +A long click event is fired after a button is held down for the specified amount +of time. The event also allows two ``ClickHandler``s to be registered - one for +when the button is pressed, and another for when the button is released. Only +one of these is required. + +```c +static void click_config_provider(void *context) { + ButtonId id = BUTTON_ID_SELECT; // The select button + uint16_t delay_ms = 500; // Minimum time pressed to fire + + window_long_click_subscribe(id, delay_ms, long_down_click_handler, + long_up_click_handler); +} +``` + + +### Raw Clicks + +The last type of button click subcsription is used to track raw button click +events. Like the long click event, two ``ClickHandler``s may be supplied to +receive each of the pressed and depressed events. + +```c +static void click_config_provider(void *context) { + ButtonId id = BUTTON_ID_SELECT; // The select button + + window_raw_click_subscribe(id, raw_down_click_handler, raw_up_click_handler, + NULL); +} +``` + +> The last parameter is an optional pointer to a context object to be passed to +> the callback, and is set to `NULL` if not used. diff --git a/devsite/source/_guides/events-and-services/compass.md b/devsite/source/_guides/events-and-services/compass.md new file mode 100644 index 00000000..0db3b31d --- /dev/null +++ b/devsite/source/_guides/events-and-services/compass.md @@ -0,0 +1,195 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Compass +description: | + How to use data from the Compass API to determine direction. +guide_group: events-and-services +order: 3 +related_docs: + - CompassService +related_examples: + - title: Feature Compass + url: https://github.com/pebble-examples/feature-compass + - title: Official Compass Watchapp + url: https://github.com/pebble-hacks/pebble-compass +--- + +The ``CompassService`` combines data from Pebble's accelerometer and +magnetometer to automatically calibrate the compass and produce a +``CompassHeading``, containing an angle measured relative to magnetic north. + +The compass service provides magnetic north and information about its status +and accuracy through the ``CompassHeadingData`` structure. + + +## Calibration + +The compass service requires an initial calibration before it can return +accurate results. Calibration is performed automatically by the system when +first required. The [`compass_status`](``CompassHeadingData``) field indicates +whether the compass service is calibrating. To help the calibration process, the +app should show a message to the user asking them to move their wrists in +different directions. + +Refer to the [compass example]({{site.links.examples_org}}/feature-compass) for +an example of how to implement this screen. + + +## Magnetic North and True North + +Depending on the user's location on Earth, the measured heading towards magnetic +north and true north can significantly differ. This is due to magnetic +variation, also known as 'declination'. + +Pebble does not automatically correct the magnetic heading to return a true +heading, but the API is designed so that this feature can be added in the future +and the app will be able to automatically take advantage of it. + +For a more precise heading, use the `magnetic_heading` field of +``CompassHeadingData`` and use a webservice to retrieve the declination at the +user's current location. Otherwise, use the `true_heading` field. This field +will contain the `magnetic_heading` if declination is not available, or the true +heading if declination is available. The field `is_declination_valid` will be +true when declination is available. Use this information to tell the user +whether the app is showing magnetic north or true north. + +![Declination illustrated](/images/guides/pebble-apps/sensors/declination.gif) + +> To see the true extent of declination, see how declination has +> [changed over time](http://maps.ngdc.noaa.gov/viewers/historical_declination/). + + +## Battery Considerations + +Using the compass will turn on both Pebble's magnetometer and accelerometer. +Those two devices will have a slight impact on battery life. A much more +significant battery impact will be caused by redrawing the screen too often or +performing CPU-intensive work every time the compass heading is updated. + +Use ``compass_service_subscribe()`` if the app only needs to update its UI when +new compass data is available, or else use ``compass_service_peek()`` if this +happens much less frequently. + + +## Defining "Up" on Pebble + +Compass readings are always relative to the current orientation of Pebble. Using +the accelerometer, the compass service figures out which direction the user is +facing. + +![Compass Orientation](/images/guides/pebble-apps/sensors/compass-orientation.png) + +The best orientation to encourage users to adopt while using a compass-enabled +watchapp is with the top of the watch parallel to the ground. If the watch is +raised so that the screen is facing the user, the plane will now be +perpedicular to the screen, but still parallel to the ground. + + +## Angles and Degrees + +The magnetic heading value is presented as a number between 0 and +TRIG_MAX_ANGLE (65536). This range is used to give a higher level of +precision for drawing commands, which is preferable to using only 360 degrees. + +If you imagine an analogue clock face on your Pebble, the angle 0 is always at +the 12 o'clock position, and the magnetic heading angle is calculated in a +counter clockwise direction from 0. + +This can be confusing to grasp at first, as it’s opposite of how direction is +measured on a compass, but it's simple to convert the values into a clockwise +direction: + +```c +int clockwise_angle = TRIG_MAX_ANGLE - heading_data.magnetic_heading; +``` + +Once you have an angle relative to North, you can convert that to degrees using +the helper function `TRIGANGLE_TO_DEG()`: + +```c +int degrees = TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE - heading_data.magnetic_heading); +``` + + +## Subscribing to Compass Data + +Compass heading events can be received in a watchapp by subscribing to the +``CompassService``: + +```c +// Subscribe to compass heading updates +compass_service_subscribe(compass_heading_handler); +``` + +The provided ``CompassHeadingHandler`` function (called +`compass_heading_handler` above) can be used to read the state of the compass, +and the current heading if it is available. This value is given in the range of +`0` to ``TRIG_MAX_ANGLE`` to preserve precision, and so it can be converted +using the ``TRIGANGLE_TO_DEG()`` macro: + +```c +static void compass_heading_handler(CompassHeadingData heading_data) { + // Is the compass calibrated? + switch(heading_data.compass_status) { + case CompassStatusDataInvalid: + APP_LOG(APP_LOG_LEVEL_INFO, "Not yet calibrated."); + break; + case CompassStatusCalibrating: + APP_LOG(APP_LOG_LEVEL_INFO, "Calibration in progress. Heading is %ld", + TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE - heading_data.magnetic_heading)); + break; + case CompassStatusCalibrated: + APP_LOG(APP_LOG_LEVEL_INFO, "Calibrated! Heading is %ld", + TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE - heading_data.magnetic_heading)); + break; + } +} +``` + +By default, the callback will be triggered whenever the heading changes by one +degree. To reduce the frequency of updates, change the threshold for heading +changes by setting a heading filter: + +```c +// Only notify me when the heading has changed by more than 5 degrees. +compass_service_set_heading_filter(DEG_TO_TRIGANGLE(5)); +``` + + +## Unsubscribing From Compass Data + +When the app is done using the compass, stop receiving callbacks by +unsubscribing: + +```c +compass_service_unsubscribe(); +``` + + +## Peeking at Compass Data + +To fetch a compass heading without subscribing, simply peek to get a single +sample: + +```c +// Peek to get data +CompassHeadingData data; +compass_service_peek(&data); +``` + +> Similar to the subscription-provided data, the app should examine the peeked +> `CompassHeadingData` to determine if it is valid (i.e. the compass is +> calibrated). diff --git a/devsite/source/_guides/events-and-services/dictation.md b/devsite/source/_guides/events-and-services/dictation.md new file mode 100644 index 00000000..f01d2ecc --- /dev/null +++ b/devsite/source/_guides/events-and-services/dictation.md @@ -0,0 +1,216 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Dictation +description: | + How to use the Dictation API to get voice-to-text input in watchapps. +guide_group: events-and-services +order: 4 +platforms: + - basalt + - chalk + - diorite + - emery +related_docs: + - Dictation +related_examples: + - title: Simple Voice Demo + url: https://github.com/pebble-examples/simple-voice-demo + - title: Voice Quiz + url: https://github.com/pebble-examples/voice-quiz +--- + +On hardware [platforms](/faqs/#pebble-sdk) supporting a microphone, the +``Dictation`` API can be used to gather arbitrary text input from a user. +This approach is much faster than any previous button-based text input system +(such as [tertiary text](https://github.com/vgmoose/tertiary_text)), and +includes the ability to allow users to re-attempt dictation if there are any +errors in the returned transcription. + +> Note: Apps running on multiple hardware platforms that may or may not include +> a microphone should use the `PBL_MICROPHONE` compile-time define (as well as +> checking API return values) to gracefully handle when it is not available. + + +## How the Dictation API Works + +The ``Dictation`` API invokes the same UI that is shown to the user when +responding to notifications via the system menu, with events occuring in the +following order: + +* The user initiates transcription and the dictation UI is displayed. + +* The user dictates the phrase they would like converted into text. + +* The audio is transmitted via the Pebble phone application to a 3rd party + service and translated into text. + +* When the text is returned, the user is given the opportunity to review the + result of the transcription. At this time they may elect to re-attempt the + dictation by pressing the Back button and speaking clearer. + +* When the user is happy with the transcription, the text is provided to the + app by pressing the Select button. + +* If an error occurs in the transcription attempt, the user is automatically + allowed to re-attempt the dictation. + +* The user can retry their dictation by rejecting a successful transcription, + but only if confirmation dialogs are enabled. + + +## Beginning a Dictation Session + +To get voice input from a user, an app must first create a ``DictationSession`` +that contains data relating to the status of the dictation service, as well as +an allocated buffer to store the result of any transcriptions. This should be +declared in the file-global scope (as `static`), so it can be used at any time +(in button click handlers, for example). + +```c +static DictationSession *s_dictation_session; +``` + +A callback of type ``DictationSessionStatusCallback`` is also required to notify +the developer to the status of any dictation requests and transcription results. +This is called at any time the dictation UI exits, which can be for any of the +following reasons: + +* The user accepts a transcription result. + +* A transcription is successful but the confirmation dialog is disabled. + +* The user exits the dictation UI with the Back button. + +* When any error occurs and the error dialogs are disabled. + +* Too many transcription errors occur. + +```c +static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, + char *transcription, void *context) { + // Print the results of a transcription attempt + APP_LOG(APP_LOG_LEVEL_INFO, "Dictation status: %d", (int)status); +} +``` + +At the end of this callback the `transcription` pointer becomes invalid - if the +text is required later it should be copied into a separate buffer provided by +the app. The size of this dictation buffer is chosen by the developer, and +should be large enough to accept all expected input. Any transcribed text longer +than the length of the buffer will be truncated. + +```c +// Declare a buffer for the DictationSession +static char s_last_text[512]; +``` + +Finally, create the ``DictationSession`` and supply the size of the buffer and +the ``DictationSessionStatusCallback``. This session may be used as many times +as requires for multiple transcriptions. A context pointer may also optionally +be provided. + +```c +// Create new dictation session +s_dictation_session = dictation_session_create(sizeof(s_last_text), + dictation_session_callback, NULL); +``` + + +## Obtaining Dictated Text + +After creating a ``DictationSession``, the developer can begin a dictation +attempt at any time, providing that one is not already in progress. + +```c +// Start dictation UI +dictation_session_start(s_dictation_session); +``` + +The dictation UI will be displayed and the user will speak their desired input. + +![listening >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/pebble-apps/sensors/listening.png) + +It is recommended to provide visual guidance on the format of the expected input +before the ``dictation_session_start()`` is called. For example, if the user is +expected to speak a location that should be a city name, they should be briefed +as such before being asked to provide input. + +When the user exits the dictation UI, the developer's +``DictationSessionStatusCallback`` will be called. The `status` parameter +provided will inform the developer as to whether or not the transcription was +successful using a ``DictationSessionStatus`` value. It is useful to check this +value, as there are multiple reasons why a dictation request may not yield a +successful result. These values are described below under +[*DictationSessionStatus Values*](#dictationsessionstatus-values). + +If the value of `status` is equal to ``DictationSessionStatusSuccess``, the +transcription was successful. The user's input can be read from the +`transcription` parameter for evaluation and storage for later use if required. +Note that once the callback returns, `transcription` will no longer be valid. + +For example, a ``TextLayer`` in the app's UI with variable name `s_output_layer` +may be used to show the status of an attempted transcription: + +```c +if(status == DictationSessionStatusSuccess) { + // Display the dictated text + snprintf(s_last_text, sizeof(s_last_text), "Transcription:\n\n%s", transcription); + text_layer_set_text(s_output_layer, s_last_text); +} else { + // Display the reason for any error + static char s_failed_buff[128]; + snprintf(s_failed_buff, sizeof(s_failed_buff), "Transcription failed.\n\nReason:\n%d", + (int)status); + text_layer_set_text(s_output_layer, s_failed_buff); +} +``` + +The confirmation mechanism allowing review of the transcription result can be +disabled if it is not needed. An example of such a scenario may be to speed up a +'yes' or 'no' decision where the two expected inputs are distinct and different. + +```c +// Disable the confirmation screen +dictation_session_enable_confirmation(s_dictation_session, false); +``` + +It is also possible to disable the error dialogs, if so desired. This will +disable the dialogs that appear when a transcription attempt fails, as well as +disabling the ability to retry the dictation if a failure occurs. + +``` +// Disable error dialogs +dictation_session_enable_error_dialogs(s_dictation_session, false); +``` + + +### DictationSessionStatus Values + +These are the possible values provided by a ``DictationSessionStatusCallback``, +and should be used to handle transcription success or failure for any of the +following reasons. + +| Status | Value | Description | +|--------|-------|-------------| +| ``DictationSessionStatusSuccess`` | `0` | Transcription successful, with a valid result. | +| ``DictationSessionStatusFailureTranscriptionRejected`` | `1` | User rejected transcription and dismissed the dictation UI. | +| ``DictationSessionStatusFailureTranscriptionRejectedWithError`` | `2` | User exited the dictation UI after a transcription error. | +| ``DictationSessionStatusFailureSystemAborted`` | `3` | Too many errors occurred during transcription and the dictation UI exited. | +| ``DictationSessionStatusFailureNoSpeechDetected`` | `4` | No speech was detected and the dictation UI exited. | +| ``DictationSessionStatusFailureConnectivityError`` | `5` | No Bluetooth or Internet connection available. | +| ``DictationSessionStatusFailureDisabled`` | `6` | Voice transcription disabled for this user. This can occur if the user has disabled sending 'Usage logs' in the Pebble mobile app. | +| ``DictationSessionStatusFailureInternalError`` | `7` | Voice transcription failed due to an internal error. | +| ``DictationSessionStatusFailureRecognizerError`` | `8` | Cloud recognizer failed to transcribe speech (only possible if error dialogs are disabled). | diff --git a/devsite/source/_guides/events-and-services/events.md b/devsite/source/_guides/events-and-services/events.md new file mode 100644 index 00000000..965de255 --- /dev/null +++ b/devsite/source/_guides/events-and-services/events.md @@ -0,0 +1,332 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Event Services +description: | + How to use the various asynchronous event services to power app features. +guide_group: events-and-services +order: 5 +related_docs: + - TickTimerService + - ConnectionService + - AccelerometerService + - BatteryStateService + - HealthService + - AppFocusService + - CompassService +--- + +All Pebble apps are executed in three phases, which are summarized below: + +* Initialization - all code from the beginning of `main()` is run to set up all + the components of the app. + +* Event Loop - the app waits for and responds to any event services it has + subscribed to. + +* Deinitialization - when the app is exiting (i.e.: the user has pressed Back + from the last ``Window`` in the stack) ``app_event_loop()`` returns, and all + deinitialization code is run before the app exits. + +Once ``app_event_loop()`` is called, execution of `main()` pauses and all +further activities are performed when events from various ``Event Service`` +types occur. This continues until the app is exiting, and is typically handled +in the following pattern: + +```c +static void init() { + // Initialization code here +} + +static void deinit() { + // Deinitialization code here +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} +``` + + +## Types of Events + +There are multiple types of events an app can receive from various event +services. These are described in the table below, along with their handler +signature and a brief description of what they do: + +| Event Service | Handler(s) | Description | +|---------------|------------|-------------| +| ``TickTimerService`` | ``TickHandler`` | Most useful for watchfaces. Allows apps to be notified when a second, minute, hour, day, month or year ticks by. | +| ``ConnectionService`` | ``ConnectionHandler`` | Allows apps to know when the Bluetooth connection with the phone connects and disconnects. | +| ``AccelerometerService`` | ``AccelDataHandler``
``AccelTapHandler`` | Allows apps to receive raw data or tap events from the onboard accelerometer. | +| ``BatteryStateService`` | ``BatteryStateHandler`` | Allows apps to read the state of the battery, as well as whether the watch is plugged in and charging. | +| ``HealthService`` | ``HealthEventHandler`` | Allows apps to be notified to changes in various ``HealthMetric`` values as the user performs physical activities. | +| ``AppFocusService`` | ``AppFocusHandler`` | Allows apps to know when they are obscured by another window, such as when a notification modal appears. | +| ``CompassService`` | ``CompassHeadingHandler`` | Allows apps to read a compass heading, including calibration status of the sensor. | + +In addition, many other APIs also operate through the use of various callbacks +including ``MenuLayer``, ``AppMessage``, ``Timer``, and ``Wakeup``, but these +are not considered to be 'event services' in the same sense. + + +## Using Event Services + +The event services described in this guide are all used in the same manner - the +app subscribes an implementation of one or more handlers, and is notified by the +system when an event of that type occurs. In addition, most also include a +'peek' style API to read a single data item or status value on demand. This can +be useful to determine the initial service state when a watchapp starts. Apps +can subscribe to as many of these services as they require, and can also +unsubscribe at any time to stop receiving events. + +Each event service is briefly discussed below with multiple snippets - handler +implementation example, subscribing to the service, and any 'peek' API. + + +### Tick Timer Service + +The ``TickTimerService`` allows an app to be notified when different units of +time change. This is decided based upon the ``TimeUnits`` value specified when a +subscription is added. + +The [`struct tm`](http://www.cplusplus.com/reference/ctime/tm/) pointer provided +in the handler is a standard C object that contains many data fields describing +the current time. This can be used with +[`strftime()`](http://www.cplusplus.com/reference/ctime/strftime/) to obtain a +human-readable string. + +```c +static void tick_handler(struct tm *tick_time, TimeUnits changed) { + static char s_buffer[8]; + + // Read time into a string buffer + strftime(s_buffer, sizeof(s_buffer), "%H:%M", tick_time); + + APP_LOG(APP_LOG_LEVEL_INFO, "Time is now %s", s_buffer); +} +``` + +```c +// Get updates when the current minute changes +tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); +``` + +> The ``TickTimerService`` has no 'peek' API, but a similar effect can be +> achieved using the ``time()`` and ``localtime()`` APIs. + + +### Connection Service + +The ``ConnectionService`` uses a handler for each of two connection types: + +* `pebble_app_connection_handler` - the connection to the Pebble app on the + phone, analogous with the bluetooth connection state. + +* `pebblekit_connection_handler` - the connection to an iOS companion app, if + applicable. Will never occur on Android. + +Either one is optional, but at least one must be specified for a valid +subscription. + +```c +static void app_connection_handler(bool connected) { + APP_LOG(APP_LOG_LEVEL_INFO, "Pebble app %sconnected", connected ? "" : "dis"); +} + +static void kit_connection_handler(bool connected) { + APP_LOG(APP_LOG_LEVEL_INFO, "PebbleKit %sconnected", connected ? "" : "dis"); +} +``` + +```c +connection_service_subscribe((ConnectionHandlers) { + .pebble_app_connection_handler = app_connection_handler, + .pebblekit_connection_handler = kit_connection_handler +}); +``` + +```c +// Peek at either the Pebble app or PebbleKit connections +bool app_connection = connection_service_peek_pebble_app_connection(); +bool kit_connection = connection_service_peek_pebblekit_connection(); +``` + + +### Accelerometer Service + +The ``AccelerometerService`` can be used in two modes - tap events and raw data +events. ``AccelTapHandler`` and ``AccelDataHandler`` are used for each of these +respective use cases. See the +{% guide_link events-and-services/accelerometer %} guide for more +information. + +**Data Events** + +```c +static void accel_data_handler(AccelData *data, uint32_t num_samples) { + APP_LOG(APP_LOG_LEVEL_INFO, "Got %d new samples", (int)num_samples); +} +``` + +```c +const int num_samples = 10; + +// Subscribe to data events +accel_data_service_subscribe(num_samples, accel_data_handler); +``` + +```c +// Peek at the last reading +AccelData data; +accel_service_peek(&data); +``` + +**Tap Events** + +```c +static void accel_tap_handler(AccelAxisType axis, int32_t direction) { + APP_LOG(APP_LOG_LEVEL_INFO, "Tap event received"); +} +``` + +```c +// Subscribe to tap events +accel_tap_service_subscribe(accel_tap_handler); +``` + + +### Battery State Service + +The ``BatteryStateService`` allows apps to examine the state of the battery, and +whether or not is is plugged in and charging. + +```c +static void battery_state_handler(BatteryChargeState charge) { + // Report the current charge percentage + APP_LOG(APP_LOG_LEVEL_INFO, "Battery charge is %d%%", + (int)charge.charge_percent); +} +``` + +```c +// Get battery state updates +battery_state_service_subscribe(battery_state_handler); +``` + +```c +// Peek at the current battery state +BatteryChargeState state = battery_state_service_peek(); +``` + + +### Health Service + +The ``HealthService`` uses the ``HealthEventHandler`` to notify a subscribed app +when new data pertaining to a ``HealthMetric`` is available. See the +{% guide_link events-and-services/health %} guide for more information. + +```c +static void health_handler(HealthEventType event, void *context) { + if(event == HealthEventMovementUpdate) { + APP_LOG(APP_LOG_LEVEL_INFO, "New health movement event"); + } +} +``` + +```c +// Subscribe to health-related events +health_service_events_subscribe(health_handler, NULL); +``` + + +### App Focus Service + +The ``AppFocusService`` operates in two modes - basic and complete. + +**Basic Subscription** + +A basic subscription involves only one handler which will be fired when the app +is moved in or out of focus, and any animated transition has completed. + +```c +static void focus_handler(bool in_focus) { + APP_LOG(APP_LOG_LEVEL_INFO, "App is %s in focus", in_focus ? "now" : "not"); +} +``` + +```c +// Add a basic subscription +app_focus_service_subscribe(focus_handler); +``` + +**Complete Subscription** + +A complete subscription will notify the app with more detail about changes in +focus using two handlers in an ``AppFocusHandlers`` object: + +* `.will_focus` - represents a change in focus that is *about* to occur, such as + the start of a transition animation to or from a modal window. `will_focus` + will be `true` if the app will be in focus at the end of the transition. + +* `.did_focus` - represents the end of a transition. `did_focus` will be `true` + if the app is now completely in focus and the animation has finished. + +```c +void will_focus_handler(bool will_focus) { + APP_LOG(APP_LOG_LEVEL_INFO, "Will %s focus", will_focus ? "gain" : "lose"); +} + +void did_focus_handler(bool did_focus) { + APP_LOG(APP_LOG_LEVEL_INFO, "%s focus", did_focus ? "Gained" : "Lost"); +} +``` + +```c +// Subscribe to both types of events +app_focus_service_subscribe_handlers((AppFocusHandlers) { + .will_focus = will_focus_handler, + .did_focus = did_focus_handler +}); +``` + + +### Compass Service + +The ``CompassService`` provides access to regular updates about the watch's +magnetic compass heading, if it is calibrated. See the +{% guide_link events-and-services/compass %} guide for more information. + +```c +static void compass_heading_handler(CompassHeadingData heading_data) { + // Is the compass calibrated? + if(heading_data.compass_status == CompassStatusCalibrated) { + APP_LOG(APP_LOG_LEVEL_INFO, "Calibrated! Heading is %ld", + TRIGANGLE_TO_DEG(heading_data.magnetic_heading)); + } +} +``` + +```c +// Subscribe to compass heading updates +compass_service_subscribe(compass_heading_handler); +``` + +```c +// Peek the compass heading data +CompassHeadingData data; +compass_service_peek(&data); +``` diff --git a/devsite/source/_guides/events-and-services/health.md b/devsite/source/_guides/events-and-services/health.md new file mode 100644 index 00000000..8f88f0c7 --- /dev/null +++ b/devsite/source/_guides/events-and-services/health.md @@ -0,0 +1,469 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Health +description: | + Information on using the HealthService API to incorporate multiple types of + health data into your apps. +guide_group: events-and-services +order: 6 +platforms: + - basalt + - chalk + - diorite + - emery +related_docs: + - HealthService +related_examples: + - title: Simple Health Example + url: https://github.com/pebble-examples/simple-health-example +--- + +[Pebble Health](https://blog.getpebble.com/2015/12/15/health/) provides builtin +health data tracking to allow users to improve their activity and sleep habits. +With SDK 3.9, the ``HealthService`` API opens this data up to developers to +include and use within their apps. For example, a watchface could display a +brief summary of the user's activity for the day. + + +## API Availability + +In order to use the ``HealthService`` (and indeed Pebble Health), the user must +enable the 'Pebble Health' app in the 'Apps/Timeline' view of the official +Pebble mobile app. If this is not enabled health data will not be available to +apps, and API calls will return values to reflect this. + +In addition, any app using the ``HealthService`` API must declare the 'health' +capability in order to be accepted by the +[developer portal](https://dev-portal.getpebble.com/). This can be done in +CloudPebble 'Settings', or in `package.json` in the local SDK: + +```js +"capabilities": [ "health" ] +``` + +Since Pebble Health is not available on the Aplite platform, developers should +check the API return values and hence the lack of ``HealthService`` on that +platform gracefully. In addition, the `PBL_HEALTH` define and +`PBL_IF_HEALTH_ELSE()` macro can be used to selectively omit affected code. + + +## Available Metrics + +The ``HealthMetric`` `enum` lists the types of data (or 'metrics') that can be +read using the API. These are described below: + +| Metric | Description | +|--------|-------------| +| `HealthMetricStepCount` | The user's step count. | +| `HealthMetricActiveSeconds` | Duration of time the user was considered 'active'. | +| `HealthMetricWalkedDistanceMeters` | Estimation of the distance travelled in meters. | +| `HealthMetricSleepSeconds` | Duration of time the user was considered asleep. | +| `HealthMetricSleepRestfulSeconds` | Duration of time the user was considered in deep restful sleep. | +| `HealthMetricRestingKCalories` | The number of kcal (thousand calories) burned due to resting metabolism. | +| `HealthMetricActiveKCalories` | The number of kcal (thousand calories) burned due to activity. | +| `HealthMetricHeartRateBPM` | The heart rate, in beats per minute. | + + +## Subscribing to HealthService Events + +Like other Event Services, an app can subscribe a handler function to receive a +callback when new health data is available. This is useful for showing +near-realtime activity updates. The handler must be a suitable implementation of +``HealthEventHandler``. The `event` parameter describes the type of each update, +and is one of the following from the ``HealthEventType`` `enum`: + +| Event Type | Value | Description | +|------------|-------|-------------| +| `HealthEventSignificantUpdate` | `0` | All data is considered as outdated, apps should re-read all health data. This can happen on a change of the day or in other cases that significantly change the underlying data. | +| `HealthEventMovementUpdate` | `1` | Recent values around `HealthMetricStepCount`, `HealthMetricActiveSeconds`, `HealthMetricWalkedDistanceMeters`, and `HealthActivityMask` changed. | +| `HealthEventSleepUpdate` | `2` | Recent values around `HealthMetricSleepSeconds`, `HealthMetricSleepRestfulSeconds`, `HealthActivitySleep`, and `HealthActivityRestfulSleep` changed. | +| `HealthEventHeartRateUpdate` | `4` | The value of `HealthMetricHeartRateBPM` has changed. | + +A simple example handler is shown below, which outputs to app logs the type of +event that fired the callback: + +```c +static void health_handler(HealthEventType event, void *context) { + // Which type of event occurred? + switch(event) { + case HealthEventSignificantUpdate: + APP_LOG(APP_LOG_LEVEL_INFO, + "New HealthService HealthEventSignificantUpdate event"); + break; + case HealthEventMovementUpdate: + APP_LOG(APP_LOG_LEVEL_INFO, + "New HealthService HealthEventMovementUpdate event"); + break; + case HealthEventSleepUpdate: + APP_LOG(APP_LOG_LEVEL_INFO, + "New HealthService HealthEventSleepUpdate event"); + break; + case HealthEventHeartRateUpdate: + APP_LOG(APP_LOG_LEVEL_INFO, + "New HealthService HealthEventHeartRateUpdate event"); + break; + } +} +``` + +The subscription is then registered in the usual way, optionally providing a +`context` parameter that is relayed to each event callback. The return value +should be used to determine whether the subscription was successful: + +```c +#if defined(PBL_HEALTH) +// Attempt to subscribe +if(!health_service_events_subscribe(health_handler, NULL)) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!"); +} +#else +APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!"); +#endif +``` + + +## Reading Health Data + +Health data is collected in the background as part of Pebble Health regardless +of the state of the app using the ``HealthService`` API, and is available to +apps through various ``HealthService`` API functions. + +Before reading any health data, it is recommended to check that data is +available for the desired time range, if applicable. In addition to the +``HealthServiceAccessibilityMask`` value, health-related code can be +conditionally compiled using `PBL_HEALTH`. For example, to check whether +any data is available for a given time range: + +```c +#if defined(PBL_HEALTH) +// Use the step count metric +HealthMetric metric = HealthMetricStepCount; + +// Create timestamps for midnight (the start time) and now (the end time) +time_t start = time_start_of_today(); +time_t end = time(NULL); + +// Check step data is available +HealthServiceAccessibilityMask mask = health_service_metric_accessible(metric, + start, end); +bool any_data_available = mask & HealthServiceAccessibilityMaskAvailable; +#else +// Health data is not available here +bool any_data_available = false; +#endif +``` + +Most applications will want to read the sum of a metric for the current day's +activity. This is the simplest method for accessing summaries of users' health +data, and is shown in the example below: + +```c +HealthMetric metric = HealthMetricStepCount; +time_t start = time_start_of_today(); +time_t end = time(NULL); + +// Check the metric has data available for today +HealthServiceAccessibilityMask mask = health_service_metric_accessible(metric, + start, end); + +if(mask & HealthServiceAccessibilityMaskAvailable) { + // Data is available! + APP_LOG(APP_LOG_LEVEL_INFO, "Steps today: %d", + (int)health_service_sum_today(metric)); +} else { + // No data recorded yet today + APP_LOG(APP_LOG_LEVEL_ERROR, "Data unavailable!"); +} +``` + +For more specific data queries, the API also allows developers to request data +records and sums of metrics from a specific time range. If data is available, it +can be read as a sum of all values recorded between that time range. You can use +the convenience constants from ``Time``, such as ``SECONDS_PER_HOUR`` to adjust +a timestamp relative to the current moment returned by ``time()``. + +> Note: The value returned will be an average since midnight, weighted for the +> length of the specified time range. This may change in the future. + +An example of this process is shown below: + +```c +// Make a timestamp for now +time_t end = time(NULL); + +// Make a timestamp for the last hour's worth of data +time_t start = end - SECONDS_PER_HOUR; + +// Check data is available +HealthServiceAccessibilityMask result = + health_service_metric_accessible(HealthMetricStepCount, start, end); +if(result & HealthServiceAccessibilityMaskAvailable) { + // Data is available! Read it + HealthValue steps = health_service_sum(HealthMetricStepCount, start, end); + + APP_LOG(APP_LOG_LEVEL_INFO, "Steps in the last hour: %d", (int)steps); +} else { + APP_LOG(APP_LOG_LEVEL_ERROR, "No data available!"); +} +``` + + +## Representing Health Data + +Depending on the locale of the user, the conventional measurement system used to +represent distances may vary between metric and imperial. For this reason it is +recommended to query the user's preferred ``MeasurementSystem`` before +formatting distance data from the ``HealthService``: + +> Note: This API is currently only meaningful when querying the +> ``HealthMetricWalkedDistanceMeters`` metric. ``MeasurementSystemUnknown`` will +> be returned for all other queries. + +```c +const HealthMetric metric = HealthMetricWalkedDistanceMeters; +const HealthValue distance = health_service_sum_today(metric); + +// Get the preferred measurement system +MeasurementSystem system = health_service_get_measurement_system_for_display( + metric); + +// Format accordingly +static char s_buffer[32]; +switch(system) { + case MeasurementSystemMetric: + snprintf(s_buffer, sizeof(s_buffer), "Walked %d meters", (int)distance); + break; + case MeasurementSystemImperial: { + // Convert to imperial first + int feet = (int)((float)distance * 3.28F); + snprintf(s_buffer, sizeof(s_buffer), "Walked %d feet", (int)feet); + } break; + case MeasurementSystemUnknown: + default: + APP_LOG(APP_LOG_LEVEL_INFO, "MeasurementSystem unknown or does not apply"); +} + +// Display to user in correct units +text_layer_set_text(s_some_layer, s_buffer); +``` + + +## Obtaining Averaged Data + +The ``HealthService`` also allows developers to read average values of a +particular ``HealthMetric`` with varying degrees of scope. This is useful for +apps that wish to display an average value (e.g.: as a goal for the user) +alongside a summed value. + +In this context, the `start` and `end` parameters specify the time period to be +used for the daily average calculation. For example, a start time of midnight +and an end time ten hours later will return the average value for the specified +metric measured until 10 AM on average across the days specified by the scope. + +The ``HealthServiceTimeScope`` specified when querying for averaged data over a +given time range determines how the average is calculated, as detailed in the +table below: + +| Scope Type | Description | +|------------|-------------| +| `HealthServiceTimeScopeOnce` | No average computed. The result is the same as calling ``health_service_sum()``. | +| `HealthServiceTimeScopeWeekly` | Compute average using the same day from each week (up to four weeks). For example, every Monday if the provided time range falls on a Monday. | +| `HealthServiceTimeScopeDailyWeekdayOrWeekend` | Compute average using either weekdays (Monday to Friday) or weekends (Saturday and Sunday), depending on which day the provided time range falls. | +| `HealthServiceTimeScopeDaily` | Compute average across all days of the week. | + +> Note: If the difference between the start and end times is greater than one +> day, an average will be returned that takes both days into account. Similarly, +> if the time range crosses between scopes (such as including weekend days and +> weekdays with ``HealthServiceTimeScopeDailyWeekdayOrWeekend``), the start time +> will be used to determine which days are used. + +Reading averaged data values works in a similar way to reading sums. The example +below shows how to read an average step count across all days of the week for a +given time range: + +```c +// Define query parameters +const HealthMetric metric = HealthMetricStepCount; +const HealthServiceTimeScope scope = HealthServiceTimeScopeDaily; + +// Use the average daily value from midnight to the current time +const time_t start = time_start_of_today(); +const time_t end = time(NULL); + +// Check that an averaged value is accessible +HealthServiceAccessibilityMask mask = + health_service_metric_averaged_accessible(metric, start, end, scope); +if(mask & HealthServiceAccessibilityMaskAvailable) { + // Average is available, read it + HealthValue average = health_service_sum_averaged(metric, start, end, scope); + + APP_LOG(APP_LOG_LEVEL_INFO, "Average step count: %d steps", (int)average); +} +``` + + +## Detecting Activities + +It is possible to detect when the user is sleeping using a +``HealthActivityMask`` value. A useful application of this information could be +to disable a watchface's animations or tick at a reduced rate once the user is +asleep. This is done by checking certain bits of the returned value: + +```c +// Get an activities mask +HealthActivityMask activities = health_service_peek_current_activities(); + +// Determine which bits are set, and hence which activity is active +if(activities & HealthActivitySleep) { + APP_LOG(APP_LOG_LEVEL_INFO, "The user is sleeping."); +} else if(activities & HealthActivityRestfulSleep) { + APP_LOG(APP_LOG_LEVEL_INFO, "The user is sleeping peacefully."); +} else { + APP_LOG(APP_LOG_LEVEL_INFO, "The user is not currently sleeping."); +} +``` + + +## Read Per-Minute History + +The ``HealthMinuteData`` structure contains multiple types of activity-related +data that are recorded in a minute-by-minute fashion. This style of data access +is best suited to those applications requiring more granular detail (such as +creating a new fitness algorithm). Up to seven days worth of data is available +with this API. + +> See [*Notes on Minute-level Data*](#notes-on-minute-level-data) below for more +> information on minute-level data. + +The data items contained in the ``HealthMinuteData`` structure are summarized +below: + +| Item | Type | Description | +|------|------|-------------| +| `steps` | `uint8_t` | Number of steps taken in this minute. | +| `orientation` | `uint8_t` | Quantized average orientation, encoding the x-y plane (the "yaw") in the lower 4 bits (360 degrees linearly mapped to 1 of 16 values) and the z axis (the "pitch") in the upper 4 bits. | +| `vmc` | `uint16_t` | Vector Magnitude Counts (VMC). This is a measure of the total amount of movement seen by the watch. More vigorous movement yields higher VMC values. | +| `is_invalid` | `bool` | `true` if the item doesn't represent actual data, and should be ignored. | +| `heart_rate_bpm` | `uint8_t` | Heart rate in beats per minute (if available). | + +These data items can be obtained in the following manner, similar to obtaining a +sum. + +```c +// Create an array to store data +const uint32_t max_records = 60; +HealthMinuteData *minute_data = (HealthMinuteData*) + malloc(max_records * sizeof(HealthMinuteData)); + +// Make a timestamp for 15 minutes ago and an hour before that +time_t end = time(NULL) - (15 * SECONDS_PER_MINUTE); +time_t start = end - SECONDS_PER_HOUR; + +// Obtain the minute-by-minute records +uint32_t num_records = health_service_get_minute_history(minute_data, + max_records, &start, &end); +APP_LOG(APP_LOG_LEVEL_INFO, "num_records: %d", (int)num_records); + +// Print the number of steps for each minute +for(uint32_t i = 0; i < num_records; i++) { + APP_LOG(APP_LOG_LEVEL_INFO, "Item %d steps: %d", (int)i, + (int)minute_data[i].steps); +} +``` + +Don't forget to free the array once the data is finished with: + +```c +// Free the array +free(minute_data); +``` + +### Notes on Minute-level Data + +Missing minute-level records can occur if the watch is reset, goes into low +power (watch-only) mode due to critically low battery, or Pebble Health is +disabled during the time period requested. + +``health_service_get_minute_history()`` will return as many **consecutive** +minute-level records that are available after the provided `start` timestamp, +skipping any missing records until one is found. This API behavior enables one +to easily continue reading data after a previous query encountered a missing +minute. If there are some minutes with missing data, the API will return all +available records up to the last available minute, and no further. Conversely, +records returned will begin with the first available record after the provided +`start` timestamp, skipping any missing records until one is found. This can +be used to continue reading data after a previous query encountered a missing +minute. + +The code snippet below shows an example function that packs a provided +``HealthMinuteData`` array with all available values in a time range, up to an +arbitrary maximum number. Any missing minutes are collapsed, so that as much +data can be returned as is possible for the allocated array size and time range +requested. + +> This example shows querying up to 60 records. More can be obtained, but this +> increases the heap allocation required as well as the time taken to process +> the query. + +```c +static uint32_t get_available_records(HealthMinuteData *array, time_t query_start, + time_t query_end, uint32_t max_records) { + time_t next_start = query_start; + time_t next_end = query_end; + uint32_t num_records_found = 0; + + // Find more records until no more are returned + while (num_records_found < max_records) { + int ask_num_records = max_records - num_records_found; + uint32_t ret_val = health_service_get_minute_history(&array[num_records_found], + ask_num_records, &next_start, &next_end); + if (ret_val == 0) { + // a 0 return value means no more data is available + return num_records_found; + } + num_records_found += ret_val; + next_start = next_end; + next_end = query_end; + } + + return num_records_found; +} + +static void print_last_hours_steps() { + // Query for the last hour, max 60 minute-level records + // (except the last 15 minutes) + const time_t query_end = time(NULL) - (15 * SECONDS_PER_MINUTE); + const time_t query_start = query_end - SECONDS_PER_HOUR; + const uint32_t max_records = (query_end - query_start) / SECONDS_PER_MINUTE; + HealthMinuteData *data = + (HealthMinuteData*)malloc(max_records * sizeof(HealthMinuteData)); + + // Populate the array + max_records = get_available_records(data, query_start, query_end, max_records); + + // Print the results + for(uint32_t i = 0; i < max_records; i++) { + if(!data[i].is_invalid) { + APP_LOG(APP_LOG_LEVEL_INFO, "Record %d contains %d steps.", (int)i, + (int)data[i].steps); + } else { + APP_LOG(APP_LOG_LEVEL_INFO, "Record %d was not valid.", (int)i); + } + } + + free(data); +} +``` diff --git a/devsite/source/_guides/events-and-services/hrm.md b/devsite/source/_guides/events-and-services/hrm.md new file mode 100644 index 00000000..f27f0d90 --- /dev/null +++ b/devsite/source/_guides/events-and-services/hrm.md @@ -0,0 +1,253 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Heart Rate Monitor +description: | + Information on using the HealthService API to obtain information from the + Heart Rate Monitor. +guide_group: events-and-services +order: 6 +related_docs: + - HealthService +related_examples: + - title: HRM Watchface + url: https://github.com/pebble-examples/watchface-tutorial-hrm + - title: HRM Activity + url: https://github.com/pebble-examples/hrm-activity-example +platform_choice: true +--- + +The Pebble Time 2 and Pebble 2 (excluding SE model) +{% guide_link tools-and-resources/hardware-information "devices" %} include a +heart rate monitor. This guide will demonstrate how to use the ``HealthService`` +API to retrieve information about the user's current, and historical heart +rates. + +If you aren't already familiar with the ``HealthService``, we recommended that +you read the {% guide_link events-and-services/health "Health guide" %} +before proceeding. + +## Enable Health Data + +
+{% markdown {} %} +Before your application is able to access the heart rate information, you will +need to add `heath` to the `capabilities` array in your applications +`package.json` file. + +```js +{ + ... + "pebble": { + ... + "capabilities": [ "health" ], + ... + } +} +``` +{% endmarkdown %} +
+ +^CP^ Before your application is able to access heart rate information, you will +need to enable the `USES HEALTH` option in your project settings on +[CloudPebble]({{ site.data.links.cloudpebble }}). + + +## Data Quality + +Heart rate sensors aren't perfect, and their readings can be affected by +improper positioning, environmental factors and excessive movement. The raw data +from the HRM sensor contains a metric to indicate the quality of the readings it +receives. + +The HRM API provides a raw BPM reading (``HealthMetricHeartRateRawBPM``) and a +filtered reading (``HealthMetricHeartRateBPM``). This filtered value minimizes +the effect of hand movement and improper sensor placement, by removing the bad +quality readings. This filtered data makes it easy for developers to integrate +in their applications, without needing to filter the data themselves. + + +## Obtaining the Current Heart Rate + +To obtain the current heart rate, you should first check whether the +``HealthMetricHeartRateBPM`` is available by using the +``health_service_metric_accessible`` method. + +Then you can obtain the current heart rate using the +``health_service_peek_current_value`` method: + +```c +HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, time(NULL), time(NULL)); +if (hr & HealthServiceAccessibilityMaskAvailable) { + HealthValue val = health_service_peek_current_value(HealthMetricHeartRateBPM); + if(val > 0) { + // Display HRM value + static char s_hrm_buffer[8]; + snprintf(s_hrm_buffer, sizeof(s_hrm_buffer), "%lu BPM", (uint32_t)val); + text_layer_set_text(s_hrm_layer, s_hrm_buffer); + } +} +``` +> **Note** this value is averaged from readings taken over the past minute, but +due to the [sampling rate](#heart-rate-sample-periods) and our data filters, +this value could be several minutes old. Use `HealthMetricHeartRateRawBPM` for +the raw, unfiltered value. + + +## Subscribing to Heart Rate Updates + +The user's heart rate can also be monitored via an event subscription, in a +similar way to the other health metrics. If you wanted your watchface to update +the displayed heart rate every time the HRM takes a new reading, you could use +the ``health_service_events_subscribe`` method. + +```c + +static void prv_on_health_data(HealthEventType type, void *context) { + // If the update was from the Heart Rate Monitor, query it + if (type == HealthEventHeartRateUpdate) { + HealthValue value = health_service_peek_current_value(HealthMetricHeartRateBPM); + // Display the heart rate + } +} + +static void prv_init(void) { + // Subscribe to health event handler + health_service_events_subscribe(prv_on_health_data, NULL); + // ... +} +``` + +> **Note** The frequency of these updates does not directly correlate to the +> sensor sampling rate. + + +## Heart Rate Sample Periods + +The default sample period is 10 minutes, but the system automatically controls +the HRM sample rate based on the level of user activity. It increases the +sampling rate during intense activity and reduces it again during inactivity. +This aims to provide the optimal battery usage. + +### Battery Considerations + +Like all active sensors, accelerometer, backlight etc, the HRM sensor will have +a negative affect on battery life. It's important to consider this when using +the APIs within your application. + +By default the system will automatically control the heart rate sampling period +for the optimal balance between update frequency and battery usage. In addition, +the APIs have been designed to allow developers to retrieve values for the most +common use cases with minimal impact on battery life. + +### Altering the Sampling Period + +Developers can request a specific sampling rate using the +``health_service_set_heart_rate_sample_period`` method. The system will use this +value as a suggestion, but does not guarantee that value will be used. The +actual sampling period may be greater or less due to other apps that require +input from the sensor, or data quality issues. + +The shortest period you can currently specify is `1` second, and the longest +period you can specify is `600` seconds (10 minutes). + +In this example, we will sample the heart rate monitor every second: + +```c +// Set the heart rate monitor to sample every second +bool success = health_service_set_heart_rate_sample_period(1); +``` +> **Note** This does not mean that you can peek the current value every second, +> only that the sensor will capture more samples. + + +### Resetting the Sampling Period + +Developers **must** reset the heart rate sampling period when their application +exits. Failing to do so may result in the heart rate monitor continuing at the +increased rate for a period of time, even after your application closes. This +is fundamentally different to other Pebble sensors and was designed so that +applications which a reliant upon high sampling rates can be temporarily +interupted for notifications, or music, without affecting the sensor data. + +```c +// Reset the heart rate sampling period to automatic +health_service_set_heart_rate_sample_period(0); +``` + + +## Obtaining Historical Data + +If your application is using heart rate information, it may also want to obtain +historical data to compare it against. In this section we'll look at how you can +use the `health_service_aggregate` functions to obtain relevant historic data. + +Before requesting historic/aggregate data for a specific time period, you +should ensure that it is available using the +``health_service_metric_accessible`` method. + +Then we'll use the ``health_service_aggregate_averaged`` method to +obtain the average daily heart rate over the last 7 days. + +```c +// Obtain history for last 7 days +time_t end_time = time(NULL); +time_t start_time = end_time - (7 * SECONDS_PER_DAY); + +HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, start_time, end_time); +if (hr & HealthServiceAccessibilityMaskAvailable) { + uint32_t weekly_avg_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM, + start_time, end_time, + HealthAggregationAvg, HealthServiceTimeScopeDaily); +} +``` + +You can also query the average `min` and `max` heart rates, but only within the +past two hours (maximum). This limitation is due to very limited storage +capacity on the device, but the implementation may change in the future. + +```c +// Obtain history for last 1 hour +time_t end_time = time(NULL); +time_t start_time = end_time - SECONDS_PER_HOUR; + +HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, start_time, end_time); +if (hr & HealthServiceAccessibilityMaskAvailable) { + uint32_t min_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM, + start_time, end_time, + HealthAggregationMin, HealthServiceTimeScopeOnce); + uint32_t max_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM, + start_time, end_time, + HealthAggregationMax, HealthServiceTimeScopeOnce); +} +``` + +## Read Per-Minute History + +The ``HealthMinuteData`` structure contains multiple types of activity-related +data that are recorded in a minute-by-minute fashion. Although this structure +now contains HRM readings, it does not contain information about the quality of +those readings. + +> **Note** Please refer to the +> {% guide_link events-and-services/health#read-per-minute-history "Health Guide" %} +> for futher information. + + +## Next Steps + +This guide covered the basics of how to interact with realtime and historic +heart information. We encourage you to further explore the ``HealthService`` +documentation, and integrate it into your next project. diff --git a/devsite/source/_guides/events-and-services/index.md b/devsite/source/_guides/events-and-services/index.md new file mode 100644 index 00000000..fcdc2f96 --- /dev/null +++ b/devsite/source/_guides/events-and-services/index.md @@ -0,0 +1,44 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Events and Services +description: | + How to get data from the onboard sensors including the accelerometer, compass, + and microphone. +guide_group: events-and-services +menu: false +permalink: /guides/events-and-services/ +generate_toc: false +hide_comments: true +--- + +All Pebble watches contain a collection of sensors than can be used as input +devices for apps. Available sensors include four buttons, an accelerometer, and +a magnetometer (accessible via the ``CompassService`` API). In addition, the +Basalt and Chalk platforms also include a microphone (accessible via the +``Dictation`` API) and access to Pebble Health data sets. Read +{% guide_link tools-and-resources/hardware-information %} for more information +on sensor availability per platform. + +While providing more interactivity, excessive regular use of these sensors will +stop the watch's CPU from sleeping and result in faster battery drain, so use +them sparingly. An alternative to constantly reading accelerometer data is to +obtain data in batches, allowing sleeping periods in between. Read +{% guide_link best-practices/conserving-battery-life %} for more information. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/events-and-services/persistent-storage.md b/devsite/source/_guides/events-and-services/persistent-storage.md new file mode 100644 index 00000000..5b21b325 --- /dev/null +++ b/devsite/source/_guides/events-and-services/persistent-storage.md @@ -0,0 +1,240 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Persistent Storage +description: | + Using persistent storage to improve your app's UX. +guide_group: events-and-services +order: 7 +related_docs: + - Storage +--- + +Developers can use the ``Storage`` API to persist multiple types of data between +app launches, enabling apps to remember information previously entered by the +user. A common use-case of this API is to enable the app to remember +configuration data chosen in an app's configuration page, removing the +tedious need to enter the information on the phone every time the watchapp is +launched. Other use cases include to-to lists, stat trackers, game highscores +etc. + +Read {% guide_link user-interfaces/app-configuration %} for more information on +implementing an app configuration page. + + +## Persistent Storage Model + +Every app is allocated 4 kB of persistent storage space and can write values to +storage using a key, similar to ``AppMessage`` dictionaries or the web +`localStorage` API. To recall values, the app simply queries the API using the +associated key . Keys are specified in the `uint32_t` type, and each value can +have a size up to ``PERSIST_DATA_MAX_LENGTH`` (currently 256 bytes). + +When an app is updated the values saved using the ``Storage`` API will be +persisted, but if it is uninstalled they will be removed. + +Apps that make large use of the ``Storage`` API may experience small pauses due +to underlying housekeeping operations. Therefore it is recommended to read and +write values when an app is launching or exiting, or during a time the user is +waiting for some other action to complete. + + +## Types of Data + +Values can be stored as boolean, integer, string, or arbitrary data structure +types. Before retrieving a value, the app should check that it has been +previously persisted. If it has not, a default value should be chosen as +appropriate. + +```c +uint32_t key = 0; +int num_items = 0; + +if (persist_exists(key)) { + // Read persisted value + num_items = persist_read_int(key); +} else { + // Choose a default value + num_items = 10; + + // Remember the default value until the user chooses their own value + persist_write_int(key, num_items); +} +``` + +The API provides a 'read' and 'write' function for each of these types, +with builtin data types retrieved through assignment, and complex ones into a +buffer provided by the app. Examples of each are shown below. + + +### Booleans + +```c +uint32_t key = 0; +bool large_font_size = true; +``` + +```c +// Write a boolean value +persist_write_bool(key, large_font_size); +``` + +```c +// Read the boolean value +bool large_font_size = persist_read_bool(key); +``` + + +### Integers + +```c +uint32_t key = 1; +int highscore = 432; +``` + +```c +// Write an integer +persist_write_int(key, highscore); +``` + +```c +// Read the integer value +int highscore = persist_read_int(key); +``` + + +### Strings + +```c +uint32_t key = 2; +char *string = "Remember this!"; +``` + +```c +// Write the string +persist_write_string(key, string); +``` + +```c +// Read the string +char buffer[32]; +persist_read_string(key, buffer, sizeof(buffer)); +``` + + +### Data Structures + +```c +typedef struct { + int a; + int b; +} Data; + +uint32_t key = 3; +Data data = (Data) { + .a = 32, + .b = 45 +}; +``` + +```c +// Write the data structure +persist_write_data(key, &data, sizeof(Data)); +``` + +```c +// Read the data structure +persist_read_data(key, &data, sizeof(Data)); +``` + +> Note: If a persisted data structure's field layout changes between app +> versions, the data read may no longer be compatible (see below). + + +## Versioning Persisted Data + +As already mentioned, automatic app updates will persist data between app +versions. However, if the format of persisted data changes in a new app version +(or keys change), developers should version their storage scheme and correctly +handle version changes appropriately. + +One way to do this is to use an extra persisted integer as the storage scheme's +version number. If the scheme changes, simply update the version number and +migrate existing data as required. If old data cannot be migrated it should be +deleted and replaced with fresh data in the correct scheme from the user. An +example is shown below: + +```c +const uint32_t storage_version_key = 786; +const int current_storage_version = 2; +``` + +```c +// Store the current storage scheme version number +persist_write_int(storage_version_key, current_storage_version); +``` + +In this example, data stored in a key of `12` is now stored in a key of `13` due +to a new key being inserted higher up the list of key values. + +```c +// The scheme has changed, increment the version number +const int current_storage_version = 3; +``` + +```c +static void migrate_storage_data() { + // Check the last storage scheme version the app used + int last_storage_version = persist_read_int(storage_version_key); + + if (last_storage_version == current_storage_version) { + // No migration necessary + return; + } + + // Migrate data + switch(last_storage_version) { + case 0: + // ... + break; + case 1: + // ... + break; + case 2: { + uint32_t old_highscore_key = 12; + uint32_t new_highscore_key = 13; + + // Migrate to scheme version 3 + int highscore = persist_read_int(old_highscore_key); + persist_write_int(new_highscore_key, highscore); + + // Delete old data + persist_delete(old_highscore_key); + break; + } + + // Migration is complete, store the current storage scheme version number + persist_write_int(storage_version_key, current_storage_version); +} +``` + + +## Alternative Method + +In addition to the ``Storage`` API, data can also be persisted using the +`localStorage` API in PebbleKit JS, and communicated with the watch over +``AppMessage`` when the app is lanched. However, this method uses more power and +fails if the watch is not connected to the phone. + diff --git a/devsite/source/_guides/events-and-services/wakeups.md b/devsite/source/_guides/events-and-services/wakeups.md new file mode 100644 index 00000000..56cb37df --- /dev/null +++ b/devsite/source/_guides/events-and-services/wakeups.md @@ -0,0 +1,206 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Wakeups +description: | + Using the Wakeup API to launch an app at some future time. +guide_group: events-and-services +order: 8 +related_examples: + - title: Tea Timer + url: https://github.com/pebble-examples/feature-app-wakeup +related_docs: + - Wakeup + - AppLaunchReason + - launch_reason + - Timer +--- + +The ``Wakeup`` API allows developers to schedule an app launch in the future, +even if the app itself is closed in the meantime. A wakeup event is scheduled in +a similar manner to a ``Timer`` with a future timestamp calculated beforehand. + + +## Calculating Timestamps + +To schedule a wakeup event, first determine the timestamp of the desired wakeup +time as a `time_t` variable. Most uses of the ``Wakeup`` API will fall into +three distinct scenarios discussed below. + + +### A Future Time + +Call ``time()`` and add the offset, measured in seconds. For example, for 30 +minutes in the future: + +```c +// 30 minutes from now +time_t timestamp = time(NULL) + (30 * SECONDS_PER_MINUTE); +``` + + +### A Specific Time + +Use ``clock_to_timestamp()`` to obtain a `time_t` timestamp by specifying a day +of the week and hours and minutes (in 24 hour format). For example, for the next +occuring Monday at 5 PM: + +```c +// Next occuring monday at 17:00 +time_t timestamp = clock_to_timestamp(MONDAY, 17, 0); +``` + + +### Using a Timestamp Provided by a Web Service + +The timestamp will need to be translated using the +[`getTimezoneOffset()`](http://www.w3schools.com/jsref/jsref_gettimezoneoffset.asp) +method available in PebbleKit JS or with any timezone information given by the +web service. + + +## Scheduling a Wakeup + +Once a `time_t` timestamp has been calculated, the wakeup event can be +scheduled: + +```c +// Let the timestamp be 30 minutes from now +const time_t future_timestamp = time() + (30 * SECONDS_PER_MINUTE); + +// Choose a 'cookie' value representing the reason for the wakeup +const int cookie = 0; + +// If true, the user will be notified if they missed the wakeup +// (i.e. their watch was off) +const bool notify_if_missed = true; + +// Schedule wakeup event +WakeupId id = wakeup_schedule(future_timestamp, cookie, notify_if_missed); + +// Check the scheduling was successful +if(id >= 0) { + // Persist the ID so that a future launch can query it + const wakeup_id_key = 43; + persist_write_int(wakeup_id_key, id); +} +``` + +After scheduling a wakeup event it is possible to perform some interaction with +it. For example, reading the timestamp for when the event will occur using the +``WakeupId`` with ``wakeup_query()``, and then perform simple arithmetic to get +the time remaining: + +```c +// This will be set by wakeup_query() +time_t wakeup_timestamp = 0; + +// Is the wakeup still scheduled? +if(wakeup_query(id, &wakeup_timestamp)) { + // Get the time remaining + int seconds_remaining = wakeup_timestamp - time(NULL); + APP_LOG(APP_LOG_LEVEL_INFO, "%d seconds until wakeup", seconds_remaining); +} +``` + +To cancel a scheduled event, use the ``WakeupId`` obtained when it was +scheduled: + +```c +// Cancel a wakeup +wakeup_cancel(id); +``` + +To cancel all scheduled wakeup events: + +```c +// Cancel all wakeups +wakeup_cancel_all(); +``` + + +## Limitations + +There are three limitations that should be taken into account when using +the Wakeup API: + +* There can be no more than 8 scheduled wakeup events per app at any one time. + +* Wakeup events cannot be scheduled within 30 seconds of the current time. + +* Wakeup events are given a one minute window either side of the wakeup time. In + this time no app may schedule an event. The return ``StatusCode`` of + ``wakeup_schedule()`` should be checked to determine whether the scheduling of + the new event should be reattempted. A negative value indicates that the + wakeup could not be scheduled successfully. + +The possible ``StatusCode`` values are detailed below: + +|StatusCode|Value|Description| +|----------|-----|-----------| +| `E_RANGE` | `-8` | The wakeup event cannot be scheduled due to another event in that period. | +| `E_INVALID_ARGUMENT` | `-4` | The time requested is in the past. | +| `E_OUT_OF_RESOURCES` | `-7` | The application has already scheduled all 8 wakeup events. | +| `E_INTERNAL` | `-3` | A system error occurred during scheduling. | + + +## Handling Wakeup Events + +A wakeup event can occur at two different times - when the app is closed, and +when it is already launched and in the foreground. + +If the app is launched due to a previously scheduled wakeup event, check the +``AppLaunchReason`` and load the app accordingly: + +```c +static void init() { + if(launch_reason() == APP_LAUNCH_WAKEUP) { + // The app was started by a wakeup event. + WakeupId id = 0; + int32_t reason = 0; + + // Get details and handle the event appropriately + wakeup_get_launch_event(&id, &reason); + } + + /* other init code */ + +} +``` + +If the app is expecting a wakeup to occur while it is open, use a subscription +to the wakeup service to be notified of such events: + +```c +static void wakeup_handler(WakeupId id, int32_t reason) { + // A wakeup event has occurred while the app was already open +} +``` + +```c +// Subscribe to wakeup service +wakeup_service_subscribe(wakeup_handler); +``` + +The two approaches can also be combined for a unified response to being woken +up, not depenent on the state of the app: + +```c +// Get details of the wakeup +wakeup_get_launch_event(&id, &reason); + +// Manually handle using the handler +wakeup_handler(id, reason); +``` diff --git a/devsite/source/_guides/graphics-and-animations/animations.md b/devsite/source/_guides/graphics-and-animations/animations.md new file mode 100644 index 00000000..ecbb446a --- /dev/null +++ b/devsite/source/_guides/graphics-and-animations/animations.md @@ -0,0 +1,451 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Animations +description: | + How to use Animations and Timers to add life to your app. +guide_group: graphics-and-animations +order: 0 +related_docs: + - Animation + - Timer + - AnimationImplementation +related_examples: + - title: Composite Animations Example + url: https://github.com/pebble-examples/composite-animations-example + - title: Feature Property Animation + url: https://github.com/pebble-examples/feature-property-animation +--- + +The ``Animation`` API allows a variety of different types of value to be +smoothly animated from an initial value to a new value over time. Animations can +also use built-in easing curves to affect how the transition behaves. + + +## Using PropertyAnimations + +The most common use of animations is to move a ``Layer`` (or similar) around the +display. For example, to show or hide some information or animate the time +changing in a watchface. + +The simplest method of animating a ``Layer`` (such as a ``TextLayer``) is to use +a ``PropertyAnimation``, which animates a property of the target object. In this +example, the target is the frame property, which is a ``GRect`` To animate the +this property, ``property_animation_create_layer_frame()`` is used, which is a +convenience ``PropertyAnimation`` implementation provided by the SDK. + +```c +static Layer *s_layer; +``` + +Create the Layer during ``Window`` initialization: + +```c +// Create the Layer +s_layer = layer_create(some_bounds); +``` + +Determine the start and end values of the ``Layer``'s frame. These are the +'from' and 'to' locations and sizes of the ``Layer`` before and after the +animation takes place: + +```c +// The start and end frames - move the Layer 40 pixels to the right +GRect start = GRect(10, 10, 20, 20); +GRect finish = GRect(50, 10, 20, 20); +``` + +At the appropriate time, create a ``PropertyAnimation`` to animate the +``Layer``, specifying the `start` and `finish` values as parameters: + +```c +// Animate the Layer +PropertyAnimation *prop_anim = property_animation_create_layer_frame(s_layer, + &start, &finish); +``` + +Configure the attributes of the ``Animation``, such as the delay before +starting, and total duration (in milliseconds): + +```c +// Get the Animation +Animation *anim = property_animation_get_animation(prop_anim); + +// Choose parameters +const int delay_ms = 1000; +const int duration_ms = 500; + +// Configure the Animation's curve, delay, and duration +animation_set_curve(anim, AnimationCurveEaseOut); +animation_set_delay(anim, delay_ms); +animation_set_duration(anim, duration_ms); +``` + +Finally, schedule the ``Animation`` to play at the next possible opportunity +(usually immediately): + +```c +// Play the animation +animation_schedule(anim); +``` + +If the app requires knowledge of the start and end times of an ``Animation``, it +is possible to register ``AnimationHandlers`` to be notified of these events. +The handlers should be created with the signature of these examples shown below: + +```c +static void anim_started_handler(Animation *animation, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation started!"); +} + +static void anim_stopped_handler(Animation *animation, bool finished, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation stopped!"); +} +``` + +Register the handlers with an optional third context parameter **before** +scheduling the ``Animation``: + +```c +// Set some handlers +animation_set_handlers(anim, (AnimationHandlers) { + .started = anim_started_handler, + .stopped = anim_stopped_handler +}, NULL); +``` + +With the handlers registered, the start and end times of the ``Animation`` can +be detected by the app and used as appropriate. + + +### Other Types of PropertyAnimation + +In addition to ``property_animation_create_layer_frame()``, it is also possible +to animate the origin of a ``Layer``'s bounds using +``property_animation_create_bounds_origin()``. Animation of more types of data +can be achieved using custom implementations and one the following provided +update implementations and the associated +[getters and setters](``property_animation_update_int16``): + +* ``property_animation_update_int16`` - Animate an `int16`. +* ``property_animation_update_uint32`` - Animate a `uint32`. +* ``property_animation_update_gpoint`` - Animate a ``GPoint``. +* ``property_animation_update_grect`` - Animate a ``GRect`` +* ``property_animation_update_gcolor8`` - Animate a ``GColor8``. + + +## Custom Animation Implementations + +Beyond the convenience functions provided by the SDK, apps can implement their +own ``Animation`` by using custom callbacks for each stage of the animation +playback process. A ``PropertyAnimation`` is an example of such an +implementation. + +The callbacks to implement are the `.setup`, `.update`, and `.teardown` members +of an ``AnimationImplementation`` object. Some example implementations are shown +below. It is in the `.update` callback where the value of `progress` can be used +to modify the custom target of the animation. For example, some percentage of +completion: + +```c +static void implementation_setup(Animation *animation) { + APP_LOG(APP_LOG_LEVEL_INFO, "Animation started!"); +} + +static void implementation_update(Animation *animation, + const AnimationProgress progress) { + // Animate some completion variable + s_animation_percent = ((int)progress * 100) / ANIMATION_NORMALIZED_MAX; + + APP_LOG(APP_LOG_LEVEL_INFO, "Animation progress: %d%%", s_animation_percent); +} + +static void implementation_teardown(Animation *animation) { + APP_LOG(APP_LOG_LEVEL_INFO, "Animation finished!"); +} +``` + +Once these are in place, create a new ``Animation`` , specifying the new custom +implementation as a `const` object pointer at the appropriate time: + +```c +// Create a new Animation +Animation *animation = animation_create(); +animation_set_delay(animation, 1000); +animation_set_duration(animation, 1000); + +// Create the AnimationImplementation +const AnimationImplementation implementation = { + .setup = implementation_setup, + .update = implementation_update, + .teardown = implementation_teardown +}; +animation_set_implementation(animation, &implementation); + +// Play the Animation +animation_schedule(animation); +``` + +The output of the example above will look like the snippet shown below (edited +for brevity). Note the effect of the easing ``AnimationCurve`` on the progress +value: + +```nc|text +[13:42:33] main.c:11> Animation started! +[13:42:34] main.c:19> Animation progress: 0% +[13:42:34] main.c:19> Animation progress: 0% +[13:42:34] main.c:19> Animation progress: 0% +[13:42:34] main.c:19> Animation progress: 2% +[13:42:34] main.c:19> Animation progress: 3% +[13:42:34] main.c:19> Animation progress: 5% +[13:42:34] main.c:19> Animation progress: 7% +[13:42:34] main.c:19> Animation progress: 10% +[13:42:34] main.c:19> Animation progress: 14% +[13:42:35] main.c:19> Animation progress: 17% +[13:42:35] main.c:19> Animation progress: 21% +[13:42:35] main.c:19> Animation progress: 26% + +... + +[13:42:35] main.c:19> Animation progress: 85% +[13:42:35] main.c:19> Animation progress: 88% +[13:42:35] main.c:19> Animation progress: 91% +[13:42:35] main.c:19> Animation progress: 93% +[13:42:35] main.c:19> Animation progress: 95% +[13:42:35] main.c:19> Animation progress: 97% +[13:42:35] main.c:19> Animation progress: 98% +[13:42:35] main.c:19> Animation progress: 99% +[13:42:35] main.c:19> Animation progress: 99% +[13:42:35] main.c:19> Animation progress: 100% +[13:42:35] main.c:23> Animation finished! +``` + + +## Timers + +[`AppTimer`](``Timer``) objects can be used to schedule updates to variables and +objects at a later time. They can be used to implement frame-by-frame animations +as an alternative to using the ``Animation`` API. They can also be used in a +more general way to schedule events to occur at some point in the future (such +as UI updates) while the app is open. + +A thread-blocking alternative for small pauses is ``psleep()``, but this is +**not** recommended for use in loops updating UI (such as a counter), or for +scheduling ``AppMessage`` messages, which rely on the event loop to do their +work. + +> Note: To create timed events in the future that persist after an app is +> closed, check out the ``Wakeup`` API. + +When a timer elapses, it will call a developer-defined ``AppTimerCallback``. +This is where the code to be executed after the timed interval should be placed. +The callback will only be called once, so use this opportunity to re-register +the timer if it should repeat. + +```c +static void timer_callback(void *context) { + APP_LOG(APP_LOG_LEVEL_INFO, "Timer elapsed!"); +} +``` + +Schedule the timer with a specific `delay` interval, the name of the callback to +fire, and an optional context pointer: + +```c +const int delay_ms = 5000; + +// Schedule the timer +app_timer_register(delay_ms, timer_callback, NULL); +``` + +If the timer may need to be cancelled or rescheduled at a later time, ensure a +reference to it is kept for later use: + +```c +static AppTimer *s_timer; +``` + +```c +// Register the timer, and keep a handle to it +s_timer = app_timer_register(delay_ms, timer_callback, NULL); +``` + +If the timer needs to be cancelled, use the previous reference. If it has +already elapsed, nothing will occur: + +```c +// Cancel the timer +app_timer_cancel(s_timer); +``` + + +## Sequence and Spawn Animations + +The Pebble SDK also includes the capability to build up composite animations +built from other ``Animation`` objects. There are two types: a +sequence animation and a spawn animation. + +* A sequence animation is a set of two or more other animations that are played + out in **series** (one after another). For example, a pair of timed animations + to show and hide a ``Layer``. + +* A spawn animation is a set of two or more other animations that are played out + in **parallel**. A spawn animation acts the same as creating and starting two + or more animations at the same time, but has the advantage that it can be + included as part of a sequence animation. + +> Note: Composite animations can be composed of other composite animations. + + +### Important Considerations + +When incorporating an ``Animation`` into a sequence or spawn animation, there +are a couple of points to note: + +* Any single animation cannot appear more than once in the list of animations + used to create a more complex animation. + +* A composite animation assumes ownership of its component animations once it + has been created. + +* Once an animation has been added to a composite animation, it becomes + immutable. This means it can only be read, and not written to. Attempts to + modify such an animation after it has been added to a composite animation will + fail. + +* Once an animation has been added to a composite animation, it cannot then be + used to build a different composite animation. + + +### Creating a Sequence Animation + +To create a sequence animation, first create the component ``Animation`` objects +that will be used to build it. + +```c +// Create the first Animation +PropertyAnimation *prop_anim = property_animation_create_layer_frame(s_layer, + &start, &finish); +Animation *animation_a = property_animation_get_animation(prop_anim); + +// Set some properties +animation_set_delay(animation_a, 1000); +animation_set_duration(animation_a, 500); + +// Clone the first, modify the duration and reverse it. +Animation *animation_b = animation_clone(animation_a); +animation_set_reverse(animation_b, true); +animation_set_duration(animation_b, 1000); +``` + +Use these component animations to create the sequence animation. You can either +specify the components as a list or pass an array. Both approaches are shown +below. + + +#### Using a List + +You can specify up to 20 ``Animation`` objects as parameters to +`animation_sequence_create()`. The list must always be terminated with `NULL` to +mark the end. + +```c +// Create the sequence +Animation *sequence = animation_sequence_create(animation_a, animation_b, NULL); + +// Play the sequence +animation_schedule(sequence); +``` + + +#### Using an Array + +You can also specify the component animations using a dynamically allocated +array. Give this to `animation_sequence_create_from_array()` along with the size +of the array. + +```c +const uint32_t array_length = 2; + +// Create the array +Animation **arr = (Animation**)malloc(array_length * sizeof(Animation*)); +arr[0] = animation_a; +arr[1] = animation_b; + +// Create the sequence, set to loop forever +Animation *sequence = animation_sequence_create_from_array(arr, array_length); +animation_set_play_count(sequence, ANIMATION_DURATION_INFINITE); + +// Play the sequence +animation_schedule(sequence); + +// Destroy the array +free(arr); +``` + + +### Creating a Spawn Animation + +Creating a spawn animation is done in a very similiar way to a sequence +animation. The animation is built up from component animations which are then +all started at the same time. This simplifies the task of precisely timing +animations that are designed to coincide. + +The first step is the same as for sequence animations, which is to create a +number of component animations to be spawned together. + +```c +// Create the first animation +Animation *animation_a = animation_create(); +animation_set_duration(animation_a, 1000); + +// Clone the first, modify the duration and reverse it. +Animation *animation_b = animation_clone(animation_a); +animation_set_reverse(animation_b, true); +animation_set_duration(animation_b, 300); +``` + +Next, the spawn animation is created in a similar manner to the sequence +animation with a `NULL` terminated list of parameters: + +```c +// Create the spawn animation +Animation *spawn = animation_spawn_create(animation_a, animation_b, NULL); + +// Play the animation +animation_schedule(spawn); +``` + +Alternatively the spawn animation can be created with an array of ``Animation`` +objects. + +```c +const uint32_t array_length = 2; + +// Create the array +Animation **arr = (Animation**)malloc(array_length * sizeof(Animation*)); +arr[0] = animation_a; +arr[1] = animation_b; + +// Create the sequence and set the play count to 3 +Animation *spawn = animation_spawn_create_from_array(arr, array_length); +animation_set_play_count(spawn, 3); + +// Play the spawn animation +animation_schedule(spawn); + +// Destroy the array +free(arr); +``` diff --git a/devsite/source/_guides/graphics-and-animations/drawing-primitives-images-and-text.md b/devsite/source/_guides/graphics-and-animations/drawing-primitives-images-and-text.md new file mode 100644 index 00000000..7d54d971 --- /dev/null +++ b/devsite/source/_guides/graphics-and-animations/drawing-primitives-images-and-text.md @@ -0,0 +1,277 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Drawing Primitives, Images and Text +description: | + How to draw primitive shapes, image, and text onto the Graphics Context. +guide_group: graphics-and-animations +order: 1 +--- + +While ``Layer`` types such as ``TextLayer`` and ``BitmapLayer`` allow easy +rendering of text and bitmaps, more precise drawing can be achieved through the +use of the ``Graphics Context`` APIs. Custom drawing of primitive shapes such as +line, rectangles, and circles is also supported. Clever use of these functions +can remove the need to pre-prepare bitmap images for many UI elements and icons. + + +## Obtaining a Drawing Context + +All custom drawing requires a ``GContext`` instance. These cannot be created, +and are only available inside a ``LayerUpdateProc``. This update procedure is +simply a function that is called when a ``Layer`` is to be rendered, and is +defined by the developer as opposed to the system. For example, a +``BitmapLayer`` is simply a ``Layer`` with a ``LayerUpdateProc`` abstracted away +for convenience by the SDK. + +First, create the ``Layer`` that will have a custom drawing procedure: + +```c +static Layer *s_canvas_layer; +``` + +Allocate the ``Layer`` during ``Window`` creation: + +```c +GRect bounds = layer_get_bounds(window_get_root_layer(window)); + +// Create canvas layer +s_canvas_layer = layer_create(bounds); +``` + +Next, define the ``LayerUpdateProc`` according to the function specification: + +```c +static void canvas_update_proc(Layer *layer, GContext *ctx) { + // Custom drawing happens here! + +} +``` + +Assign this procedure to the canvas layer and add it to the ``Window`` to make +it visible: + +```c +// Assign the custom drawing procedure +layer_set_update_proc(s_canvas_layer, canvas_update_proc); + +// Add to Window +layer_add_child(window_get_root_layer(window), s_canvas_layer); +``` + +From now on, every time the ``Layer`` needs to be redrawn (for example, if other +layer geometry changes), the ``LayerUpdateProc`` will be called to allow the +developer to draw it. It can also be explicitly marked for redrawing at the next +opportunity: + +```c +// Redraw this as soon as possible +layer_mark_dirty(s_canvas_layer); +``` + + +## Drawing Primitive Shapes + +The ``Graphics Context`` API allows drawing and filling of lines, rectangles, +circles, and arbitrary paths. For each of these, the colors of the output can be +set using the appropriate function: + +```c +// Set the line color +graphics_context_set_stroke_color(ctx, GColorRed); + +// Set the fill color +graphics_context_set_fill_color(ctx, GColorBlue); +``` + +In addition, the stroke width and antialiasing mode can also be changed: + +```c +// Set the stroke width (must be an odd integer value) +graphics_context_set_stroke_width(ctx, 5); + +// Disable antialiasing (enabled by default where available) +graphics_context_set_antialiased(ctx, false); +``` + + +### Lines + +Drawing a simple line requires only the start and end positions, expressed as +``GPoint`` values: + +```c +GPoint start = GPoint(10, 10); +GPoint end = GPoint(40, 60); + +// Draw a line +graphics_draw_line(ctx, start, end); +``` + + +### Rectangles + +Drawing a rectangle requires a bounding ``GRect``, as well as other parameters +if it is to be filled: + +```c +GRect rect_bounds = GRect(10, 10, 40, 60); + +// Draw a rectangle +graphics_draw_rect(ctx, rect_bounds); + +// Fill a rectangle with rounded corners +int corner_radius = 10; +graphics_fill_rect(ctx, rect_bounds, corner_radius, GCornersAll); +``` + +It is also possible to draw a rounded unfilled rectangle: + +```c +// Draw outline of a rounded rectangle +graphics_draw_round_rect(ctx, rect_bounds, corner_radius); +``` + + +### Circles + +Drawing a circle requries its center ``GPoint`` and radius: + +```c +GPoint center = GPoint(25, 25); +uint16_t radius = 50; + +// Draw the outline of a circle +graphics_draw_circle(ctx, center, radius); + +// Fill a circle +graphics_fill_circle(ctx, center, radius); +``` + +In addition, it is possble to draw and fill arcs. In these cases, the +``GOvalScaleMode`` determines how the shape is adjusted to fill the rectangle, +and the cartesian angle values are transformed to preserve accuracy: + +```c +int32_t angle_start = DEG_TO_TRIGANGLE(0); +int32_t angle_end = DEG_TO_TRIGANGLE(45); + +// Draw an arc +graphics_draw_arc(ctx, rect_bounds, GOvalScaleModeFitCircle, angle_start, + angle_end); +``` + +Lastly, a filled circle with a sector removed can also be drawn in a similar +manner. The value of `inset_thickness` determines the inner inset size that is +removed from the full circle: + +```c +uint16_t inset_thickness = 10; + +// Fill a radial section of a circle +graphics_fill_radial(ctx, rect_bounds, GOvalScaleModeFitCircle, inset_thickness, + angle_start, angle_end); +``` + +For more guidance on using round elements in apps, watch the presentation given +at the 2015 Developer Retreat on +[developing for Pebble Time Round](https://www.youtube.com/watch?v=3a1V4n9HDvY). + + +## Bitmaps + +Manually drawing ``GBitmap`` images with the ``Graphics Context`` API is a +simple task, and has much in common with the alternative approach of using a +``BitmapLayer`` (which provides additional convenience funcionality). + +The first step is to load the image data from resources (read +{% guide_link app-resources/images %} to learn how to include images in a +Pebble project): + +```c +static GBitmap *s_bitmap; +``` + +```c +// Load the image data +s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE); +``` + +When the appropriate ``LayerUpdateProc`` is called, draw the image inside the +desired rectangle: + +> Note: Unlike ``BitmapLayer``, the image will be drawn relative to the +> ``Layer``'s origin, and not centered. + +```c +// Get the bounds of the image +GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap); + +// Set the compositing mode (GCompOpSet is required for transparency) +graphics_context_set_compositing_mode(ctx, GCompOpSet); + +// Draw the image +graphics_draw_bitmap_in_rect(ctx, s_bitmap, bitmap_bounds); +``` + +Once the image is no longer needed (i.e.: the app is exiting), free the data: + +```c +// Destroy the image data +gbitmap_destroy(s_bitmap); +``` + + +## Drawing Text + +Similar to the ``TextLayer`` UI component, a ``LayerUpdateProc`` can also be +used to draw text. Advantages can include being able to draw in multiple fonts +with only one ``Layer`` and combining text with other drawing operations. + +The first operation to perform inside the ``LayerUpdateProc`` is to get or load +the font to be used for drawing and set the text's color: + +```c +// Load the font +GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD); +// Set the color +graphics_context_set_text_color(ctx, GColorBlack); +``` + +Next, determine the bounds that will guide the text's position and overflow +behavior. This can either be the size of the ``Layer``, or a more precise bounds +of the text itself. This information can be useful for drawing multiple text +items after one another with automatic spacing. + +```c +char *text = "Example test string for the Developer Website guide!"; + +// Determine a reduced bounding box +GRect layer_bounds = layer_get_bounds(layer); +GRect bounds = GRect(layer_bounds.origin.x, layer_bounds.origin.y, + layer_bounds.size.w / 2, layer_bounds.size.h); + +// Calculate the size of the text to be drawn, with restricted space +GSize text_size = graphics_text_layout_get_content_size(text, font, bounds, + GTextOverflowModeWordWrap, GTextAlignmentCenter); +``` + +Finally, the text can be drawn into the appropriate bounding rectangle: + +```c +// Draw the text +graphics_draw_text(ctx, text, font, bounds, GTextOverflowModeWordWrap, + GTextAlignmentCenter, NULL); +``` \ No newline at end of file diff --git a/devsite/source/_guides/graphics-and-animations/framebuffer-graphics.md b/devsite/source/_guides/graphics-and-animations/framebuffer-graphics.md new file mode 100644 index 00000000..cae90d86 --- /dev/null +++ b/devsite/source/_guides/graphics-and-animations/framebuffer-graphics.md @@ -0,0 +1,170 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Framebuffer Graphics +description: | + How to perform advanced drawing using direct framebuffer access. +guide_group: graphics-and-animations +order: 2 +related_docs: + - Graphics Context + - GBitmap + - GBitmapDataRowInfo +--- + +In the context of a Pebble app, the framebuffer is the data region used to store +the contents of the what is shown on the display. Using the ``Graphics Context`` +API allows developers to draw primitive shapes and text, but at a slower speed +and with a restricted set of drawing patterns. Getting direct access to the +framebuffer allows arbitrary transforms, special effects, and other +modifications to be applied to the display contents, and allows drawing at a +much greater speed than standard SDK APIs. + + +## Accessing the Framebuffer + +Access to the framebuffer can only be obtained during a ``LayerUpdateProc``, +when redrawing is taking place. When the time comes to update the associated +``Layer``, the framebuffer can be obtained as a ``GBitmap``: + +```c +static void layer_update_proc(Layer *layer, GContext *ctx) { + // Get the framebuffer + GBitmap *fb = graphics_capture_frame_buffer(ctx); + + // Manipulate the image data... + + // Finally, release the framebuffer + graphics_release_frame_buffer(ctx, fb); +} +``` + +> Note: Once obtained, the framebuffer **must** be released back to the app so +> that it may continue drawing. + +The format of the data returned will vary by platform, as will the +representation of a single pixel, shown in the table below. + +| Platform | Framebuffer Bitmap Format | Pixel Format | +|:--------:|---------------------------|--------------| +| Aplite | ``GBitmapFormat1Bit`` | One bit (black or white) | +| Basalt | ``GBitmapFormat8Bit`` | One byte (two bits per color) | +| Chalk | ``GBitmapFormat8BitCircular`` | One byte (two bits per color) | + + +## Modifying the Framebuffer Data + +Once the framebuffer has been captured, the underlying data can be manipulated +on a row-by-row or even pixel-by-pixel basis. This data region can be obtained +using ``gbitmap_get_data()``, but the recommended approach is to make use of +``gbitmap_get_data_row_info()`` objects to cater for platforms (such as Chalk), +where not every row is of the same width. The ``GBitmapDataRowInfo`` object +helps with this by providing a `min_x` and `max_x` value for each `y` used to +build it. + +To iterate over all rows and columns, safely avoiding those with irregular start +and end indices, use two nested loops as shown below. The implementation of +`set_pixel_color()` is shown in +[*Getting and Setting Pixels*](#getting-and-setting-pixels): + +> Note: it is only necessary to call ``gbitmap_get_data_row_info()`` once per +> row. Calling it more often (such as for every pixel) will incur a sigificant +> speed penalty. + +```c +GRect bounds = layer_get_bounds(layer); + +// Iterate over all rows +for(int y = 0; y < bounds.size.h; y++) { + // Get this row's range and data + GBitmapDataRowInfo info = gbitmap_get_data_row_info(fb, y); + + // Iterate over all visible columns + for(int x = info.min_x; x <= info.max_x; x++) { + // Manipulate the pixel at x,y... + const GColor random_color = (GColor){ .argb = rand() % 255 }; + + // ...to be a random color + set_pixel_color(info, GPoint(x, y), random_color); + } +} +``` + + +## Getting and Setting Pixels + +To modify a pixel's value, simply set a new value at the appropriate position in +the `data` field of that row's ``GBitmapDataRowInfo`` object. This will modify +the underlying data, and update the display once the frame buffer is released. + +This process will be different depending on the ``GBitmapFormat`` of the +captured framebuffer. On a color platform, each pixel is stored as a single +byte. However, on black and white platforms this will be one bit per byte. Using +``memset()`` to read or modify the correct pixel on a black and white display +requires a bit more logic, shown below: + +```c +static GColor get_pixel_color(GBitmapDataRowInfo info, GPoint point) { +#if defined(PBL_COLOR) + // Read the single byte color pixel + return (GColor){ .argb = info.data[point.x] }; +#elif defined(PBL_BW) + // Read the single bit of the correct byte + uint8_t byte = point.x / 8; + uint8_t bit = point.x % 8; + return byte_get_bit(&info.data[byte], bit) ? GColorWhite : GColorBlack; +#endif +} +``` + +Setting a pixel value is achieved in much the same way, with different logic +depending on the format of the framebuffer on each platform: + +```c +static void set_pixel_color(GBitmapDataRowInfo info, GPoint point, + GColor color) { +#if defined(PBL_COLOR) + // Write the pixel's byte color + memset(&info.data[point.x], color.argb, 1); +#elif defined(PBL_BW) + // Find the correct byte, then set the appropriate bit + uint8_t byte = point.x / 8; + uint8_t bit = point.x % 8; + byte_set_bit(&info.data[byte], bit, gcolor_equal(color, GColorWhite) ? 1 : 0); +#endif +} +``` + +The `byte_get_bit()` and `byte_set_bit()` implementations are shown here for +convenience: + +```c +static bool byte_get_bit(uint8_t *byte, uint8_t bit) { + return ((*byte) >> bit) & 1; +} + +static void byte_set_bit(uint8_t *byte, uint8_t bit, uint8_t value) { + *byte ^= (-value ^ *byte) & (1 << bit); +} +``` + + +## Learn More + +To see an example of what can be achieved with direct access to the framebuffer +and learn more about the underlying principles, watch the +[talk given at the 2014 Developer Retreat](https://www.youtube.com/watch?v=lYoHh19RNy4). + +[EMBED](//www.youtube.com/watch?v=lYoHh19RNy4) diff --git a/devsite/source/_guides/graphics-and-animations/index.md b/devsite/source/_guides/graphics-and-animations/index.md new file mode 100644 index 00000000..a2feeee3 --- /dev/null +++ b/devsite/source/_guides/graphics-and-animations/index.md @@ -0,0 +1,42 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Graphics and Animations +description: | + Information on using animations and drawing shapes, text, and images, as well + as more advanced techniques. +guide_group: graphics-and-animations +menu: false +permalink: /guides/graphics-and-animations/ +generate_toc: false +hide_comments: true +--- + +The Pebble SDK allows drawing of many types of graphics in apps. Using the +``Graphics Context``, ``GBitmap``, ``GDrawCommand`` and +[`Framebuffer`](``graphics_capture_frame_buffer``) APIs gives developers +complete control over the contents of the display, and also can be used to +complement UI created with the various types of ``Layer`` available to enable +more complex UI layouts. + +Both image-based and property-based animations are available, as well as +scheduling regular updates to UI components with ``Timer`` objects. Creative use +of ``Animation`` and ``PropertyAnimation`` can help to delight and engage users, +as well as highlight important information inside apps. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} \ No newline at end of file diff --git a/devsite/source/_guides/graphics-and-animations/vector-graphics.md b/devsite/source/_guides/graphics-and-animations/vector-graphics.md new file mode 100644 index 00000000..91f8d88d --- /dev/null +++ b/devsite/source/_guides/graphics-and-animations/vector-graphics.md @@ -0,0 +1,182 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Vector Graphics +description: | + How to draw simple images using vector images, instead of bitmaps. +guide_group: graphics-and-animations +order: 3 +platform_choice: true +related_docs: + - Draw Commands +related_examples: + - title: PDC Image + url: https://github.com/pebble-examples/pdc-image + - title: PDC Sequence + url: https://github.com/pebble-examples/pdc-sequence +--- + +This is an overview of drawing vector images using Pebble Draw Command files. +See the [*Vector Animations*](/tutorials/advanced/vector-animations) tutorial +for more information. + + +## Vector Graphics on Pebble + +As opposed to bitmaps which contain data for every pixel to be drawn, a vector +file contains only instructions about points contained in the image and how to +draw lines connecting them up. Instructions such as fill color, stroke color, +and stroke width are also included. + +Vector images on Pebble are implemented using the ``Draw Commands`` API, which +allows apps to load and display PDC (Pebble Draw Command) images and sequences +that contain sets of these instructions. An example is the weather icon used in +weather timeline pins. The benefit of using vector graphics for this icon is +that is allows the image to stretch in the familiar manner as it moves between +the timeline view and the pin detail view: + +![weather >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather.png) + +The main benefits of vectors over bitmaps for simple images and icons are: + +* Smaller resource size - instructions for joining points are less memory + expensive than per-pixel bitmap data. + +* Flexible rendering - vector images can be rendered as intended, or manipulated + at runtime to move the individual points around. This allows icons to appear + more organic and life-like than static PNG images. Scaling and distortion is + also made possible. + +However, there are also some drawbacks to choosing vector images in certain +cases: + +* Vector files require more specialized tools to create than bitmaps, and so are + harder to produce. + +* Complicated vector files may take more time to render than if they were simply + drawn per-pixel as a bitmap, depending on the drawing implementation. + + +## Creating Compatible Files + +The file format of vector image files on Pebble is the PDC (Pebble Draw Command) +format, which includes all the instructions necessary to allow drawing of +vectors. These files are created from compatible SVG (Scalar Vector Graphics) +files. Read {% guide_link app-resources/converting-svg-to-pdc %} for more +information. + +
+Pebble Draw Command files can only be used from app resources, and cannot be +created at runtime. +
+ + +## Drawing Vector Graphics + +^CP^ Add the PDC file as a project resource using the 'Add new' under +'Resources' on the left-hand side of the CloudPebble editor as a 'raw binary +blob'. + +^LC^ Add the PDC file to the project resources in `package.json` with the +'type' field to `raw`: + +
+{% highlight {} %} +"media": [ + { + "type": "raw", + "name": "EXAMPLE_IMAGE", + "file": "example_image.pdc" + } +] +{% endhighlight %} +
+ +^LC^ Drawing a Pebble Draw Command image is just as simple as drawing a normal +PNG image to a graphics context, requiring only one draw call. First, load the +`.pdc` file from resources as shown below. + +^CP^ Drawing a Pebble Draw Command image is just as simple as drawing a normal +PNG image to a graphics context, requiring only one draw call. First, load the +`.pdc` file from resources, as shown below. + +First, declare a pointer of type ``GDrawCommandImage`` at the top of the file: + +```c +static GDrawCommandImage *s_command_image; +``` + +Create and assign the ``GDrawCommandImage`` in `init()`, before calling +`window_stack_push()`: + +```nc|c +// Create the object from resource file +s_command_image = gdraw_command_image_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE); +``` + +Next, define the ``LayerUpdateProc`` that will be used to draw the PDC image: + +```c +static void update_proc(Layer *layer, GContext *ctx) { + // Set the origin offset from the context for drawing the image + GPoint origin = GPoint(10, 20); + + // Draw the GDrawCommandImage to the GContext + gdraw_command_image_draw(ctx, s_command_image, origin); +} +``` + +Next, create a ``Layer`` to display the image: + +```c +static Layer *s_canvas_layer; +``` + +Assign the ``LayerUpdateProc`` that will do the rendering to the canvas +``Layer`` and add it to the desired ``Window`` during `window_load()`: + +```c +// Create the canvas Layer +s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h)); + +// Set the LayerUpdateProc +layer_set_update_proc(s_canvas_layer, update_proc); + +// Add to parent Window +layer_add_child(window_layer, s_canvas_layer); +``` + +Finally, don't forget to free the memory used by the sub-components of the +``Window`` in `main_window_unload()`: + +```c +// Destroy the canvas Layer +layer_destroy(s_canvas_layer); + +// Destroy the PDC image +gdraw_command_image_destroy(s_command_image); +``` + +When run, the PDC image will be loaded, and rendered in the ``LayerUpdateProc``. +To put the image into contrast, optionally change the ``Window`` background +color after `window_create()`: + +```c +window_set_background_color(s_main_window, GColorBlueMoon); +``` + +The result will look similar to the example shown below. + +![weather-image >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather-image.png) diff --git a/devsite/source/_guides/index.md b/devsite/source/_guides/index.md new file mode 100644 index 00000000..e84e18b9 --- /dev/null +++ b/devsite/source/_guides/index.md @@ -0,0 +1,45 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: guides/default +title: Developer Guides +description: | + Details of all the guides available for use for building watchapps and + companion phone apps. +permalink: /guides/ +hide_comments: true +generate_toc: false +--- + +This is the main page for the Developer Guides, containing a large number of +resources for developing all kinds of Pebble watchapps. New and experienced +developers can use these resources to help build their skill set when writing +for Pebble. + +## What's Here? + +To help guide towards the most relevant information, the developer guides are +split up into the following sections. A complete list of every available guide +is available in the [Table of Contents](/guides/toc). + +{% for category in site.data.guide-categories %} +### {{ category.title }} +{% for group in site.data.guides %} + {% assign grp = group[1] %} + {% if grp.category == category.id %} + * [**{{ grp.title }}**]({{ grp.url }}) - {{ grp.description }} + {% endif %} +{% endfor %} +{% endfor %} \ No newline at end of file diff --git a/devsite/source/_guides/migration/3x-aplite-migration.md b/devsite/source/_guides/migration/3x-aplite-migration.md new file mode 100644 index 00000000..1644733d --- /dev/null +++ b/devsite/source/_guides/migration/3x-aplite-migration.md @@ -0,0 +1,155 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: SDK 3.x on Aplite Migration Guide +description: How to migrate apps that use SDK 2.x on Aplite to SDK 3.x +permalink: /guides/migration/3x-aplite-migration/ +generate_toc: true +guide_group: migration +order: 3 +--- + +With the release of SDK 3.8, all Pebble platforms can be targeted with one SDK. +This enables developers to make use of lots of new APIs added since SDK 2.9, as +well as the countless bug fixes and improvements also added in the intervening +time. Some examples are: + +* Timezone support + +* Sequence and spawn animations + +* Pebble timeline support + +* Pebble Draw Commands + +* Native PNG image support + +* 8k `AppMessage` buffers + +* New UI components such as `ActionMenu`, `StatusBarLayer`, and + `ContentIndicator`. + +* The stroke width, drawing arcs, polar points APIs, and the color gray! + + +## Get the Beta SDK + +To try out the beta SDK, read the instructions on the [SDK Beta](/sdk/beta) +page. + + +## Mandatory Changes + +To be compatible with users who update their Pebble Classic or Pebble Steel to +firmware 3.x the following important changes **MUST** be made: + +* If you are adding support for Aplite, add `aplite` to your `targetPlatforms` + array in `package.json`, or tick the 'Build Aplite' box in 'Settings' on + CloudPebble. + +* Recompile your app with at least Pebble SDK 3.8 (coming soon!). The 3.x on + Aplite files will reside in `/aplite/` instead of the `.pbw` root folder. + Frankenpbws are **not** encouraged - a 2.x compatible release can be uploaded + separately (see [*Appstore Changes*](#appstore-changes)). + +* Update any old practices such as direct struct member access. An example is + shown below: + + ```c + // 2.x - don't do this! + GRect bitmap_bounds = s_bitmap->bounds; + + // 3.x - please do this! + GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap); + ``` + +* Apps that make use of the `png-trans` resource type should now make use of + built-in PNG support, which allows a single black and white image with + transparency to be used in place of the older compositing technique. + +* If your app uses either the ``Dictation`` or ``Smartstrap`` APIs, you must + check that any code dependant on these hardware features fails gracefully when + they are not available. This should be done by checking for `NULL` or + appropriate `enum` values returned from affected API calls. An example is + shown below: + + ```c + if(smartstrap_subscribe(handlers) != SmartstrapResultNotPresent) { + // OK to use Smartstrap API! + } else { + // Not available, handle gracefully + text_layer_set_text(s_text_layer, "Smartstrap not available!"); + } + + DictationSession *session = dictation_session_create(size, callback, context); + if(session) { + // OK to use Dictation API! + } else { + // Not available, handle gracefully + text_layer_set_text(s_text_layer, "Dictation not available!"); + } + ``` + + +## Appstore Changes + +To handle the transition as users update their Aplite to firmware 3.x (or choose +not to), the appstore will include the following changes: + +* You can now have multiple published releases. When you publish a new release, + it doesn’t unpublish the previous one. You can still manually unpublish + releases whenever they want. + +* The appstore will provide the most recently compatible release of an app to + users. This means that if you publish a new release that has 3.x Aplite + support, the newest published release that supports 2.x Aplite will be + provided to users on 2.x Aplite. + +* There will be a fourth Asset Collection type that you can create: Legacy + Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite + should use the Legacy Aplite asset collection for their 2.x assets. + + +## Suggested Changes + +To fully migrate to SDK 3.x, we also suggest you make these nonessential +changes: + +* Remove any code conditionally compiled with `PBL_SDK_2` defines. It will no + longer be compiled at all. + +* Ensure that any use of ``app_message_inbox_size_maximum()`` and + ``app_message_outbox_size_maximum()`` does not cause your app to run out of + memory. These calls now create ``AppMessage`` buffers of 8k size by default. + Aplite apps limited to 24k of RAM will quickly run out if they use much more + memory. + +* Colors not available on the black and white Aplite display will be silently + displayed as the closet match (black, or white). We recommend checking every + instance of a `GColor`to ensure each is the correct one. `GColorDarkGray` and + `GColorLightGray` will result in a 50/50 dithered gray for **fill** + operations. All other line and text drawing will be mapped to the nearest + solid color (black or white). + +* Apps using image resources should take advantage of the new `bitmap` resource + type, which optimizes image files for you. Read the + [*Unifying Bitmap Resources*](/blog/2015/12/02/Bitmap-Resources/) + blog post to learn more. + +* In addition to the point above, investigate how the contrast and readability + of your app can be improved by making use of gray. Examples of this can be + seen in the system UI: + +![3x-aplite-system >{pebble-screenshot,pebble-screenshot--black}](/images/guides/migration/3x-aplite-system.png) diff --git a/devsite/source/_guides/migration/index.md b/devsite/source/_guides/migration/index.md new file mode 100644 index 00000000..e72e34ef --- /dev/null +++ b/devsite/source/_guides/migration/index.md @@ -0,0 +1,38 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Migrating Older Apps +description: | + Details on how to update older apps affected by API changes. +guide_group: migration +menu: false +permalink: /guides/migration/ +generate_toc: false +hide_comments: true +--- + +When the Pebble SDK major version is increased (such as from 2.x to 3.x), some +breaking API and build process changes are made. This means that some apps +written for an older SDK may no longer compile with the newer one. + +To help developers transition their code, these guides detail the specific +changes they should look out for and highlighting the changes to APIs they may +have previously been using. When breaking changes are made in the future, new +guides will be added here to help developers make the required changes. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/migration/migration-guide-3.md b/devsite/source/_guides/migration/migration-guide-3.md new file mode 100644 index 00000000..74eb4d00 --- /dev/null +++ b/devsite/source/_guides/migration/migration-guide-3.md @@ -0,0 +1,527 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: SDK 3.x Migration Guide +description: Migrating Pebble apps from SDK 2.x to SDK 3.x. +permalink: /guides/migration/migration-guide-3/ +generate_toc: true +guide_group: migration +order: 2 +--- + +This guide provides a detailed list of the changes to existing APIs in Pebble +SDK 3.x. To migrate an older app's code successfully from Pebble SDK 2.x to +Pebble SDK 3.x, consider the information outlined here and make the necessary +changes if the app uses a changed API. + +The number of breaking changes in SDK 3.x for existing apps has been minimized +as much as possible. This means that: + +* Apps built with SDK 2.x **will continue to run on firmware 3.x without any + recompilation needed**. + +* Apps built with SDK 3.x will generate a `.pbw` file that will run on firmware + 3.x. + + +## Backwards Compatibility + +Developers can easily modify an existing app (or create a new one) to be +compilable for both Pebble/Pebble Steel as well as Pebble Time, Pebble Time +Steel, and Pebble Time Round by using `#ifdef` and various defines that are made +available by the SDK at build time. For example, to check that the app will run +on hardware supporting color: + +```c +#ifdef PBL_COLOR + window_set_background_color(s_main_window, GColorDukeBlue); +#else + window_set_background_color(s_main_window, GColorBlack); +#endif +``` + +When the app is compiled, it will be built once for each platform with +`PBL_COLOR` defined as is appropriate. By catering for all cases, apps will run +and look good on both platforms with minimal effort. This avoids the need to +maintain two Pebble projects for one app. + +In addition, as of Pebble SDK 3.6 there are macros that can be used to +selectively include code in single statements. This is an alternative to the +approach shown above using `#ifdef`: + +```c +window_set_background_color(s_main_window, + PBL_IF_COLOR_ELSE(GColorDukeBlue, GColorBlack)); +``` + +See +{% guide_link best-practices/building-for-every-pebble %} +to learn more about these macros, as well as see a complete list. + + +## PebbleKit Considerations + +Apps that use PebbleKit Android will need to be re-compiled in Android Studio +(or similar) with the PebbleKit Android **3.x** (see +{% guide_link communication/using-pebblekit-android %}) +library in order to be compatible with the Pebble Time mobile application. +No code changes are required, however. + +PebbleKit iOS developers remain unaffected and their apps will continue to run +with the new Pebble mobile application. However, iOS companion apps will need to +be recompiled with PebbleKit iOS **3.x** (see +{% guide_link migration/pebblekit-ios-3 "PebbleKit iOS 3.0 Migration Guide" %}) +to work with Pebble Time Round. + + +## Changes to appinfo.json + +There is a new field for tracking which version of the SDK the app is built for. +For example, when using 3.x SDK add this line to the project's `appinfo.json`. + +``` +"sdkVersion": "3" +``` + +Apps will specify which hardware platforms they support (and wish to be built +for) by declaring them in the `targetPlatforms` field of the project's +`appinfo.json` file. + +``` +"targetPlatforms": [ + "aplite", + "basalt", + "chalk" +] +``` + +For each platform listed here, the SDK will generate an appropriate binary and +resource pack that will be included in the `.pbw` file. This means that the app +is actually compiled and resources are optimized once for each platform. The +image below summarizes this build process: + +![build process](/images/sdk/build-process-3.png) + +> Note: If `targetPlatforms` is not specified in `appinfo.json` the app will be +> compiled for all platforms. + +Apps can also elect to not appear in the app menu on the watch (if is is only +pushing timeline pins, for example) by setting `hiddenApp`: + +``` +"watchapp": { + "watchface": false, + "hiddenApp": true +}, +``` + + +## Project Resource Processing + +SDK 3.x enhances the options for adding image resources to a Pebble project, +including performing some pre-processing of images into compatible formats prior +to bundling. For more details on the available resource types, check out the +{% guide_link app-resources %} section of the guides. + + +## Platform-specific Resources + +**Different Resources per Platform** + +It is possible to include different versions of resources on only one of the +platforms with a specific type of display. Do this by appending `~bw` or +`~color` to the name of the resource file and the SDK will prefer that file over +another with the same name, but lacking the suffix. + +This means is it possible to can include a smaller black and white version of an +image by naming it `example-image~bw.png`, which will be included in the +appropriate build over another file named `example-image.png`. In a similar +manner, specify a resource for a color platform by appending `~color` to the +file name. + +An example file structure is shown below. + +```text +my-project/ + resources/ + images/ + example-image~bw.png + example-image~color.png + src/ + main.c + appinfo.json + wscript +``` + +This resource will appear in `appinfo.json` as shown below. + +``` +"resources": { + "media": [ + { + "type": "bitmap", + "name": "EXAMPLE_IMAGE", + "file": "images/example-image.png" + } + ] +} +``` + +Read {% guide_link app-resources/platform-specific %} for more information about +specifying resources per-platform. + +**Single-platform Resources** + +To only include a resource on a **specific** platform, add a `targetPlatforms` +field to the resource's entry in the `media` array in `appinfo.json`. For +example, the resource shown below will only be included for the Basalt build. + +``` +"resources": { + "media": [ + { + "type": "bitmap", + "name": "BACKGROUND_IMAGE", + "file": "images/background.png", + "targetPlatforms": [ + "basalt" + ] + } + ] +} +``` + + +## Changes to wscript + +To support compilation for multiple hardware platforms and capabilities, the +default `wscript` file included in every Pebble project has been updated. + +If a project uses a customized `wscript` file and `pebble convert-project` is +run (which will fully replace the file with a new compatible version), the +`wscript` will be copied to `wscript.backup`. + +View +[this GitHub gist](https://gist.github.com/pebble-gists/72a1a7c85980816e7f9b) +to see a sample of what the new format looks like, and re-add any customizations +afterwards. + + +## Changes to Timezones + +With SDK 2.x, all time-related SDK functions returned values in local time, with +no concept of timezones. With SDK 3.x, the watch is aware of the user's timezone +(specified in Settings), and will return values adjusted for this value. + + +## API Changes Quick Reference + +### Compatibility Macros + +Since SDK 3.0-dp2, `pebble.h` includes compatibility macros enabling developers +to use the new APIs to access fields of opaque structures and still be +compatible with both platforms. An example is shown below: + +```c +static GBitmap *s_bitmap; +``` + +```c +// SDK 2.9 +GRect bounds = s_bitmap->bounds; + +// SDK 3.x +GRect bounds = gbitmap_get_bounds(s_bitmap); +``` + + +### Comparing Colors + +Instead of comparing two GColor values directly, use the new ``gcolor_equal`` +function to check for identical colors. + +```c +GColor a, b; + +// SDK 2.x, bad +if (a == b) { } + +// SDK 3.x, good +if (gcolor_equal(a, b)) { } +``` + +> Note: Two colors with an alpha transparency(`.a`) component equal to `0` +> (completely transparent) are considered as equal. + + +### Assigning Colors From Integers + +Specify a color previously stored as an `int` and convert it to a +`GColor`: + +```c +GColor a; + +// SDK 2.x +a = (GColor)persist_read_int(key); + +// SDK 3.x +a.argb = persist_read_int(key); + +/* OR */ + +a = (GColor){.argb = persist_read_int(key)}; +``` + + +### Specifying Black and White + +The internal representation of SDK 2.x colors such as ``GColorBlack`` and +``GColorWhite`` have changed, but they can still be used with the same name. + + +### PebbleKit JS Account Token + +In SDK 3.0 the behavior of `Pebble.getAccountToken()` changes slightly. In +previous versions, the token returned on Android could differ from that returned +on iOS by dropping some zero characters. The table below shows the different +tokens received for a single user across platforms and versions: + +| Platform | Token | +|:---------|-------| +| iOS 2.6.5 | 29f00dd7872ada4bd14b90e5d49568a8 | +| iOS 3.x | 29f00dd7872ada4bd14b90e5d49568a8 | +| Android 2.3 | 29f0dd7872ada4bd14b90e5d49568a8 | +| Android 3.x | 29f00dd7872ada4bd14b90e5d49568a8 | + +> Note: This process should **only** be applied to new tokens obtained from +> Android platforms, to compare to tokens from older app versions. + +To account for this difference, developers should adapt the new account token as +shown below. + +**JavaScript** + +```js +function newToOld(token) { + return token.split('').map(function (x, i) { + return (x !== '0' || i % 2 == 1) ? x : ''; + }).join(''); +} +``` + +**Python** + +```python +def new_to_old(token): + return ''.join(x for i, x in enumerate(token) if x != '0' or i % 2 == 1) +``` + +**Ruby** + +```ruby +def new_to_old(token) + token.split('').select.with_index { |c, i| (c != '0' or i % 2 == 1) }.join('') +end +``` + +**PHP** + +
+{% highlight { "language": "php", "options": { "startinline": true } } %} +function newToOld($token) { + $array = str_split($token); + return implode('', array_map(function($char, $i) { + return ($char !== '0' || $i % 2 == 1) ? $char : ''; + }, $array, array_keys($array))); +} +{% endhighlight %} +
+ + +### Using the Status Bar + +To help apps integrate aesthetically with the new system experience, all +``Window``s are now fullscreen-only in SDK 3.x. To keep the time-telling +functionality, developers should use the new ``StatusBarLayer`` API in their +`.load` handler. + +> Note: Apps built with SDK 2.x will still keep the system status bar unless +> specified otherwise with `window_set_fullscreen(window, true)`. As a result, +> such apps that have been recompiled will be shifted up sixteen pixels, and +> should account for this in any window layouts. + +```c +static StatusBarLayer *s_status_bar; +``` + +```c +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + + /* other UI code */ + + // Set up the status bar last to ensure it is on top of other Layers + s_status_bar = status_bar_layer_create(); + layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar)); +} +``` + +By default, the status bar will look the same as it did on 2.x, minus the +battery meter. + +![status-bar-default >{pebble-screenshot,pebble-screenshot--time-red}](/images/sdk/status-bar-default.png) + +To display the legacy battery meter on the Basalt platform, simply add an +additional ``Layer`` after the ``StatusBarLayer``, and use the following code in +its ``LayerUpdateProc``. + +```c +static void battery_proc(Layer *layer, GContext *ctx) { + // Emulator battery meter on Aplite + graphics_context_set_stroke_color(ctx, GColorWhite); + graphics_draw_rect(ctx, GRect(126, 4, 14, 8)); + graphics_draw_line(ctx, GPoint(140, 6), GPoint(140, 9)); + + BatteryChargeState state = battery_state_service_peek(); + int width = (int)(float)(((float)state.charge_percent / 100.0F) * 10.0F); + graphics_context_set_fill_color(ctx, GColorWhite); + graphics_fill_rect(ctx, GRect(128, 6, width, 4), 0, GCornerNone); +} +``` + +```c +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + /* other UI code */ + + // Set up the status bar last to ensure it is on top of other Layers + s_status_bar = status_bar_layer_create(); + layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar)); + + // Show legacy battery meter + s_battery_layer = layer_create(GRect(bounds.origin.x, bounds.origin.y, + bounds.size.w, STATUS_BAR_LAYER_HEIGHT)); + layer_set_update_proc(s_battery_layer, battery_proc); + layer_add_child(window_layer, s_battery_layer); +} +``` + +> Note: To update the battery meter more frequently, use ``layer_mark_dirty()`` +> in a ``BatteryStateService`` subscription. Unless the current ``Window`` is +> long-running, this should not be neccessary. + +The ``StatusBarLayer`` can also be extended by the developer in similar ways to +the above. The API also allows setting the layer's separator mode and +foreground/background colors: + +```c +status_bar_layer_set_separator_mode(s_status_bar, + StatusBarLayerSeparatorModeDotted); +status_bar_layer_set_colors(s_status_bar, GColorClear, GColorWhite); +``` + +This results in a a look that is much easier to integrate into a color app. + +![status-bar-color >{pebble-screenshot,pebble-screenshot--time-red}](/images/sdk/status-bar-color.png) + + +### Using PropertyAnimation + +The internal structure of ``PropertyAnimation`` has changed, but it is still +possible to access the underlying ``Animation``: + +```c +// SDK 2.x +Animation *animation = &prop_animation->animation; +animation = (Animation*)prop_animation; + +// SDK 3.x +Animation *animation = property_animation_get_animation(prop_animation); +animation = (Animation*)prop_animation; +``` + +Accessing internal fields of ``PropertyAnimation`` has also changed. For +example, to access the ``GPoint`` in the `from` member of an animation: + +```c +GPoint p; +PropertyAnimation *prop_anim; + +// SDK 2.x +prop_animation->values.from.gpoint = p; + +// SDK 3.x +property_animation_set_from_gpoint(prop_anim, &p); +``` + +Animations are now automatically freed when they have finished. This means that +code using ``animation_destroy()`` should be corrected to no longer do this +manually when building with SDK 3.x, which will fail. **SDK 2.x code must still +manually free Animations as before.** + +Developers can now create complex synchronized and chained animations using the +new features of the Animation Framework. Read +{% guide_link graphics-and-animations/animations %} +to learn more. + + +### Accessing GBitmap Members + +``GBitmap`` is now opaque, so accessing structure members directly is no longer +possible. However, direct references to members can be obtained with the new +accessor functions provided by SDK 3.x: + +```c +static GBitmap *s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE); + +// SDK 2.x +GRect image_bounds = s_bitmap->bounds; + +// SDK 3.x +GRect image_bounds = gbitmap_get_bounds(s_bitmap); +``` + + +### Drawing Rotated Bitmaps + +
+{% markdown %} + The bitmap rotation API requires a significant amount of CPU power and will + have a substantial effect on users' battery life. + + There will also be a large reduction in performance of the app and a lower + framerate may be seen. Use alternative drawing methods such as + ``Draw Commands`` or [`GPaths`](``GPath``) wherever possible. +{% endmarkdown %%} +
+ +Alternatively, draw a ``GBitmap`` with a rotation angle and center point inside a +``LayerUpdateProc`` using ``graphics_draw_rotated_bitmap()``. + + +### Using InverterLayer + +SDK 3.x deprecates the `InverterLayer` UI component which was primarily used +for ``MenuLayer`` highlighting. Developers can now make use of +`menu_cell_layer_is_highlighted()` inside a ``MenuLayerDrawRowCallback`` to +determine which text and selection highlighting colors they prefer. + +> Using this for determining highlight behaviour is preferable to using +> ``menu_layer_get_selected_index()``. Row drawing callbacks may be invoked +> multiple times with a different highlight status on the same cell in order to +> handle partially highlighted cells during animation. diff --git a/devsite/source/_guides/migration/migration-guide-4.md b/devsite/source/_guides/migration/migration-guide-4.md new file mode 100644 index 00000000..df56fa7d --- /dev/null +++ b/devsite/source/_guides/migration/migration-guide-4.md @@ -0,0 +1,102 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: SDK 4.x Migration Guide +description: Migrating Pebble apps from SDK 3.x to SDK 4.x. +permalink: /guides/migration/migration-guide-4/ +generate_toc: true +guide_group: migration +order: 4 +--- + +This guide provides details of the changes to existing APIs in Pebble +SDK 4.x. To migrate an older app's code successfully from Pebble SDK 3.x to +Pebble SDK 4.x, consider the information outlined here and make the necessary +changes if the app uses a changed API. + +The number of breaking changes in SDK 4.x for existing apps has been minimized +as much as possible. This means that: + +* Apps built with SDK 3.x **will continue to run on firmware 4.x without any + recompilation needed**. + +* Apps built with SDK 4.x will generate a `.pbw` file that will run on firmware + 4.x. + +## New APIs + +* ``AppExitReason`` - API for the application to notify the system of the reason +it will exit. +* ``App Glance`` - API for the application to modify its glance. +* ``UnobstructedArea`` - Detect changes to the available screen real-estate +based on obstructions. + +## Timeline Quick View + +Although technically not a breaking change, the timeline quick view feature will +appear overlayed on a watchface which may impact the visual appearance and +functionality of a watchface. Developers should read the +{% guide_link user-interfaces/unobstructed-area "UnobstructedArea guide%} to +learn how to adapt their watchface to handle obstructions. + +## appinfo.json + +Since the [introduction of Pebble Packages in June 2016](/blog/2016/06/07/pebble-packages/), the `appinfo.json` +file has been deprecated and replaced with `package.json`. Your project can +automatically be converted when you run `pebble convert-project` inside your +project folder. + +You can read more about the `package.json` file in the +{% guide_link tools-and-resources/app-metadata "App Metadata" %} guide. + +## Launcher Icon + +The new launcher in 4.0 allows developers to provide a custom icon for +their watchapps and watchfaces. + +
+
+ {% markdown %} + ![Launcher Icon](/images/blog/2016-08-19-pikachu-icon.png) + {% endmarkdown %} +
+
+ {% markdown %} + ![Launcher >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-19-pikachu-launcher.png) + {% endmarkdown %} +
+
+ +> If your `png` file is color, we will use the luminance of the image to add +> some subtle gray when rendering it in the launcher, rather than just black +> and white. Transparency will be preserved. + +You should add a 25x25 `png` to the `resources.media` section of the +`package.json` file, and set `"menuIcon": true`. +Please note that icons that are larger will be ignored and +your app will have the default icon instead. + +```js +"resources": { + "media": [ + { + "menuIcon": true, + "type": "png", + "name": "IMAGE_MENU_ICON", + "file": "images/icon.png" + } + ] +} +``` diff --git a/devsite/source/_guides/migration/migration-guide.md b/devsite/source/_guides/migration/migration-guide.md new file mode 100644 index 00000000..5cbf326b --- /dev/null +++ b/devsite/source/_guides/migration/migration-guide.md @@ -0,0 +1,441 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: SDK 2.x Migration Guide +description: Migrating Pebble apps from SDK 1.x to SDK 2.x. +permalink: /guides/migration/migration-guide/ +generate_toc: true +guide_group: migration +order: 1 +--- + +{% alert important %} +This page is outdated, intended for updating SDK 1.x apps to SDK 2.x. All app +should now be created with SDK 3.x. For migrating an app from SDK 2.x to SDK +3.x, the read {% guide_link migration/migration-guide-3 %}. +{% endalert %} + + +## Introduction + +This guide provides you with a summary and a detailed list of the changes, +updates and new APIs available in Pebble SDK 2.0. To migrate your code successfully +from Pebble SDK 1.x to Pebble SDK 2.x, you should read this guide. + +In addition to updated and new Pebble APIs, you'll find updated developer tools +and a simplified build system that makes it easier to create, build, and deploy +Pebble apps. + +**Applications written for Pebble SDK 1.x do not work on Pebble 2.0.** It is +extremely important that you upgrade your apps, so that your users can continue +to enjoy your watchfaces and watchapps. + +These are the essential steps to perform the upgrade: + +* You'll need to upgrade Pebble SDK on your computer, the firmware on your + Pebble, and the Pebble mobile application on your phone. +* You need to upgrade the `arm-cs-tools`. The version shipped with Pebble SDK 2 + contains several important improvements that help reduce the size of the + binaries generated and improve the performance of your app. +* You need to upgrade the python dependencies + `pip install --user -r {{ site.pb_sdk_path }}{{ site.pb_sdk2_package }}/requirements.txt`). + +## Discovering the new Pebble tools (Native SDK only) + +One of the new features introduced in Pebble native SDK 2.0 is the `pebble` command +line tool. This tool is used to create new apps, build and install those apps on +your Pebble. + +The tool was designed to simplify and optimize the build process for your Pebble +watchfaces and watchapps. Give it a try right now: + +```c +$ pebble new-project helloworld +$ cd helloworld +$ ls +appinfo.json resources src wscript +``` + +Notice that the new SDK does not require symlinks as the earlier SDK did. There +is also a new `appinfo.json` file, described in greater detail later in this +guide. The file provides you with a more readable format and includes all the +metadata about your app. + +```c +$ pebble build +... + +Memory usage: +============= +Total app footprint in RAM: 801 bytes / ~24kb +Free RAM available (heap): 23775 bytes + +[12/13] inject-metadata: build/pebble-app.raw.bin build/app_resources.pbpack.data -> build/pebble-app.bin +[13/13] helloworld.pbw: build/pebble-app.bin build/app_resources.pbpack -> build/helloworld.pbw + +... + +'build' finished successfully (0.562s) +``` + +You don't need to call the `waf` tool to configure and then build the project +anymore (`pebble` still uses `waf`, however). The new SDK also gives you some +interesting information on how much memory your app will use and how much memory +will be left for you in RAM. + +```c +$ pebble install --phone 10.0.64.113 --logs +[INFO ] Installation successful +[INFO ] Enabling application logging... +[INFO ] Displaying logs ... Ctrl-C to interrupt. +[INFO ] D helloworld.c:58 Done initializing, pushed window: 0x2001a524 +``` + +Installing an app with `pebble` is extremely simple. It uses your phone and the +official Pebble application as a gateway. You do need to configure your phone +first, however. For more information on working with this tool, read +{% guide_link tools-and-resources/pebble-tool %}. + +You don't need to run a local HTTP server or connect with Bluetooth like you did +with SDK 1.x. You will also get logs sent directly to the console, which will +make development a lot easier! + +## Upgrading a 1.x app to 2.0 + +Pebble 2.0 is a major release with many changes visible to users and developers +and some major changes in the system that are not visible at first sight but +will have a strong impact on your apps. + +Here are the biggest changes in Pebble SDK 2.0 that will impact you when +migrating your app. The changes are discussed in more detail below: + +* Every app now requires an `appinfo.json`, which includes your app name, UUID, + resources and a few other new configuration parameters. For more information, + refer to {% guide_link tools-and-resources/app-metadata %}. +* Your app entry point is called `main()` and not `pbl_main()`. +* Most of the system structures are not visible to apps anymore, and instead of + allocating the memory yourself, you ask the system to allocate the memory and + return a pointer to the structure. + + > This means that you'll have to change most of your system calls and + > significantly rework your app. This change was required to allow us to + > update the structs in the future (for example, to add new fields in them) + > without forcing you to recompile your app code. + +* Pebble has redesigned many APIs to follow standard C best practices and + futureproof the SDK. + +### Application metadata + +To upgrade your app for Pebble SDK 2.0, you should first run the +`pebble convert-project` command in your existing 1.x project. This will +automatically try to generate the `appinfo.json` file based on your existing +source code and resource file. It will not touch your C code. + +Please review your `appinfo.json` file and make sure everything is OK. If it is, +you can safely remove the UUID and the `PBL_APP_INFO` in your C file. + +Refer to {% guide_link tools-and-resources/app-metadata %} +for more information on application metadata and the basic structure of an app +in Pebble SDK 2.0. + +### Pebble Header files + +In Pebble SDK 1.x, you would reference Pebble header files with three include +statements: + +```c +#include "pebble_os.h" +#include "pebble_app.h" +#include "pebble_fonts.h" +``` + +In Pebble SDK 2.x, you can replace them with one statement: + +```c +#include +``` + +### Initializing your app + +In Pebble SDK 1.x, your app was initialized in a `pbl_main()` function: + +```c +void pbl_main(void *params) { + PebbleAppHandlers handlers = { + .init_handler = &handle_init + }; + app_event_loop(params, &handlers); +} +``` + +In Pebble SDK 2.0: + +* `pbl_main` is replaced by `main`. +* The `PebbleAppHandlers` structure no longer exists. You call your init and + destroy handlers directly from the `main()` function. + +```c +int main(void) { + handle_init(); + app_event_loop(); + handle_deinit(); +} +``` + +There were other fields in the `PebbleAppHandlers`: + +* `PebbleAppInputHandlers`: + Use a ``ClickConfigProvider`` instead. +* `PebbleAppMessagingInfo`: Refer to the section below on ``AppMessage`` + changes. +* `PebbleAppTickInfo`: Refer to the section below on Tick events. +* `PebbleAppTimerHandler`: Refer to the section below on ``Timer`` events. +* `PebbleAppRenderEventHandler`: Use a ``Layer`` and call + ``layer_set_update_proc()`` to provide your own function to render. + +### Opaque structures and Dynamic Memory allocation + +In Pebble SDK 2.0, system structures are opaque and your app can't directly +allocate memory for them. Instead, you use system functions that allocate memory +and initialize the structure at the same time. + +#### Allocating dynamic memory: A simple example + +In Pebble SDK 1.x, you would allocate memory for system structures inside your +app with static global variables. For example, it was very common to write: + +```c +Window my_window; +TextLayer text_layer; + +void handle_init(AppContextRef ctx) { + window_init(&my_window, "My App"); + text_layer_init(&text_layer, GRect(0, 0, 144, 20)); +} +``` + +In Pebble SDK 2, you can't allocate memory statically in your program because +the compiler doesn't know at compile time how big the system structures are +(here, in the above code snippet ``Window`` and ``TextLayer``). Instead, you use +pointers and ask the system to allocate the memory for you. + +This simple example becomes: + +```c +Window *my_window; +TextLayer *text_layer; + +void handle_init(void) { + my_window = window_create(); + + text_layer = text_layer_create(GRect(0, 0, 144, 20)); +} +``` + +Instead of using `*_init()` functions and passing them a pointer to the structure, +in SDK 2.0, you call functions that end in `_create()`, and these functions will +allocate memory and return to your app a pointer to a structure that is +initialized. + +Because the memory is dynamically allocated, it is extremely important that you +release that memory when you are finished using the structure. This can be done +with the `*_destroy()` functions. For our example, we could write: + +```c +void handle_deinit(void) { + text_layer_destroy(text_layer); + window_destroy(my_window); +} +``` + +#### Dynamic memory: General rules in Pebble SDK 2.0 + +* Replace all statically allocated system structures with a pointer to the + structure. +* Replace functions that ended in `_init()` with their equivalent that end in + `_create()`. +* Keep pointers to the structures that you have initialized. Call the + `*_destroy()` functions to release the memory. + +### AppMessage changes + + * Instead of defining your buffer sizes in `PebbleAppMessagingInfo`, you pass + them to ``app_message_open()`` + * Instead of using a `AppMessageCallbacksNode` structure and + `app_message_register_callbacks()`, you register handler for the different + ``AppMessage`` events with: + * ``app_message_register_inbox_received()`` + * ``app_message_register_inbox_dropped()`` + * ``app_message_register_outbox_failed()`` + * ``app_message_register_outbox_sent()`` + * ``app_message_set_context(void *context)``: To set the context that will be + passed to all the handlers. + +* `app_message_out_get()` is replaced by ``app_message_outbox_begin()``. +* `app_message_out_send()` is replaced by ``app_message_outbox_send()``. +* `app_message_out_release()` is removed. You do not need to call this anymore. + +For more information, please review the ``AppMessage`` API Documentation. + +For working examples using AppMessage and AppSync in SDK 2.0, refer to: + + * `{{ site.pb_sdk2_package }}/PebbleSDK-2.x/Examples/pebblekit-js/quotes`: + Demonstrates how to use PebbleKit JS to fetch price quotes from the web. + It uses AppMessage on the C side. + * `{{ site.pb_sdk2_package }}/PebbleSDK-2.x/Examples/pebblekit-js/weather`: + A PebbleKit JS version of the traditional `weather-demo` example. It uses + AppSync on the C side. + +### Dealing with Tick events + +Callbacks for tick events can't be defined through `PebbleAppHandlers` anymore. +Instead, use the Tick Timer Event service with: +``tick_timer_service_subscribe()``. + +For more information, read {% guide_link events-and-services/events %}/ + +### Timer changes + +`app_timer_send_event()` is replaced by ``app_timer_register()``. + +For more information, refer to the ``Timer`` API documentation. + +### WallTime API changes + +* `PblTm` has been removed and replaced by the libc standard struct. Use struct + `tm` from `#include `. + +* `tm string_format_time()` function is replaced by ``strftime()``. + +* `get_time()` is replaced by `localtime(time(NULL))`. This lets you convert a + timestamp into a struct. + +* Pebble OS does not, as yet, support timezones. However, Pebble SDK 2 + introduces `gmtime()` and `localtime()` functions to prepare for timezone + support. + + +### Click handler changes + +In SDK 1.x, you would set up click handlers manually by modifying an array of +config structures to contain the desired configuration. In SDK 2.x, how click +handlers are registered and used has changed. + +The following functions for subscribing to events have been added in SDK 2.x: + +```c +void window_set_click_context(); +void window_single_click_subscribe(); +void window_single_repeating_click_subscribe(); +void window_multi_click_subscribe(); +void window_multi_click_subscribe(); +void window_long_click_subscribe(); +void window_raw_click_subscribe(); +``` + +For more information, refer to the ``Window`` API documentation. + +For example, in SDK 1.x you would do this: + +```c +void click_config_provider(ClickConfig **config, void *context) { + config[BUTTON_ID_UP]->click.handler = up_click_handler; + config[BUTTON_ID_UP]->context = context; + config[BUTTON_ID_UP]->click.repeat_interval_ms = 100; + + config[BUTTON_ID_SELECT]->click.handler = select_click_handler; + + config[BUTTON_ID_DOWN]->multi_click.handler = down_click_handler; + config[BUTTON_ID_DOWN]->multi_click.min = 2; + config[BUTTON_ID_DOWN]->multi_click.max = 10; + config[BUTTON_ID_DOWN]->multi_click.timeout = 0; /* default timeout */ + config[BUTTON_ID_DOWN]->multi_click.last_click_only = true; + + config[BUTTON_ID_SELECT]->long_click.delay_ms = 1000; + config[BUTTON_ID_SELECT]->long_click.handler = select_long_click_handler; +} +``` + +In SDK 2.x, you would use the following calls instead: + +```c +void click_config_provider(void *context) { + window_set_click_context(BUTTON_ID_UP, context); + window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_click_handler); + + window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler); + + window_multi_click_subscribe(BUTTON_ID_DOWN, 2, 10, 0, true, down_click_handler); + + window_long_click_subscribe(BUTTON_ID_SELECT, 1000, select_long_click_handler, NULL /* No handler on button release */); +} +``` + +Notice that the signature of ``ClickConfigProvider`` has also changed. These +``Clicks`` API functions **must** be called from within the +ClickConfigProvider function. If they are not, your app code will fail. + +### Other changes + +* `graphics_text_draw()` has been renamed to ``graphics_draw_text()``, matching + the rest of Pebble's graphics_draw_ functions. There are no changes with the + usage of the function. + +## Quick reference for the upgrader + +**Table 1. API changes from SDK 1.x to 2.x** + + API Call in SDK 1.x | API Call in SDK 2.x | +:-----------|:------------| + `#define APP_TIMER_INVALID_HANDLE ((AppTimerHandle)0)` | Changed. No longer needed; `app_timer_register()` always succeeds. See [``Timer``. + `#define INT_MAX 32767` | Changed. See `#include ` + `AppTimerHandle app_timer_send_event();` | See ``app_timer_register()`` for more information at ``Timer``. + `ARRAY_MAX` | Removed from Pebble headers. Now use limits.h + `bool app_timer_cancel_event();` | Changed. See ``app_timer_cancel()`` for more information at ``Timer``. + `GContext *app_get_current_graphics_context();` | Removed. Use the context supplied to you in the drawing callbacks. + `GSize text_layer_get_max_used_size();` | Use ``text_layer_get_content_size()``. See ``TextLayer``. + `INT_MAX` | Removed from Pebble headers. Now use limits.h + `void get_time();` | Use `localtime(time(NULL))` from `#include `. + `void resource_init_current_app();` | No longer needed. + `void string_format_time();` | Use ``strftime`` from `#include `. + `void window_render();` | No longer available. + +### Using `*_create()/*_destroy()` instead of `*_init()/*_deinit()` functions + +If you were using the following `_init()/_deinit()` functions, you should now +use `*_create()/*_destroy()` instead when making these calls: + +* `bool rotbmp_init_container();` See ``BitmapLayer``. +* `bool rotbmp_pair_init_container();` ``BitmapLayer``. +* `void action_bar_layer_init();` See ``ActionBarLayer``. +* `void animation_init();` See ``Animation``. +* `void bitmap_layer_init();` ``BitmapLayer``. +* `void gbitmap_init_as_sub_bitmap();` See [Graphics Types](``Graphics Types``). +* `void gbitmap_init_with_data();` See [Graphics Types](``Graphics Types``). +* `void inverter_layer_init();` Now `InverterLayer` (deprecated in SDK 3.0). +* `void layer_init();` See ``Layer``. +* `void menu_layer_init();` See ``MenuLayer``. +* `void number_window_init();` +* `void property_animation_init_layer_frame();` See ``Animation``. +* `void property_animation_init();` See ``Animation``. +* `void rotbmp_deinit_container();` ``BitmapLayer``. +* `void rotbmp_pair_deinit_container();` ``BitmapLayer``. +* `void scroll_layer_init();` See ``ScrollLayer``. +* `void simple_menu_layer_init();` See ``SimpleMenuLayer``. +* `void text_layer_deinit();` See ``TextLayer``. +* `void text_layer_init();` See ``TextLayer``. +* `void window_deinit();` See ``Window``. +* `void window_init();` See ``Window``. diff --git a/devsite/source/_guides/migration/pebblekit-ios-3.md b/devsite/source/_guides/migration/pebblekit-ios-3.md new file mode 100644 index 00000000..09e2c46a --- /dev/null +++ b/devsite/source/_guides/migration/pebblekit-ios-3.md @@ -0,0 +1,308 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PebbleKit iOS 3.0 Migration Guide +description: How to migrate apps that use PebbleKit iOS to the 3.0 version. +permalink: /guides/migration/pebblekit-ios-3/ +generate_toc: true +guide_group: migration +order: 1 +--- + +With previous Pebble firmware versions, iOS users had to manage two different +Bluetooth pairings to Pebble. A future goal is removing the Bluetooth *Classic* +pairing and keeping only the *LE* (Low Energy) one. This has a couple of +advantages. By using only one Bluetooth connection Pebble saves energy, +improving the battery life of both Pebble and the phone. It also has the +potential to simplify the onboarding experience. In general, fewer moving parts +means less opportunity for failures and bugs. + +The plan is to remove the Bluetooth *Classic* connection and switch to *LE* in +gradual steps. The first step is to make the Pebble app communicate over *LE* if +it is available. The Bluetooth *Classic* pairing and connection will be kept +since as of today most iOS companion apps rely on the *Classic* connection in +order to work properly. + +Building a companion app against PebbleKit iOS 3.0 will make it compatible with +the new *LE* connection, while still remaining compatible with older Pebble +watches which don't support the *LE* connection. Once it is decided to cut the +Bluetooth *Classic* cord developers won't have to do anything, existing apps +will continue to work. + +> Note: Pebble Time Round (the chalk platform) uses only Bluetooth LE, and so +> companion apps **must** use PebbleKit iOS 3.0 to connect with it. + + +## What's New + +### Sharing No More: Dedicated Channels per App + +A big problem with the Bluetooth *Classic* connection is that all iOS companion +apps have to share a single communication channel which gets assigned on a "last +one wins" basis. Another problem is that a "session" on this channel has to be +opened and closed by the companion app. + +PebbleKit iOS 3.0 solved both these problems with *LE* based connections. When +connected over *LE* each companion app has a dedicated and persistent +communication channel to Pebble. + +This means that an app can stay connected as long as there is a physical +Bluetooth LE connection, and it does not have to be closed before that other +apps can use it! + + +### Starting an App from Pebble + +Since each companion app using the *LE* connection will have a dedicated and +persistent channel, the user can now start using an app from the watch without +having to pull out the phone to open the companion app. The companion app will +already be connected and listening. However there are a few caveats to this: + +* The user must have launched the companion app at least once after rebooting + the iOS device. + +* If the user force-quits the companion app (by swiping it out of the app + manager) the channel to the companion app will be disconnected. + +Otherwise the channel is pretty robust. iOS will revive the companion app in the +background when the watchapp sends a message if the companion app is suspended, +has crashed, or was stopped/killed by iOS because it used too much memory. + + +## How to Upgrade + +1. Download the new `PebbleKit.framework` from the + [`pebble-ios-sdk`](https://github.com/pebble/pebble-ios-sdk/) repository. + +2. Replace the existing `PebbleKit.framework` directory in the iOS project. + +3. The `PebbleVendor.framework` isn't needed anymore. If it is not used, remove + it from the project to reduce its size. + +3. See the [Breaking API Changes](#breaking-api-changes) section below. + +4. When submitting the iOS companion to the + [Pebble appstore](https://dev-portal.getpebble.com/), make sure to check the + checkbox shown below. + +![](/images/guides/migration/companion-checkbox.png) + +> **Important**: Make sure to invoke `[[PBPebbleCentral defaultCentral] run]` +> after the iOS app is launched, or watches won't connect! + + +## Breaking API Changes + +### App UUIDs Are Now NSUUIDs + +Older example code showed how to use the `appUUID` property of the +`PBPebbleCentral` object, which was passed as an `NSData` object. Now it is +possible to directly use an `NSUUID` object. This also applies to the `PBWatch` +APIs requiring `appUUID:` parameters. An example of each case is shown below. + +**Previous Versions of PebbleKit iOS** + +```obj-c +uuid_t myAppUUIDbytes; +NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"]; +[myAppUUID getUUIDBytes:myAppUUIDbytes]; +[PBPebbleCentral defaultCentral].appUUID = [NSData dataWithBytes:myAppUUIDbytes length:16]; +``` + +**With PebbleKit iOS 3.0** + +```obj-c +NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"]; +[PBPebbleCentral defaultCentral].appUUID = myAppUUID; +``` + + +### Cold PBPebbleCentral + +As soon as PebbleKit uses a `CoreBluetooth` API a pop-up asking for Bluetooth +permissions will appear. Since it is undesirable for this pop-up to jump right +into users' faces when they launch the iOS app, `PBPebbleCentral` will start in +a "cold" state. + +This gives developers the option to explain to app users that this pop-up will +appear, in order to provide a smoother onboarding experience. As soon as a +pop-up would be appropriate to show (e.g.: during the app's onboarding flow), + call `[central run]`, and the pop-up will be shown to the user. + +To help personalize the experience, add some custom text to the pop-up by adding +a `NSBluetoothPeripheralUsageDescription` ("Privacy - Bluetooth Peripheral Usage +Description") value to the project's `Info.plist` file. + +```obj-c +// MyAppDelegate.m - Set up PBPebbleCentral and run if the user has already +// performed onboarding +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [PBPebbleCentral defaultCentral].delegate = self; + [PBPebbleCentral defaultCentral].appUUID = myAppUUID; + if ([MySettings sharedSettings].userDidPerformOnboarding) { + [[PBPebbleCentral defaultCentral] run]; + } +} +``` + +```obj-c +// MyOnboarding.m - Once the pop-up has been accepted, begin PBPebbleCentral +- (IBAction)didTapGrantBluetoothPermissionButton:(id)sender { + [MySettings sharedSettings].userDidPerformOnboarding = YES; + [[PBPebbleCentral defaultCentral] run]; // will trigger pop-up +} +``` + +It is very unlikely that the Pebble watch represented by the `PBWatch` object +returned by `lastConnectedWatch` is connected instantly after invoking `[central +run]`. Instead, it is guaranteed that the delegate will receive +`pebbleCentral:watchDidConnect:` as soon as the watch connects (which might take +a few seconds). Once this has occurred, the app may then perform operations on +the `PBWatch` object. + + +## New Features + +### 8K AppMessage Buffers + +In previous versions of PebbleKit iOS, if an app wanted to transmit large +amounts of data it had to split it up into packets of 126 bytes. As of firmware +version 3.5, this is no longer the case - the maximum message size is now such +that a dictionary with one byte array (`NSData`) of 8192 bytes fits in a single +app message. The maximum available buffer sizes are increased for messages in +both directions (i.e.: inbox and outbox buffer sizes). Note that the watchapp +should be compiled with SDK 3.5 or later in order to use this capability. + +To check whether the connected watch supports the increased buffer sizes, use +`getVersionInfo:` as shown below. + +```obj-c +[watch getVersionInfo:^(PBWatch *watch, PBVersionInfo *versionInfo) { + // If 8k buffers are supported... + if ((versionInfo.remoteProtocolCapabilitiesFlags & PBRemoteProtocolCapabilitiesFlagsAppMessage8kSupported) != 0) { + // Send a larger message! + NSDictionary *update = @{ @(0): someHugePayload }; + [watch appMessagesPushUpdate:update onSent:^(PBWatch *watch, NSDictionary *update, NSError *error) { + // ... + }]; + } else { + // Fall back to sending smaller 126 byte messages... + } +}]; +``` + + +### Swift Support + +The library now exports a module which makes using PebbleKit iOS in +[Swift](https://developer.apple.com/swift/) projects much easier. PebbleKit iOS +3.0 also adds nullability and generic annotations so that developers get the +best Swift experience possible. + +```obj-c +func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + let pebbleCentral = PBPebbleCentral.defaultCentral() + pebbleCentral.appUUID = PBGolfUUID + pebbleCentral.delegate = self + pebbleCentral.run() + + return true +} +``` + + +## Minor Changes and Deprecations + +* Removed the PebbleVendor framework. + + * Also removed CocoaLumberjack from the framework. This should + reduce conflicts if the app is using CocoaLumberjack itself. + + * If the project need these classes, it can keep the PebbleVendor dependency, + therwise just remove it. + +* Added `[watch releaseSharedSession]` which will close *Classic* sessions that + are shared between iOS apps (but not *LE* sessions as they are not shared). + + * If the app doesn't need to talk to Pebble in the background, it doesn't have + to use it. + + * If the app does talk to Pebble while in the background, call this method as + soon as it is done talking. + +* Deprecated `[watch closeSession:]` - please use `[watch releaseSharedSession]` + if required (see note above). The app can't close *LE* sessions actively. + +* Deprecated `[defaultCentral hasValidAppUUID]` - please use `[defaultCentral + appUUID]` and check that it is not `nil`. + +* Added `[defaultCentral addAppUUID:]` if the app talks to multiple app UUIDs from + the iOS application, allowing `PebbleCentral` to eagerly create *LE* + sessions. + +* Added logging - PebbleKit iOS 3.0 now logs internal warnings and errors via + `NSLog`. To change the verbosity, use `[PBPebbleCentral setLogLevel:]` or even + override the `PBLog` function (to forward it to CocoaLumberjack for example). + +* Changed `[watch appMessagesAddReceiveUpdateHandler:]` - the handler must not + be `nil`. + + +## Other Recommendations + +### Faster Connection + +Set `central.appUUID` before calling `[central run]`. If using multiple app +UUIDs please use the new `addAppUUID:` API before calling `[central run]` for +every app UUID that the app will talk to. + + +### Background Apps + +If the app wants to run in the background (please remember that Apple might +reject it unless it provides reasonable cause) add the following entries to the +`UIBackgroundModes` item in the project's `Info.plist` file: + +* `bluetooth-peripheral` ("App shares data using CoreBluetooth") which is used + for communication. + +* `bluetooth-central` ("App communicates using CoreBluetooth") which is used for + discovering and reconnecting Pebbles. + + +### Compatibility with Older Pebbles + +Most of the Pebble users today will be using a firmware that is not capable of +connecting to an iOS application using *LE*. *LE* support will gradually roll +out to all Pebble watches. However, this will not happen overnight. Therefore, +both *LE* and *Classic* PebbleKit connections have to be supported for some +period of time. This has several implications for apps: + +* Apps still need to be whitelisted. Read + {% guide_link appstore-publishing/whitelisting %} for more information and to + whitelist a new app. + +* Because the *Classic* communication channel is shared on older Pebble firmware + versions, iOS apps still need to provide a UI to let the user connect to/disconnect + from the Pebble app. For example, a "Disconnect" button would cause `[watch + releaseSharedSession]` to be called. + +* In the project's `Info.plist` file: + + * The `UISupportedExternalAccessoryProtocols` key still needs to be added with + the value `com.getpebble.public`. + + * The `external-accessory` value needs to be added to the `UIBackgroundModes` + array, if you want to support using the app while backgrounded. diff --git a/devsite/source/_guides/pebble-packages/creating-packages.md b/devsite/source/_guides/pebble-packages/creating-packages.md new file mode 100644 index 00000000..1f7160b2 --- /dev/null +++ b/devsite/source/_guides/pebble-packages/creating-packages.md @@ -0,0 +1,223 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Creating Pebble Packages +description: How to create Pebble Packages +permalink: /guides/pebble-packages/creating-packages/ +generate_toc: true +guide_group: pebble-packages +--- + +{% alert notice %} +Currently package _creation_ is only supported by the native SDK. +However, you can still use packages in CloudPebble. +{% endalert %} + +## Getting Started + +To get started creating a package, run `pebble new-package some-name`. +Make `some-name` something meaningful and unique; it is what you'll be +publishing it under. You can check if it's taken on [npm](https://npmjs.org). +If you'll be including a JavaScript component, you can add `--javascript` for +a sample javascript file. + +## Components of a Package + +### C code + +{% alert notice %} +**Tip**: If you want to use an +``Event Service``, +you should use the +[pebble-events](https://www.npmjs.com/package/pebble-events) package to +handle subscriptions from multiple packages. +{% endalert %} + +Packages can export C functions to their consumers, and the default package +exports `somelib_find_truth` as an example. To export a function, it +simply has to be declared in a C file in `src/c/`, and declared in a header +file in `include/`. For instance: + +`src/c/somelib.c`: + +```c +#include +#include "somelib.h" + +bool somelib_find_truth(void) { + return true; +} +``` + +`include/somelib.h`: + +```c +#pragma once + +bool somelib_find_truth(void); +``` + +Notice that `include/` is already in the include path when building packages, +so include files there can be included easily. By convention, packages should +prefix their non-static functions with their name (in this case `somelib`) in +order to avoid naming conflicts. If you don't want to export a function, don't +include it in a file in `includes/`; you can instead use `src/c/`. You should +still prefix any non-static symbols with your library name to avoid conflicts. + +Once the package is imported by a consumer — either an app or package — its +include files will be in a directory named for the package, and so can be +included with `#include `. There is no limit on the number +or structure of files in the `include` directory. + +### JavaScript code + +Packages can export JavaScript code for use in PebbleKit JS. The default +JavaScript entry point for packages is always in `src/js/index.js`. However, +files can also be required directly, according to +[standard node `require` rules](https://nodejs.org/api/modules.html). In either +case they are looked up relative to their root in `src/js/`. + +JavaScript code can export functions by attaching them to the global `exports` +object: + +`src/js/index.js`: + +``` +exports.addNumbers = function(a, b) { + return a + b; +}; +``` + +Because JavaScript code is scoped and namespaced already, there is no need to +use any naming convention. + +### Resources + +Packages can include resources in the same way as apps, and those resources can +then be used by both the package and the app. They are included in +`package.json` in +[the same manner as they are for apps](/guides/app-resources/). +To avoid naming conflicts, packages should prefix their resource names with the +package name, e.g. `SOMELIB_IMAGE_LYRA`. + +It's best practice to define an image resource using the package name as a +prefix on the resource `name`: + +```javascript +"resources": { + "media": [ + { + "name": "MEDIA_PACKAGE_IMAGE_01_TINY", + "type": "bitmap", + "file": "images/01-tiny.png" + }, + //... + ] +} +``` + +Create a `publishedMedia` entry if you want to make the images available for +{% guide_link user-interfaces/appglance-c "AppGlance slices" %} or +{% guide_link pebble-timeline/pin-structure "Timeline pins" %}. + + +```javascript +"resources": { + //... + "publishedMedia": [ + { + "name": "MEDIA_PACKAGE_IMAGE_01", + "glance": "MEDIA_PACKAGE_IMAGE_01_TINY", + "timeline": { + "tiny": "MEDIA_PACKAGE_IMAGE_01_TINY", + "small": "MEDIA_PACKAGE_IMAGE_01_SMALL", + "large": "MEDIA_PACKAGE_IMAGE_01_LARGE" + } + } + ] +} +``` + +> Note: Do NOT assign an `id` when defining `publishedMedia` within packages, +see {% guide_link pebble-packages/using-packages "Using Packages" %}. + +Resource IDs are not assigned until the package has been linked with an app and +compiled, so `RESOURCE_ID_*` constants cannot be used as constant initializers +in packages. To work around this, either assign them at runtime or reference +them directly. It is also no longer valid to try iterating over the resource id +numbers directly; you must use the name defined for you by the SDK. + + +### AppMessage Keys + +Libraries can use AppMessage keys to reduce friction when creating a package +that needs to communicate with the phone or internet, such as a weather package. +A list of key names can be included in `package.json`, under +`pebble.messageKeys`. These keys will be allocated numbers at app build time. +We will inject them into your C code with the prefix `MESSAGE_KEY_`, e.g. +`MESSAGE_KEY_CURRENT_TEMP`. + +If you want to use multiple keys as an 'array', you can specify a name like +`ELEMENTS[6]`. This will create a single key, `ELEMENTS`, but leave five empty +spaces after it, for a total of six available keys. You can then use arithmetic +to access the additional keys, such as `MESSAGE_KEY_ELEMENTS + 5`. + +To use arrays in JavaScript you will need to know the actual numeric values of +your keys. They will exist as keys on an object you can access via +`require('message_keys')`. For instance: + +```js +var keys = require('message_keys'); +var elements = ['honesty', 'generosity', 'loyalty', 'kindness', 'laughter', 'magic']; +var dict = {} +for (var i = 0; i < 6; ++i) { + dict[keys.ELEMENTS + i] = elements[i]; +} +Pebble.sendAppMessage(dict, successCallback, failureCallback); +``` + +## Building and testing + +Run `pebble build` to build your package. You can install it in a test project +using `pebble package install ../path/to/package`. Note that you will have to +repeat both steps when you change the package. If you try symlinking the package, +you are likely to run into problems building your app. + +## Publishing + +Publishing a Pebble Package requires you to have an npm account. For your +convenience, you can create or log in to one using `pebble package login` +(as distinct from `pebble login`). Having done this once, you can use +`pebble package publish` to publish a package. + +Remember to document your package! It isn't any use to anyone if they can't +figure out how to use it. README.md is a good place to include some +documentation. + +Adding extra metadata to package.json is also worthwhile. In particular, +it is worth specifying: + +* `repository`: if your package is open source, the repo you can find it in. + If it's hosted on github, `"username/repo-name"` works; otherwise a git + URL. +* `license`: the license the package is licensed under. If you're not sure, + try [choosealicense.com](http://choosealicense.com). You will also want to + include a copy of the full license text in a file called LICENSE. +* `description`: a one-sentence description of the package. + +For more details on package.json, check out +[npm's package.json documentation](https://docs.npmjs.com/getting-started/using-a-package.json). + + diff --git a/devsite/source/_guides/pebble-packages/index.md b/devsite/source/_guides/pebble-packages/index.md new file mode 100644 index 00000000..4b91ee06 --- /dev/null +++ b/devsite/source/_guides/pebble-packages/index.md @@ -0,0 +1,36 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Packages +description: | + Details on how to create and use Pebble Packages +guide_group: pebble-packages +menu: false +permalink: /guides/pebble-packages/ +generate_toc: false +hide_comments: true +--- + +It is very common to want to use some piece of common functionality that +we do not provide directly in our SDK: for instance, show the weather or +swap our colors in a bitmap. + +To provide this functionality, developers can create _Pebble Packages_, +which provide developers with easy ways to share their code. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/pebble-packages/using-packages.md b/devsite/source/_guides/pebble-packages/using-packages.md new file mode 100644 index 00000000..6d249f38 --- /dev/null +++ b/devsite/source/_guides/pebble-packages/using-packages.md @@ -0,0 +1,136 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Using Pebble Packages +description: How to use Pebble Packages +permalink: /guides/pebble-packages/using-packages/ +generate_toc: true +guide_group: pebble-packages +--- + +## Getting started + +Using pebble packages is easy: + +1. Find a package. We will have a searchable listing soon, but for now you + can [browse the pebble-package keyword on npm](https://www.npmjs.com/browse/keyword/pebble-package). +2. Run `pebble package install pebble-somelib` to install pebble-somelib. +3. Use the package. + +It is possible to use _some_ standard npm packages. However, packages that +depend on being run in node, or in a real web browser, are likely to fail. If +you install an npm package, you can use it in the usual manner, as described +below. + +### C code + +Packages should document their specific usage. However, in general, +for C packages you can include their headers and call them like so: + +```c +#include + +int main() { + somelib_do_the_thing(); +} +``` + +All of the package's include files will be in a folder named after the package. +Packages may have any structure inside that folder, so you are advised to +read their documentation. + +{% alert notice %} +**Tip**: If you want to use an +``Event Service``, +you should use the +[pebble-events](https://www.npmjs.com/package/pebble-events) package to +avoid conflicting with handlers registered by packages. +{% endalert %} + +### JavaScript code + +JavaScript packages are used via the `require` function. In most cases you can +just `require` the package by name: + +```js +var somelib = require('pebble-somelib'); + +somelib.doTheThing(); +``` + +### Resources + +If the package you are using has included image resources, you can reference +them directly using their `RESOURCE_ID_*` identifiers. + +```c +static GBitmap *s_image_01; +s_image_01 = gbitmap_create_with_resource(RESOURCE_ID_MEDIA_PACKAGE_IMAGE_01_TINY); +``` + +### Published Media + +If the package you are using has defined `publishedMedia` resources, you can +either reference the resources using their resource identifier (as above), or +you can create an alias within the `package.json`. The `name` you specify in +your own project can be used to reference that `publishedMedia` item for +AppGlances and Timeline pins, eg. `PUBLISHED_ID_` + +For example, if the package exposes the following `publishedMedia`: + +```javascript +"resources": { + //... + "publishedMedia": [ + { + "name": "MEDIA_PACKAGE_IMAGE_01", + "glance": "MEDIA_PACKAGE_IMAGE_01_TINY", + "timeline": { + "tiny": "MEDIA_PACKAGE_IMAGE_01_TINY", + "small": "MEDIA_PACKAGE_IMAGE_01_SMALL", + "large": "MEDIA_PACKAGE_IMAGE_01_LARGE" + } + } + ] +} +``` + +You could define the following `name` and `alias` with a unique `id` in your +`package.json`: + +```javascript +"resources": { + //... + "publishedMedia": [ + { + "name": "SHARED_IMAGE_01", + "id": 1, + "alias": "MEDIA_PACKAGE_IMAGE_01" + } + ] +} +``` + +You can then proceed to use that `name`, prefixed with `PUBLISHED_ID_`, within +your code: + +```c +const AppGlanceSlice entry = (AppGlanceSlice) { + .layout = { + .icon = PUBLISHED_ID_SHARED_IMAGE_01, + .subtitle_template_string = "message" + } +}; +``` diff --git a/devsite/source/_guides/pebble-timeline/index.md b/devsite/source/_guides/pebble-timeline/index.md new file mode 100644 index 00000000..293c377d --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/index.md @@ -0,0 +1,85 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Timeline +description: | + How to use Pebble timeline to bring timely information to app users outside + the app itself via web services. +guide_group: pebble-timeline +permalink: /guides/pebble-timeline/ +generate_toc: false +menu: false +related_examples: + - title: Timeline Push Pin + url: https://github.com/pebble-examples/timeline-push-pin + - title: Hello Timeline + url: https://github.com/pebble-examples/hello-timeline + - title: Timeline TV Tracker + url: https://github.com/pebble-examples/timeline-tv-tracker +hide_comments: true +--- + +The Pebble timeline is a system-level display of chronological events that apps +can insert data into to deliver user-specific data, events, notifications and +reminders. These items are called pins and are accessible outside the running +app, but are deeply associated with an app the user has installed on their +watch. + +Every user can view their personal list of pins from the main watchface by +pressing Up for the past and Down for the future. Examples of events the user +may see include weather information, calendar events, sports scores, news items, +and notifications from any web-based external service. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} + + +## Enabling a New App + +To push pins via the Pebble timeline API, a first version of a new app must be +uploaded to the [Developer Portal](https://dev-portal.getpebble.com). This is +required so that the appstore can identify the app's UUID, and so generate +sandbox and production API keys for the developer to push pins to. It is then +possible to use the timeline web API in sandbox mode for development or in +production mode for published apps. + +1. In the Developer Portal, go to the watchapp's details page in the 'Dashboard' + view and click the 'Enable timeline' button. + +2. To obtain API keys, click the 'Manage Timeline Settings' button at the + top-right of the page. New API keys can also be generated from this page. If + required, users with sandbox mode access can also be whitelisted here. + + +## About Sandbox Mode + +The sandbox mode is automatically used when the app is sideloaded using the SDK. +By default, sandbox pins will be delivered to all users who sideload a PBW. + +The production mode is used when a user installs the app from the Pebble +appstore. Use the two respective API key types for these purposes. If +whitelisting is enabled in sandbox mode, the developer's account is +automatically included, and they can add more Pebble users by adding the users' +email addresses in the [Developer Portal](https://dev-portal.getpebble.com). + +If preferred, it is possible to enable whitelisting to limit this access to only +users involved in development and testing of the app. Enter the email addresses +of users to be authorized to use the app's timeline in sandbox mode on the +'Manage Timeline Settings' page of an app listing. + +> When whitelisting is enabled, the `Pebble.getTimelineToken()` will return an +> error for users who are not in the whitelist. diff --git a/devsite/source/_guides/pebble-timeline/pin-structure.md b/devsite/source/_guides/pebble-timeline/pin-structure.md new file mode 100644 index 00000000..dcd2b317 --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/pin-structure.md @@ -0,0 +1,965 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Creating Pins +description: | + How to create timeline pins with reminders, actions, and layouts. +guide_group: pebble-timeline +order: 0 +--- + +A timeline pin contains all the information required to be displayed on the +watch, and is written in the JSON object format. It can contain basic +information such as title and times, or more advanced data such as +notifications, reminders, or actions that can be used out from the pin view. + + +## Pin Overview + +The table below details the pin object fields and their function within the +object. Those marked in **bold** are required. + +| Field | Type | Function | +|-------|------|----------| +| **`id`** | String (max. 64 chars) | Developer-implemented identifier for this pin event, which cannot be re-used. This means that any pin that was previously deleted cannot then be re-created with the same `id`. | +| **`time`** | String ([ISO date-time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)) | The start time of the event the pin represents, such as the beginning of a meeting. See {% guide_link pebble-timeline/timeline-public#pin-time-limitations "Pin Time Limitations" %} for information on the acceptable time range. | +| `duration` | Integer number | The duration of the event the pin represents, in minutes. | +| `createNotification` | [Notification object](#notification-object) | The notification shown when the event is first created. | +| `updateNotification` | [Notification object](#notification-object) | The notification shown when the event is updated but already exists. | +| **`layout`** | [Layout object](#layout-object) | Description of the values to populate the layout when the user views the pin. | +| `reminders` | [Reminder object](#reminder-object) array (Max. 3) | Collection of event reminders to display before an event starts. | +| `actions` | [Action object](#action-object) array | Collection of event actions that can be executed by the user. | + + +### Notification Object + +The notification objects used for `createNotification` and `updateNotification` +contain only one common field: a Layout object describing the visual properties +of the notification. + +The other field (`time`) is used only in an `updateNotification` object. The +`createNotification` type does **not** require a `time` attribute. + +| Field | Type | Function | +|-------|------|----------| +| `layout` | [Layout object](#layout-object) | The layout that will be used to display this notification. | +| `time` | String (ISO date-time) | The new time of the pin update. | + +The `createNotification` is shown when the pin is first delivered to the watch. + +The `updateNotification` is shown when the pin already existed and is being +updated on the watch. It will only be shown if the `updateNotification.time` is +newer than the last `updateNotification.time` received by the watch. + +Using these fields, developers can build a great experience for their users when +live updating pins. For example, when sending updates about a sports game the +app could use the `createNotification` to tell the user that "The game has just +been added to your timeline" and the `updateNotification` to tell them that "The +game starting time has just been updated in your timeline.". + + +### Layout Object + +The Layout object is used to describe any message shown in a customizable +layout. This includes a pin in the timeline, a notification, and also reminders. +Developers can choose between different layout types and customize them with +attributes. + +Required fields are shown in **bold**. Some layout types have additional +required fields not shown below, but listed in their dedicated sections. Values +for icon URIs can be found below under [Pin Icons](#pin-icons), although not all +icons are available at all sizes. + +| Field | Type | Function | +|-------|------|----------| +| **`type`** | String | The type of layout the pin will use. See [*Pin Layouts*](#pin-layouts) for a list of available types. | +| `title` | String | The title of the pin when viewed. | +| `subtitle` | String | Shorter subtitle for details. | +| `body` | String | The body text of the pin. Maximum of 512 characters. | +| `tinyIcon` | String | URI of the pin's tiny icon. | +| `smallIcon` | String | URI of the pin's small icon. | +| `largeIcon` | String | URI of the pin's large icon. | + +The following attributes are also available for all pin layout types +**(excluding notifications and reminders)**. + +| Field | Type | Function | +|-------|------|----------| +| `primaryColor` | String | Six-digit color hexadecimal string or case-insensitive SDK constant (e.g.: "665566" or "mintgreen"), describing the primary text color. | +| `secondaryColor` | String | Similar to `primaryColor`, except applies to the layout's secondary-colored elements. | +| `backgroundColor` | String | Similar to `primaryColor`, except applies to the layout's background color. | +| `headings` | Array of Strings | List of section headings in this layout. The list must be less than 128 characters in length, including the underlying delimiters (one byte) between each item. Longer items will be truncated with an ellipsis ('...'). | +| `paragraphs` | Array of Strings | List of paragraphs in this layout. **Must equal the number of `headings`**. The list must be less than 1024 characters in length, including the underlying delimiters (one byte) between each item. Longer items will be truncated with an ellipsis ('...'). | +| `lastUpdated` | ISO date-time | Timestamp of when the pin’s data (e.g: weather forecast or sports score) was last updated. | + + +### Reminder Object + +Reminders are synchronized to the watch and will be shown at the precise time +set in the reminder. They work even when Pebble is disconnected from the +user's mobile phone. + +| Field | Type | Function | +|-------|------|----------| +| `time` | String (ISO date-time) | The time the reminder is scheduled to be shown. | +| `layout` | [Layout object](#layout-object) | The layout of the reminder. | + + +### Action Object + +| Field | Type | Function | +|-------|------|----------| +| `title` | String | The name of the action that appears on the watch. | +| `type` | String | The type of action this will execute. See [*Pin Actions*](#pin-actions) for a list of available actions. | + + +## Minimal Pin Example + +The example pin object shown below includes only the required fields for a +generic pin. + +```json +{ + "id": "example-pin-generic-1", + "time": "2015-03-19T18:00:00Z", + "layout": { + "type": "genericPin", + "title": "News at 6 o'clock", + "tinyIcon": "system://images/NOTIFICATION_FLAG" + } +} +``` + + +## Complete Pin Example + +Below is a more advanced example pin object: + +```json +{ + "id": "meeting-453923", + "time": "2015-03-19T15:00:00Z", + "duration": 60, + "createNotification": { + "layout": { + "type": "genericNotification", + "title": "New Item", + "tinyIcon": "system://images/NOTIFICATION_FLAG", + "body": "A new appointment has been added to your calendar at 4pm." + } + }, + "updateNotification": { + "time": "2015-03-19T16:00:00Z", + "layout": { + "type": "genericNotification", + "tinyIcon": "system://images/NOTIFICATION_FLAG", + "title": "Reminder", + "body": "The meeting has been rescheduled to 4pm." + } + }, + "layout": { + "title": "Client Meeting", + "type": "genericPin", + "tinyIcon": "system://images/TIMELINE_CALENDAR", + "body": "Meeting in Kepler at 4:00pm. Topic: discuss pizza toppings for party." + }, + "reminders": [ + { + "time": "2015-03-19T14:45:00Z", + "layout": { + "type": "genericReminder", + "tinyIcon": "system://images/TIMELINE_CALENDAR", + "title": "Meeting in 15 minutes" + } + }, + { + "time": "2015-03-19T14:55:00Z", + "layout": { + "type": "genericReminder", + "tinyIcon": "system://images/TIMELINE_CALENDAR", + "title": "Meeting in 5 minutes" + } + } + ], + "actions": [ + { + "title": "View Schedule", + "type": "openWatchApp", + "launchCode": 15 + }, + { + "title": "Show Directions", + "type": "openWatchApp", + "launchCode": 22 + } + ] +} +``` + + +## View Modes + +When viewing pins in the timeline, they can be displayed in two different ways. + +| State | Preview | Details | +|-------|---------|---------| +| Selected | ![](/images/guides/timeline/timeline-selected.png) | Three lines of text shown from the title, location and sender. | +| Not selected | ![](/images/guides/timeline/timeline-one-line.png) | Time, short title, and icon are shown. | + + +## Pin Icons + +The tables below detail the available icons provided by the system. Each icon +can be used when pushing a pin in the following manner: + +``` +"layout": { + "type": "genericNotification", + "title": "Example Pin", + "tinyIcon": "system://images/NOTIFICATION_FLAG" +} +``` + +> For general use in watchapps, PDC files are available for these icons in +> {% guide_link app-resources/app-assets#pebble-timeline-pin-icons %}. + + +### Notifications + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/NOTIFICATION_GENERIC.svg =25) | `NOTIFICATION_GENERIC` | Generic notification | +| ![](/images/guides/timeline/NOTIFICATION_REMINDER.svg =25) | `NOTIFICATION_REMINDER` | Reminder notification | +| ![](/images/guides/timeline/NOTIFICATION_FLAG.svg =25) | `NOTIFICATION_FLAG` | Generic notification flag | +| ![](/images/guides/timeline/NOTIFICATION_LIGHTHOUSE.svg =25) | `NOTIFICATION_LIGHTHOUSE` | Generic lighthouse | + + +### Generic + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/GENERIC_EMAIL.svg =25) | `GENERIC_EMAIL` | Generic email | +| ![](/images/guides/timeline/GENERIC_SMS.svg =25) | `GENERIC_SMS` | Generic SMS icon | +| ![](/images/guides/timeline/GENERIC_WARNING.svg =25) | `GENERIC_WARNING` | Generic warning icon | +| ![](/images/guides/timeline/GENERIC_CONFIRMATION.svg =25) | `GENERIC_CONFIRMATION` | Generic confirmation icon | +| ![](/images/guides/timeline/GENERIC_QUESTION.svg =25) | `GENERIC_QUESTION` | Generic question icon | + + +### Weather + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/PARTLY_CLOUDY.svg =25) | `PARTLY_CLOUDY` | Partly cloudy weather | +| ![](/images/guides/timeline/CLOUDY_DAY.svg =25) | `CLOUDY_DAY` | Cloudy weather | +| ![](/images/guides/timeline/LIGHT_SNOW.svg =25) | `LIGHT_SNOW` | Light snow weather | +| ![](/images/guides/timeline/LIGHT_RAIN.svg =25) | `LIGHT_RAIN` | Light rain weather | +| ![](/images/guides/timeline/HEAVY_RAIN.svg =25) | `HEAVY_RAIN` | Heavy rain weather icon | +| ![](/images/guides/timeline/HEAVY_SNOW.svg =25) | `HEAVY_SNOW` | Heavy snow weather icon | +| ![](/images/guides/timeline/TIMELINE_WEATHER.svg =25) | `TIMELINE_WEATHER` | Generic weather icon | +| ![](/images/guides/timeline/TIMELINE_SUN.svg =25) | `TIMELINE_SUN` | Sunny weather icon | +| ![](/images/guides/timeline/RAINING_AND_SNOWING.svg =25) | `RAINING_AND_SNOWING` | Raining and snowing weather icon | +| ![](/images/guides/timeline/SUNRISE.svg =25) | `SUNRISE` | Sunrise weather icon | +| ![](/images/guides/timeline/SUNSET.svg =25) | `SUNSET` | Sunset weather icon | + + +### Timeline + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/TIMELINE_MISSED_CALL.svg =25) | `TIMELINE_MISSED_CALL` | Generic missed call icon | +| ![](/images/guides/timeline/TIMELINE_CALENDAR.svg =25) | `TIMELINE_CALENDAR` | Generic calendar event icon | +| ![](/images/guides/timeline/TIMELINE_SPORTS.svg =25) | `TIMELINE_SPORTS` | Generic sports icon | + + +### Sports + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/TIMELINE_BASEBALL.svg =25) | `TIMELINE_BASEBALL` | Baseball sports icon | +| ![](/images/guides/timeline/AMERICAN_FOOTBALL.svg =25) | `AMERICAN_FOOTBALL` | American football sports icon | +| ![](/images/guides/timeline/BASKETBALL.svg =25) | `BASKETBALL` | Basketball sports icon | +| ![](/images/guides/timeline/CRICKET_GAME.svg =25) | `CRICKET_GAME` | Cricket sports icon | +| ![](/images/guides/timeline/SOCCER_GAME.svg =25) | `SOCCER_GAME` | Soccer sports icon | +| ![](/images/guides/timeline/HOCKEY_GAME.svg =25) | `HOCKEY_GAME` | Hockey sports icon | + + +### Action Results + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/RESULT_DISMISSED.svg =25) | `RESULT_DISMISSED` | Dismissed event | +| ![](/images/guides/timeline/RESULT_DELETED.svg =25) | `RESULT_DELETED` | Deleted event | +| ![](/images/guides/timeline/RESULT_MUTE.svg =25) | `RESULT_MUTE` | Mute event | +| ![](/images/guides/timeline/RESULT_SENT.svg =25) | `RESULT_SENT` | Generic message sent event | +| ![](/images/guides/timeline/RESULT_FAILED.svg =25) | `RESULT_FAILED` | Generic failure event | + + +### Events + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/STOCKS_EVENT.svg =25) | `STOCKS_EVENT` | Stocks icon | +| ![](/images/guides/timeline/MUSIC_EVENT.svg =25) | `MUSIC_EVENT` | Music event | +| ![](/images/guides/timeline/BIRTHDAY_EVENT.svg =25) | `BIRTHDAY_EVENT` | Birthday event | +| ![](/images/guides/timeline/NEWS_EVENT.svg =25) | `NEWS_EVENT` | Generic news story event | +| ![](/images/guides/timeline/SCHEDULED_EVENT.svg =25) | `SCHEDULED_EVENT` | Generic scheduled event | +| ![](/images/guides/timeline/MOVIE_EVENT.svg =25) | `MOVIE_EVENT` | Generic movie icon | +| ![](/images/guides/timeline/NO_EVENTS.svg =25) | `NO_EVENTS` | No events icon | + + +### Miscellaneous + +| Preview | Name | Description | +|---------|------|-------------| +| ![](/images/guides/timeline/PAY_BILL.svg =25) | `PAY_BILL` | Pay bill event | +| ![](/images/guides/timeline/HOTEL_RESERVATION.svg =25) | `HOTEL_RESERVATION` | Hotel event | +| ![](/images/guides/timeline/TIDE_IS_HIGH.svg =25) | `TIDE_IS_HIGH` | High tide event | +| ![](/images/guides/timeline/INCOMING_PHONE_CALL.svg =25) | `INCOMING_PHONE_CALL` | Incoming phone call event | +| ![](/images/guides/timeline/DURING_PHONE_CALL.svg =25) | `DURING_PHONE_CALL` | Phone call event | +| ![](/images/guides/timeline/DURING_PHONE_CALL_CENTERED.svg =25) | `DURING_PHONE_CALL_CENTERED` | Phone call event centered | +| ![](/images/guides/timeline/DISMISSED_PHONE_CALL.svg =25) | `DISMISSED_PHONE_CALL` | Phone call dismissed event | +| ![](/images/guides/timeline/CHECK_INTERNET_CONNECTION.svg =25) | `CHECK_INTERNET_CONNECTION` | Check Internet connection event | +| ![](/images/guides/timeline/GLUCOSE_MONITOR.svg =25) | `GLUCOSE_MONITOR` | Sensor monitor event | +| ![](/images/guides/timeline/ALARM_CLOCK.svg =25) | `ALARM_CLOCK` | Alarm clock event | +| ![](/images/guides/timeline/CAR_RENTAL.svg =25) | `CAR_RENTAL` | Generic car rental event | +| ![](/images/guides/timeline/DINNER_RESERVATION.svg =25) | `DINNER_RESERVATION` | Dinner reservation event | +| ![](/images/guides/timeline/RADIO_SHOW.svg =25) | `RADIO_SHOW` | Radio show event | +| ![](/images/guides/timeline/AUDIO_CASSETTE.svg =25) | `AUDIO_CASSETTE` | Audio cassette icon | +| ![](/images/guides/timeline/SCHEDULED_FLIGHT.svg =25) | `SCHEDULED_FLIGHT` | Scheduled flight event | +| ![](/images/guides/timeline/REACHED_FITNESS_GOAL.svg =25) | `REACHED_FITNESS_GOAL` | Reached fitness goal event | +| ![](/images/guides/timeline/DAY_SEPARATOR.svg =25) | `DAY_SEPARATOR` | Day separator icon | +| ![](/images/guides/timeline/WATCH_DISCONNECTED.svg =25) | `WATCH_DISCONNECTED` | Watch disconnected event | +| ![](/images/guides/timeline/TV_SHOW.svg =25) | `TV_SHOW` | Generic TV show icon | +| ![](/images/guides/timeline/LOCATION.svg =25) | `LOCATION` | Generic location icon | +| ![](/images/guides/timeline/SETTINGS.svg =25) | `SETTINGS` | Generic settings icon | + + +### Custom Icons + +Custom icons were introduced in SDK 4.0. They allow you to use custom images for +timeline pins, by utilizing the +{% guide_link tools-and-resources/app-metadata#published-media "Published Media" %} +`name`. E.g. `app://images/*name*` + + +## Pin Layouts + +Developers can customize how pins, reminders and notifications are shown to the +user using different layouts. The Pebble SDK includes layouts appropriate for a +broad set of apps. Each layout has different customization options, called the +layout attributes. Most layouts also offer the option of showing an icon, which +must be one of the standard system provided icons, listed under +[*Pin Icons*](#pin-icons) above. + +The sub-sections below detail the available layouts and the fields they will +display. Required attributes are shown in **bold**. + + +### Generic Layout + +Generic layout for generic pins of no particular type. + +**Timeline view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/generic-pin.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Detail view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/generic-layout.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`**, **`tinyIcon`**, `subtitle`, `body`. + +**Color Elements** + +| Layout Property | Applies To | +|-----------------|------------| +| `primaryColor` | Time, body | +| `secondaryColor` | Title | +| `backgroundColor` | Background | + +**Example JSON** + +```json +{ + "id": "pin-generic-1", + "time": "2015-09-22T16:30:00Z", + "layout": { + "type": "genericPin", + "title": "This is a genericPin!", + "tinyIcon": "system://images/NOTIFICATION_FLAG", + "primaryColor": "#FFFFFF", + "secondaryColor": "#666666", + "backgroundColor": "#5556FF" + } +} +``` + + +### Calendar Layout + +Standard layout for pins displaying calendar events. + +**Timeline view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/calendar-pin.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Detail view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/calendar-layout.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`**, `body`. + +**Special Attributes** + +| Field | Type | Function | +|-------|------|----------| +| `locationName` | String | Name of the location of this pin event. Used if `shortSubtitle` is not present on the list view, and always in the detail view. | + +**Color Elements** + +| Layout Property | Applies To | +|-----------------|------------| +| `primaryColor` | Times, body | +| `secondaryColor` | Title | +| `backgroundColor` | Background | + +**Example JSON** + +```json +{ + "id": "pin-calendar-1", + "time": "2015-03-18T15:45:00Z", + "duration": 60, + "layout": { + "type": "calendarPin", + "title": "Pin Layout Meeting", + "locationName": "Conf Room 1", + "body": "Discuss layout types with Design Team." + } +} +``` + + +### Sports Layout + +Generic layout for displaying sports game pins including team ranks, scores +and records. + +**Timeline view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/sport-pin.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Detail view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/sport-layout.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`** (name of the game), `subtitle` (friendly name of the period), `body` +(game description), **`tinyIcon`**, `largeIcon`, `lastUpdated`. + +**Special Attributes** + +> Note: The `rankAway` and `rankHome` fields will be shown before the event +> begins, otherwise `scoreAway` and `scoreHome` will be shown. + +| Field | Type | Function | +|-------|------|----------| +| `rankAway` | String (~2 characters) | The rank of the away team. | +| `rankHome` | String (~2 characters) | The rank of the home team. | +| `nameAway` | String (Max 4 characters) | Short name of the away team. | +| `nameHome` | String (Max 4 characters) | Short name of the home team. | +| `recordAway` | String (~5 characters) | Record of the away team (wins-losses). | +| `recordHome` | String (~5 characters) | Record of the home team (wins-losses). | +| `scoreAway` | String (~2 characters) | Score of the away team. | +| `scoreHome` | String (~2 characters) | Score of the home team. | +| `sportsGameState` | String | `in-game` for in game or post game, `pre-game` for pre game. | + +**Color Elements** + +| Layout Property | Applies To | +|-----------------|------------| +| `primaryColor` | Text body | +| `secondaryColor` | Team names and scores | +| `backgroundColor` | Background | + +**Example JSON** + +```json +{ + "id": "pin-sports-1", + "time": "2015-03-18T19:00:00Z", + "layout": { + "type": "sportsPin", + "title": "Bulls at Bears", + "subtitle": "Halftime", + "body": "Game of the Century", + "tinyIcon": "system://images/AMERICAN_FOOTBALL", + "largeIcon": "system://images/AMERICAN_FOOTBALL", + "lastUpdated": "2015-03-18T18:45:00Z", + "rankAway": "03", + "rankHome": "08", + "nameAway": "POR", + "nameHome": "LAC", + "recordAway": "39-19", + "recordHome": "39-21", + "scoreAway": "54", + "scoreHome": "49", + "sportsGameState": "in-game" + } +} +``` + + +### Weather Layout + +Standard layout for pins displaying the weather. + +**Timeline view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/weather-pin.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Detail view** + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/weather-layout.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`** (part of the day), **`tinyIcon`**, `largeIcon`, `body` +(shortcast), `lastUpdated`. + +**Special Attributes** + +| Field | Type | Function | +|-------|------|----------| +| `shortTitle` | String | Used instead of `title` in the main timeline view unless it is not specified. | +| `subtitle` | String | Show high/low temperatures. Note: currently only numbers and the degree symbol (°) are supported. | +| `shortSubtitle` | String | Used instead of `subtitle` in the main timeline view unless it is not specified. | +| **`locationName`** | String | Name of the location of this pin event. | +| `displayTime` | String | Use a value of 'pin' to display the pin's time in title of the detail view and description, or 'none' to not show the time. Defaults to 'pin' if not specified. | + +**Color Elements** + +| Layout Property | Applies To | +|-----------------|------------| +| `primaryColor` | All text | +| `backgroundColor` | Background | + +**Example JSON** + +```json +{ + "id": "pin-weather-1", + "time": "2015-03-18T19:00:00Z", + "layout": { + "type": "weatherPin", + "title": "Nice day", + "subtitle": "40/65", + "tinyIcon": "system://images/TIMELINE_SUN", + "largeIcon": "system://images/TIMELINE_SUN", + "locationName": "Palo Alto", + "body": "Sunny with a chance of rain.", + "lastUpdated": "2015-03-18T18:00:00Z" + } +} +``` + + +### Generic Reminder + +Generic layout for pin reminders, which can be set at various times before an +event is due to occur to remind the user ahead of time. + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/generic-reminder.png", + "platforms": [ + {"hw": "aplite", "wrapper": "black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`**, **`tinyIcon`**. + +**Special Attributes** + +| Field | Type | Function | +|-------|------|----------| +| `locationName` | String | Name of the location of this pin event. | + +**Example JSON** + +```json +{ + "id": "pin-generic-reminder-1", + "time": "2015-03-18T23:00:00Z", + "layout": { + "type": "genericPin", + "title": "This is a genericPin!", + "subtitle": "With a reminder!.", + "tinyIcon": "system://images/NOTIFICATION_FLAG" + }, + "reminders": [ + { + "time": "2015-03-18T22:55:00Z", + "layout": { + "type": "genericReminder", + "title": "Reminder!", + "locationName": "Conf Rm 1", + "tinyIcon": "system://images/ALARM_CLOCK" + } + } + ] +} +``` + + +### Generic Notification + +Generic notification layout which can be used with `createNotification` and +`updateNotification` to alert the user to a new pin being created on their +timeline. + +{% screenshot_viewer %} +{ + "image": "/images/guides/timeline/generic-notification-layout.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +**Standard Attributes** + +**`title`**, **`tinyIcon`**, `body`. + +**Color Elements** + +| Layout Property | Applies To | +|-----------------|------------| +| `primaryColor` | Title | +| `backgroundColor` | Banner background | + +**Example JSON** + +```json +{ + "id": "pin-generic-createmessage-1", + "time": "2015-04-30T23:45:00Z", + "layout": { + "type": "genericPin", + "title": "This is a genericPin!", + "subtitle": "With a notification", + "tinyIcon": "system://images/NOTIFICATION_FLAG" + }, + "createNotification": { + "layout": { + "type": "genericNotification", + "title": "Notification!", + "tinyIcon": "system://images/NOTIFICATION_FLAG", + "body": "A new genericPin has appeared!" + } + } +} + +``` + + +## Pin Actions + +Pins can be further customized by adding actions to them. This allows bi- +directional interactivity for pin-based apps. These apps can have multiple +actions associated with them, allowing different launch behavior depending on +how the user interacts with the pin. + +The table below shows the available actions that can be added to a pin. Required +attributes are shown in **bold**. + +| Action `type` | Description | Attributes | +|---------------|-------------|------------| +| `openWatchApp` | Launch the watchapp associated with this pin. The `launchCode` field of this action object will be passed to the watchapp and can be obtained with ``launch_get_args()``. | **`title`**, **`launchCode`**. | +| `http` | Execute an HTTP request that invokes this action on the remote service. | See [*HTTP Actions*](#http-actions) for full attribute details. | + + +### Using a Launch Code + +Launch codes can be used to pass a single integer value from a specific timeline +pin to the app associated with it when it is lauched from that pin. This +mechanism allows the context to be given to the app to allow it to change +behavior based on the action chosen. + +For example, a pin could have two actions associated with an app for making +restaurant table reservations that allowed the user to cancel the reservation or +review the restaurant. To set up these actions, add them to the pin when it is +pushed to the timeline API. + +``` +"actions": [ + { + "title": "Cancel Table", + "type": "openWatchApp", + "launchCode": 15 + }, + { + "title": "Leave Rating", + "type": "openWatchApp", + "launchCode": 22 + } +] +``` + + +### Reading the Launch Code + +When the user sees the pin and opens the action menu, they can select one of +these actions which will launch the watchapp (as dictated by the `openWatchApp` +pin action `type`). When the app launches, use ``launch_get_args()`` to read the +value of the `launchCode` associated with the chosen action, and react +accordingly. An example is shown below; + +```c +if(launch_reason() == APP_LAUNCH_TIMELINE_ACTION) { + uint32_t arg = launch_get_args(); + + switch(arg) { + case LAUNCH_ARG_CANCEL: + // Cancel table UI... + + break; + case LAUNCH_ARG_REVIEW: + // Leave a review UI... + + break; + } +} +``` + + +### HTTP Actions + +With the `http` pin action `type`, pins can include actions that carry out an +arbitrary HTTP request. This makes it possible for a web service to be used +purely by pushed pins with actions that respond to those events. + +The table below details the attributes of this type of pin action object. Items +shown in **bold** are required. + +| Attribute | Type | Default | Description | +|-----------|------|---------|-------------| +| **`title`** | String | *mandatory* | The title of the action. | +| **`url`** | String | *mandatory* | The URL of the remote service to send the request to. | +| `method` | String | `POST` | The request method, such as `GET`, `POST`, `PUT` or `DELETE`. | +| `headers` | Object | `{}` | Dictionary of key-value pairs of headers (`Content-Type` is implied by using `bodyJSON`) as required by the remote service. | +| `bodyText` | String | `''` | The data body of the request in String format. | +| `bodyJSON` | Object | *unspecified* | The data body of the request in JSON object format. | +| `successText` | String | "Done!" | The string to display if the action is successful. | +| `successIcon` | Pin Icon URL | `system://images/GENERIC_CONFIRMATION` | The icon to display if the action is successful. | +| `failureText` | String | "Failed!" | The string to display if the action is unsuccessful. | +| `failureIcon` | Pin Icon URL | `system://images/RESULT_FAILED` | The icon to display if the action is unsuccessful. | + +> Note: `bodyText` and `bodyJSON` are mutually exclusive fields (they cannot be +> used together in the same request). You should choose that which is most +> convenient for your implementation. + +> Note: Do not include a body with HTTP methods that do not support one. This +> means that `bodyText` and `bodyJSON` cannot be used with `GET` or `DELETE` +> requests. + +The following is an example action, using the `http` action `type` to confirm +attendance at a meeting managed by a fictitious meeting scheduling service. + +```js +"actions": [ + { + "type": "http", + "title": "Confirm Meeting", + "url": "http://some-meeting-service.com/api/v1/meetings/46146717", + "method": "PUT", + "headers": { + "X-Request-Source": "pebble-timeline", + "Content-Type": "application/x-www-form-urlencoded" + }, + "bodyText": "type=confirm&value=1", + "successIcon": "system://images/GENERIC_CONFIRMATION", + "successText": "Confirmed!" + } +] +``` + +Alternatively, pins can use the `bodyJSON` field to encode a JSON object. +Include this data using the `bodyJSON` field. + +```js +"actions": [ + { + "type": "http", + "title": "Confirm Meeting", + "url": "http://some-meeting-service.com/api/v1/meetings/46146717", + "method": "PUT", + "headers": { + "X-Request-Source": "pebble-timeline" + }, + "bodyJSON": { + "type": "confirm", + "value": true + }, + "successIcon": "system://images/GENERIC_CONFIRMATION", + "successText": "Confirmed!" + } +] +``` + + +### Included Headers + +When using the `http` action, the request will also include the following +additional headers. Developers can use these to personalize the timeline +experience to each individual user. + +| Header Key | Value | +|------------|-------| +| `X-Pebble-Account-Token` | Same as [`Pebble.getAccountToken()`](/guides/communication/using-pebblekit-js) | +| `X-Pebble-Watch-Token` | Same as [`Pebble.getWatchToken()`](/guides/communication/using-pebblekit-js) | + + +## Testing Pins + +**Using CloudPebble** + +When editing a CloudPebble project, developers can test inserting and deleting +any pin using the 'Timeline' tab at the top left of the screen. Use the text +field to construct the pin, then one of the two buttons to test it out. + +> Note: Once a pin with a specific `id` has been deleted, that `id` cannot be +> reused. + +![](/images/guides/timeline/cloudpebble-ui.png) + +**Push Pins with the Pebble Tool** + +It is also possible to push new timeline pins using the `pebble` +{% guide_link tools-and-resources/pebble-tool %}. Prepare your pin in a JSON +file, such as `example-pin.json` shown below: + +```json +{ + "id": "pin-generic-1", + "time": "2015-03-18T15:45:00Z", + "layout": { + "type": "genericPin", + "title": "This is a genericPin!", + "tinyIcon": "system://images/NOTIFICATION_FLAG" + } +} +``` + +Push this pin to your emulator to preview how it will appear for users. + +```nc|bash +$ pebble insert-pin example-pin.json +``` + +The pin will appear as shown below: + +![pin-preview >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/timeline/generic-pin~basalt.png) + +It is possible to delete the pin in a similar manner, making sure the `id` is +the same as the pin to be removed: + +```nc|bash +$ pebble delete-pin --id pin-generic-1 +``` diff --git a/devsite/source/_guides/pebble-timeline/timeline-architecture.md b/devsite/source/_guides/pebble-timeline/timeline-architecture.md new file mode 100644 index 00000000..0b000bce --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/timeline-architecture.md @@ -0,0 +1,146 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Service Architecture +description: | + Find out what the timeline is, how it works and how developers can take + advantage of it in their apps. +guide_group: pebble-timeline +order: 4 +--- + +Every item on the timeline is called a 'pin'. A pin can have information +attached to it which is used to show it to the user, such as layout, title, +start time and actions. When the user is viewing their timeline, they can use +the information provided about the immediate past and future to make decisions +about how they plan the rest of their day and how to respond to any missed +notifications or events. + +While the system and mobile application populate the user's timeline +automatically with items such as calendar appointments and weather details, the +real value of the timeline is realized by third party apps. For example, a +sports app could show a pin representing an upcoming match and the user could +tell how much time remained until they needed to be home to watch it. + +Developers can use a watchapp to subscribe their users to one of two types pin: + +* Personal pins pushed to a user's timeline token. These pins are only delivered + to the user the token belongs to, allows a high degree of personalization in + the pin's contents. + +* Channels called 'topics' (one more more, depending on their preferences), + allow an app to push pins to a large number of users at once. Topics are + created on a per-app basis, meaning a user receiving pins for a 'football' + topic from one app will not receive pins from another app using the same topic + name. + +Developers can use the PebbleKit JS API to combine the user's preferences with +their location and data from other web-connected sources to make pin updates +more personal, or more intelligently delivered. + +The public timeline web API is used to push data from a own backend server to +app users according to the topics they have subscribed to, or to individual +users. Methods for doing this are discussed below under +[Three Ways to Use Pins](#three-ways-to-use-pins). + + +## Architecture Overview + +![diagram](/images/guides/3.0/timeline-architecture.png) + +The timeline architecture consists of multiple components that work together to +bring timely and relevant data, events, and notifications to the user without +their intervention. These components are discussed in more detail below. + + +### Public Web API + +The Pebble timeline web API (detailed in +{% guide_link pebble-timeline/timeline-public %}) manages the currently +available topics, published pins, and timeline-enabled apps' data. All pins that +are delivered to users pass through this service. When a developer pushes a pin +it is sent to this service for distribution to the applicable users. + + +### Pebble Mobile App + +The Pebble mobile app is responsible for synchronizing the pins visible on the +watch with those that are currently available in the cloud. The PebbleKit JS +APIs allow a developer to use a configuration page (detailed in +{% guide_link user-interfaces/app-configuration %}) or onboard menu to give +users the choice of which pins they receive via the topics they are subscribed +to. Developers can also send the user's token to their own server to maintain a +custom list of users, and provide a more personal service. + +The Pebble mobile app is also responsible for inserting pins directly into the +user's timeline for their upcoming calendar events and missed calls. These pins +originate on the phone and are sent straight to the watch, not via the public +web API. Alarm pin are also inserted directly from the watch itself. + + +### Developer's App Server/Service + +When a developer wants to push pins, they can do so from their own third-party +server. Such a server will generate pins using topics that the watchapp +subscribes to (either for all users or just those that elect to be subscribed) +or user tokens received from PebbleKit JS to target individual users. See +{% guide_link pebble-timeline/timeline-public %} for information on +how to do this. + + +## Three Ways to Use Pins + +The timeline API is flexible enough to enable apps to use it to send data to +users in three distinct ways. + + +### Push to All Users + +![all-users](/images/guides/timeline/all-users.png) + +The most basic subscription method involves subscribing a user to a topic that +is global to the app. This means all users of the app will receive the pins +pushed to that topic. To do this, a developer can choose a single topic name +such as 'all-users' and subscribe all users to that topic when the app is first +installed, or the user opts in to receive pins. + +Read {% guide_link pebble-timeline/timeline-public#shared-pins "Shared Pins" %} +to find out how to create topics. + + +### Let Users Choose Their Pins + +![some-users](/images/guides/timeline/some-users.png) + +Developers can also use a configuration page in their app to allow users to +subscribe to different topics, leeting them customize their experience and only +receive pins they want to see. In the image above, the pin broadcast with the +topic 'baseball' is received by users 1 and 3, but not User 2 who has only +subscribed to the 'golf' topic. + + +### Target Individual Users + +![individual](/images/guides/timeline/individual.png) + +Lastly, developers can use the timeline token to target individual users. This +adds another dimension to how personal an app can become, allowing apps to be +customized to the user in more ways than just their topic preferences. The image +above shows a pin pushed from an app's pin server to just the user with the +matching `X-User-Token`. For example, an app tracking the delivery of a user's +packages will only be applicable to that user. + +See {% guide_link pebble-timeline/timeline-public#create-a-pin "Create a Pin" %} +to learn how to send a pin to a single user. diff --git a/devsite/source/_guides/pebble-timeline/timeline-js.md b/devsite/source/_guides/pebble-timeline/timeline-js.md new file mode 100644 index 00000000..ac272c25 --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/timeline-js.md @@ -0,0 +1,115 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Managing Subscriptions +description: | + How to integrate the timeline into apps with the PebbleKit JS subscriptions + API according to user preferences. +guide_group: pebble-timeline +order: 2 +related_examples: + - title: Timeline Push Pin + url: https://github.com/pebble-examples/timeline-push-pin + - title: Hello Timeline + url: https://github.com/pebble-examples/hello-timeline + - title: Timeline TV Tracker + url: https://github.com/pebble-examples/timeline-tv-tracker +--- + +The following PebbleKit JS APIs allow developers to intereact with the timeline +API, such as adding and removing the user's subscribed topics. By combining +these with user preferences from a configuration page (detailed in +{% guide_link user-interfaces/app-configuration %}) it is possible to allow +users to choose which pin sources they receive updates and events from. + +> The timeline APIs to subscribe users to topics and retrieve user tokens are +> only available in PebbleKit JS. +> +> If you wish to use the timeline APIs with a Pebble app that uses PebbleKit iOS +> or Android please [contact us](/contact) to discuss your specific use-case. + + +## Requirements + +These APIs require some knowledge of your app before they can work. For example, +in order to return the user's timeline token, the web API must know your app's +UUID. This also ensures that only users who have your app installed will receive +the correct pins. + +If you have not performed this process for your app and attempt to use these +APIs, you will receive an error similar to the following message: + +```text +[INFO ] No token available for this app and user. +``` + + +## Get a Timeline Token + +The timeline token is unique for each user/app combination. This can be used by +an app's third party backend server to selectively send pins only to those users +who require them, or even to target users individually for complete +personalization. + +```js +Pebble.getTimelineToken(function(token) { + console.log('My timeline token is ' + token); +}, function(error) { + console.log('Error getting timeline token: ' + error); +}); +``` + +## Subscribe to a Topic + +A user can also subscribe to a specific topic in each app. Every user that +subscribes to this topic will receive the pins pushed to it. This can be used to +let the user choose which features of your app they wish to subscribe to. For +example, they may want 'world news' stories but not 'technology' stories. In +this case they would be subscribed only to the topic that includes pins with +'world news' information. + +```js +Pebble.timelineSubscribe('world-news', function() { + console.log('Subscribed to world-news'); +}, function(err) { + console.log('Error subscribing to topic: ' + err); +}); +``` + +## Unsubscribe from a Topic + +The user may unsubscribe from a topic they previously subscribed to. They will +no longer receive pins from this topic. + +```js +Pebble.timelineUnsubscribe('world-news', function() { + console.log('Unsubscribed from world-news'); +}, function(err) { + console.log('Error unsubscribing from topic: ' + err); +}); +``` + +## List Current Subscriptions + +You can use the function below to list all the topics a user has subscribed to. + +```js +Pebble.timelineSubscriptions(function(topics) { + // List all the subscribed topics + console.log('Subscribed to ' + topics.join(', ')); +}, function(errorString) { + console.log('Error getting subscriptions: ' + errorString); +}); +``` diff --git a/devsite/source/_guides/pebble-timeline/timeline-libraries.md b/devsite/source/_guides/pebble-timeline/timeline-libraries.md new file mode 100644 index 00000000..5b0755f4 --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/timeline-libraries.md @@ -0,0 +1,273 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Libraries for Pushing Pins +description: | + A list of libraries available for interacting with the Pebble timeline. +guide_group: pebble-timeline +order: 1 +related_examples: + - title: Hello Timeline + url: https://github.com/pebble-examples/hello-timeline + - title: Timeline TV Tracker + url: https://github.com/pebble-examples/timeline-tv-tracker + - title: Timeline Push Pin + url: https://github.com/pebble-examples/timeline-push-pin +--- + +This page contains libraries that are currently available to interact with +the timeline. You can use these to build apps and services that push pins to +your users. + +## timeline.js + +**JavaScript Code Snippet** - [Available on GitHub](https://gist.github.com/pebble-gists/6a4082ef12e625d23455) + +**Install** + +Copy into the `src/pkjs/` directory of your project, add `enableMultiJS: true` in +`package.json`, then `require` and use in `index.js`. + +**Example** + +```js +var timeline = require('./timeline'); + +// Push a pin when the app starts +Pebble.addEventListener('ready', function() { + // An hour ahead + var date = new Date(); + date.setHours(date.getHours() + 1); + + // Create the pin + var pin = { + "id": "example-pin-0", + "time": date.toISOString(), + "layout": { + "type": "genericPin", + "title": "Example Pin", + "tinyIcon": "system://images/SCHEDULED_EVENT" + } + }; + + console.log('Inserting pin in the future: ' + JSON.stringify(pin)); + + // Push the pin + timeline.insertUserPin(pin, function(responseText) { + console.log('Result: ' + responseText); + }); +}); +``` + +## pebble-api + +**Node Module** - [Available on NPM](https://www.npmjs.com/package/pebble-api) + +**Install** + +```bash +npm install pebble-api --save +``` + +**Example** + +```js +var Timeline = require('pebble-api'); + +var USER_TOKEN = 'a70b23d3820e9ee640aeb590fdf03a56'; + +var timeline = new Timeline(); + +var pin = new Timeline.Pin({ + id: 'test-pin-5245', + time: new Date(), + duration: 10, + layout: new Timeline.Pin.Layout({ + type: Timeline.Pin.LayoutType.GENERIC_PIN, + tinyIcon: Timeline.Pin.Icon.PIN, + title: 'Pin Title' + }) +}); + +timeline.sendUserPin(USER_TOKEN, pin, function (err) { + if (err) { + return console.error(err); + } + + console.log('Pin sent successfully!'); +}); +``` + +## PebbleTimeline API Ruby + +**Ruby Gem** - [Available on RubyGems](https://rubygems.org/gems/pebble_timeline/versions/0.0.1) + +**Install** + +```bash +gem install pebble_timeline +``` + +**Example** + +```ruby +require 'pebble_timeline' + +api = PebbleTimeline::API.new(ENV['PEBBLE_TIMELINE_API_KEY']) + +# Shared pins +pins = PebbleTimeline::Pins.new(api) +pins.create(id: "test-1", topics: 'test', time: "2015-06-10T08:01:10.229Z", layout: { type: 'genericPin', title: 'test 1' }) +pins.delete("test-1") + +# User pins +user_pins = PebbleTimeline::Pins.new(api, 'user', USER_TOKEN) +user_pins.create(id: "test-1", time: "2015-06-12T16:42:00Z", layout: { type: 'genericPin', title: 'test 1' }) +user_pins.delete("test-1") +``` + +## pypebbleapi + +**Python Library** - [Available on pip](https://pypi.python.org/pypi/pypebbleapi/0.0.1) + +**Install** + +```bash +pip install pypebbleapi +``` + +**Example** + +```python +from pypebbleapi import Timeline, Pin +import datetime + +timeline = Timeline(my_api_key) + +my_pin = Pin(id='123', datetime.date.today().isoformat()) + +timeline.send_shared_pin(['a_topic', 'another_topic'], my_pin) +``` + +## php-pebble-timeline + +**PHPebbleTimeline** - [Available on Github](https://github.com/fletchto99/PHPebbleTimeline) + +**Install** + +Copy the TimelineAPI folder (from the above repository) to your project's directory and include the required files. + +**Example** + +
+{% highlight { "language": "php", "options": { "startinline": true } } %} +//Include the timeline API +require_once 'TimelineAPI/Timeline.php'; + +//Import the required classes +use TimelineAPI\Pin; +use TimelineAPI\PinLayout; +use TimelineAPI\PinLayoutType; +use TimelineAPI\PinIcon; +use TimelineAPI\PinReminder; +use TimelineAPI\Timeline; + +//Create some layouts which our pin will use +$reminderlayout = new PinLayout(PinLayoutType::GENERIC_REMINDER, 'Sample reminder!', null, null, null, PinIcon::NOTIFICATION_FLAG); +$pinlayout = new PinLayout(PinLayoutType::GENERIC_PIN, 'Our title', null, null, null, PinIcon::NOTIFICATION_FLAG); + +//Create a reminder which our pin will push before the event +$reminder = new PinReminder($reminderlayout, (new DateTime('now')) -> add(new DateInterval('PT10M'))); + +//Create the pin +$pin = new Pin('', (new DateTime('now')) -> add(new DateInterval('PT5M')), $pinlayout); + +//Attach the reminder +$pin -> addReminder($reminder); + +//Push the pin to the timeline +Timeline::pushPin('sample-userToken', $pin); +{% endhighlight %} +
+ +## PinPusher + +**PHP Library** - [Available on Composer](https://packagist.org/packages/valorin/pinpusher) + +**Install** + +```bash +composer require valorin/pinpusher +``` + +**Example** + +
+{% highlight { "language": "php", "options": { "startinline": true } } %} +use Valorin\PinPusher\Pusher; +use Valorin\PinPusher\Pin; + +$pin = new Pin( + 'example-pin-generic-1', + new DateTime('2015-03-19T18:00:00Z'), + new Pin\Layout\Generic( + "News at 6 o'clock", + Pin\Icon::NOTIFICATION_FLAG + ) +); + +$pusher = new Pusher() +$pusher->pushToUser($userToken, $pin); +{% endhighlight %} +
+ +## pebble-api-dotnet + +**PCL C# Library** - [Available on Github](https://github.com/nothingmn/pebble-api-dotnet) + +**Install** + +```text +git clone git@github.com:nothingmn/pebble-api-dotnet.git +``` + +**Example** + +In your C# project, define your global API Key. + +```csharp +public static string APIKey = "APIKEY"; +``` + +Launch your app on the watch, and make the API call... + +Now, on the server, you can use your "userToken" from the client app, and send pins as follows: + +```csharp +var timeline = new Timeline(APIKey); +var result = await timeline.SendUserPin(userToken, new Pin() +{ + Id = System.Guid.NewGuid().ToString(), + Layout = new GenericLayout() + { + Title = "Generic Layout", + Type = LayoutTypes.genericPin, + SmallIcon = Icons.Notification.Flag + }, +}); +``` + +See more examples on the +[GitHub repo](https://github.com/nothingmn/pebble-api-dotnet). diff --git a/devsite/source/_guides/pebble-timeline/timeline-public.md b/devsite/source/_guides/pebble-timeline/timeline-public.md new file mode 100644 index 00000000..b46c57ce --- /dev/null +++ b/devsite/source/_guides/pebble-timeline/timeline-public.md @@ -0,0 +1,342 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Public Web API +description: | + How to push Pebble timeline data to an app's users using the public web API. +guide_group: pebble-timeline +order: 3 +related_examples: + - title: Hello Timeline + url: https://github.com/pebble-examples/hello-timeline + - title: Timeline TV Tracker + url: https://github.com/pebble-examples/timeline-tv-tracker + - title: Timeline Push Pin + url: https://github.com/pebble-examples/timeline-push-pin +--- + +While users can register subscriptions and receive data from the timeline using +the PebbleKit JS subscriptions API +(detailed in {% guide_link pebble-timeline/timeline-js %}), app developers can +use the public timeline web API to provide that data by pushing pins. Developers +will need to create a simple web server to enable them to process and send the +data they want to display in the timeline. Each pin represents a specific event +in the past or the future, and will be shown on the watch once pushed to the +public timeline web API and automatically synchronized with the watch via the +Pebble mobile applications. + +The Pebble SDK emulator supports the timeline and automatically synchronizes +every 30 seconds. + + +## Pushing Pins + +Developers can push data to the timeline using their own backend servers. Pins +are created and updated using HTTPS requests to the Pebble timeline web API. + +> Pins pushed to the Pebble timeline web API may take **up to** 15 minutes to +> appear on a user's watch. Although most pins can arrive faster than this, we +> recommend developers do not design apps that rely on near-realtime updating of +> pins. + + +### Create a Pin + +To create a pin, send a `PUT` request to the following URL scheme, where `ID` is +the `id` of the pin object. For example 'reservation-1395203': + +```text +PUT https://timeline-api.getpebble.com/v1/user/pins/ID +``` + +Use the following headers, where `X-User-Token` is the user's +timeline token (read +{% guide_link pebble-timeline/timeline-js#get-a-timeline-token "Get a Timeline Token" %} +to learn how to get a token): + +```text +Content-Type: application/json +X-User-Token: a70b23d3820e9ee640aeb590fdf03a56 +``` + +Include the JSON object as the request body from a file such as `pin.json`. A +sample of an object is shown below: + +```json +{ + "id": "reservation-1395203", + "time": "2014-03-07T08:01:10.229Z", + "layout": { + "shortTitle": "Dinner at La Fondue", + ... + }, + ... +} +``` + + +#### Curl Example + +```bash +$ curl -X PUT https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 \ + --header "Content-Type: application/json" \ + --header "X-User-Token: a70b23d3820e9ee640aeb590fdf03a56" \ + -d @pin.json +OK +``` + + +### Update a Pin + +To update a pin, send a `PUT` request with a new JSON object with the **same +`id`**. + +```text +PUT https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 + +``` + +Remember to include the user token in the headers. + +```text +X-User-Token: a70b23d3820e9ee640aeb590fdf03a56 +``` + +When an update to an existing pin is issued, it replaces the original +pin entirely, so all fields (including those that have not changed) should be +included. The example below shows an event updated with a new `time`: + +```json +{ + "id": "reservation-1395203", + "time": "2014-03-07T09:01:10.229Z", + "layout": { + "shortTitle": "Dinner at La Fondue", + ... + }, + ... +} +``` + + +#### Curl Example + +```bash +$ curl -X PUT https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 \ + --header "Content-Type: application/json" \ + --header "X-User-Token: a70b23d3820e9ee640aeb590fdf03a56" \ + -d @pin.json +OK +``` + + +### Delete a Pin + +Delete a pin by issuing a HTTP `DELETE` request. + +```text +DELETE https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 +``` + +Remember to include the user token in the headers. + +```text +X-User-Token: a70b23d3820e9ee640aeb590fdf03a56 +``` + +This pin will then be removed from that timeline on the user's watch. +In some cases it may be preferred to simply update a pin with a cancelled +event's details so that it can remain visible and useful to the user. + + +#### Curl Example + +```bash +$ curl -X DELETE https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 \ + --header "Content-Type: application/json" \ + --header "X-User-Token: a70b23d3820e9ee640aeb590fdf03a56" +OK +``` + + +## Shared Pins + +### Create a Shared Pin + +It is possible to send a pin (and updates) to multiple users at once by +modifying the `PUT` header to include `X-Pin-Topics` (the topics a user must be +subscribed to in order to receive this pin) and `X-API-Key` (issued by the +[Developer Portal](https://dev-portal.getpebble.com/)). In this case, the URL is +also modified: + +```text +PUT /v1/shared/pins/giants-game-1 +``` + +The new headers: + +```text +Content-Type: application/json +X-API-Key: fbbd2e4c5a8e1dbef2b00b97bf83bdc9 +X-Pin-Topics: giants,redsox,baseball +``` + +The pin body remains the same: + +```json +{ + "id": "giants-game-1", + "time": "2014-03-07T10:01:10.229Z", + "layout": { + "title": "Giants vs Red Sox: 5-3", + ... + }, + ... +} +``` + + +#### Curl Example + +```bash +$ curl -X PUT https://timeline-api.getpebble.com/v1/shared/pins/giants-game-1 \ + --header "Content-Type: application/json" \ + --header "X-API-Key: fbbd2e4c5a8e1dbef2b00b97bf83bdc9" \ + --header "X-Pin-Topics: giants,redsox,baseball" \ + -d @pin.json +OK +``` + + +### Delete a Shared Pin + +Similar to deleting a user pin, shared pins can be deleted by issuing a `DELETE` +request: + +```text +DELETE /v1/shared/pins/giants-game-1 +``` + +As with creating a shared pin, the API key must also be provided in the request +headers: + +```text +X-API-Key: fbbd2e4c5a8e1dbef2b00b97bf83bdc9 +``` + + +#### Curl Example + +```bash +$ curl -X DELETE https://timeline-api.getpebble.com/v1/shared/pins/giants-game-1 \ + --header "Content-Type: application/json" \ + --header "X-API-Key: fbbd2e4c5a8e1dbef2b00b97bf83bdc9" \ +OK +``` + + +## Listing Topic Subscriptions + +Developers can also query the public web API for a given user's currently +subscribed pin topics with a `GET` request: + +```text +GET /v1/user/subscriptions +``` + +This requires the user's timeline token: + +```text +X-User-Token: a70b23d3820e9ee640aeb590fdf03a56 +``` + + +#### Curl Example + +```bash +$ curl -X GET https://timeline-api.getpebble.com/v1/user/subscriptions \ + --header "X-User-Token: a70b23d3820e9ee640aeb590fdf03a56" \ +``` + +The response will be a JSON object containing an array of topics the user is +currently subscribed to for that app: + +```json +{ + "topics": [ + "topic1", + "topic2" + ] +} +``` + + +## Pin Time Limitations + +The `time` property on a pin pushed to the public API must not be more than two +days in the past, or a year in the future. The same condition applies to the +`time` associated with a pin's reminders and notifications. + +Any pins that fall outside these conditions may be rejected by the web API. In +addition, the actual range of events shown on the watch may be different under +some conditions. + +For shared pins, the date and time of an event will vary depending on the user's +timezone. + + +## Error Handling + +In the event of an error pushing a pin, the public timeline API will return one +of the following responses. + +| HTTP Status | Response Body | Description | +|-------------|---------------|-------------| +| 200 | None | Success. | +| 400 | `{ "errorCode": "INVALID_JSON" }` | The pin object submitted was invalid. | +| 403 | `{ "errorCode": "INVALID_API_KEY" }` | The API key submitted was invalid. | +| 410 | `{ "errorCode": "INVALID_USER_TOKEN" }` | The user token has been invalidated, or does not exist. All further updates with this user token will fail. You should not send further updates for this user token. A user token can become invalidated when a user uninstalls an app for example. | +| 429 | `{ "errorCode": "RATE_LIMIT_EXCEEDED" }` | Server is sending updates too quickly, and has been rate limited (see [*Rate Limiting*](#rate-limiting) below). | +| 503 | `{ "errorCode": "SERVICE_UNAVAILABLE" }` | Could not save pin due to a temporary server error. | + + +## Rate Limiting + +For requests using API Keys, developers can make up to 5000 requests per minute. +For requests using User Tokens, up to 300 requests every 15 minutes can be made. +Check the returned HTTP headers of any API request to see the current rate limit +status: + +```text +$ curl -i https://timeline-api.getpebble.com/v1/user/pins/reservation-1395203 + +HTTP/1.1 429 OK +date: Wed, 13 May 2015 21:36:58 GMT +x-ratelimit-percent: 100 +retry-after: 43 +``` + +The headers contain information about the current rate limit status: + +| Header Name | Description | +|-------------|-------------| +| `x-ratelimit-percent` | The percentage of the rate limit currently utilized. | +| `retry-after` | When `x-ratelimit-percent` has reached `100`, this header will be set to the number of seconds after which the rate limit will reset. | + +When the rate limit is exceeded, the response body also reports the error: + +```text +{ "errorCode":"RATE_LIMIT_EXCEEDED" } +``` diff --git a/devsite/source/_guides/rocky-js/index.md b/devsite/source/_guides/rocky-js/index.md new file mode 100644 index 00000000..033db397 --- /dev/null +++ b/devsite/source/_guides/rocky-js/index.md @@ -0,0 +1,42 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Rocky.js JavaScript API +description: | + Information on using JavaScript to create watchfaces with Rocky.js +guide_group: rocky-js +menu: false +permalink: /guides/rocky-js/ +generate_toc: false +hide_comments: true +--- + +This section of the developer guides contains information and resources +surrounding creating Pebble watchfaces using the Rocky.js JavaScript API. + +Rocky.js is ECMAScript 5.1 JavaScript running natively on Pebble smartwatches, +thanks to +[our collaboration](https://github.com/Samsung/jerryscript/wiki/JerryScriptWorkshopApril2016) +with [JerryScript](https://github.com/pebble/jerryscript). + +![Rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-16-jotw.png) + +At present, Rocky.js can only be used to create watchfaces, but we're adding +more APIs and functionality with every release. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/rocky-js/rocky-js-overview.md b/devsite/source/_guides/rocky-js/rocky-js-overview.md new file mode 100644 index 00000000..90854caa --- /dev/null +++ b/devsite/source/_guides/rocky-js/rocky-js-overview.md @@ -0,0 +1,369 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Rocky.js Overview +description: | + Details of Rocky.js projects, available APIs, limitations, and how to get + started. +guide_group: rocky-js +order: 1 +platform_choice: true +related_examples: + - title: Tutorial Part 1 + url: https://github.com/pebble-examples/rocky-watchface-tutorial-part1 + - title: Tutorial Part 2 + url: https://github.com/pebble-examples/rocky-watchface-tutorial-part2 + - title: Memory Pressure + url: https://github.com/pebble-examples/rocky-memorypressure +--- + +Rocky.js can be used to create Pebble 4.0 watchfaces using ECMAScript 5.1 +JavaScript, which runs natively on the watch. Rocky.js is possible due to +[our collaboration](https://github.com/Samsung/jerryscript/wiki/JerryScriptWorkshopApril2016) +with [JerryScript](https://github.com/pebble/jerryscript). + +> At this time, Rocky.js cannot be used to create watchapps, but we're currently +working towards this goal. + + +## It's JavaScript on the freakin' watch! + +```javascript +var rocky = require('rocky'); + +rocky.on('minutechange', function(event) { + rocky.requestDraw(); +}); + +rocky.on('draw', function(event) { + var ctx = event.context; + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + ctx.fillStyle = 'white'; + ctx.textAlign = 'center'; + + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + ctx.fillText('JavaScript\non the watch!', w / 2, h / 2); +}); +``` + +![Rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-16-jotw.png) + + +## Rocky.js Projects + +Rocky.js projects have a different structure from projects created using our C +SDK. There is a Rocky.js (rocky) component which runs on the watch, and a +{% guide_link communication/using-pebblekit-js "PebbleKit JS" %} (pkjs) component +which runs on the phone. The pkjs component allows developers to access web +services, use geolocation, and offload data processing tasks to the phone. + + +### Creating a Project + +^CP^ Go to [CloudPebble]({{ site.links.cloudpebble }}) and click 'CREATE', enter +a project name, then select the 'Rocky.js' project type. + +
+{% markdown {} %} +Once you've installed the Pebble SDK, you can create a new Rocky.js project +using the following command: + +```nc|text +$ pebble new-project --rocky projectname +``` +{% endmarkdown %} +
+ + +### Rocky JS + +^CP^ In [CloudPebble]({{ site.links.cloudpebble }}), add a new App Source, the +file type is JavaScript and the target is `Rocky JS`. `index.js` is now the main +entry point into the application on the watch. + +^LC^ In the local SDK, our main entry point into the application on the watch is +`/src/rocky/index.js`. + +This is file is where our Rocky.js JavaScript code resides. All code within this +file will be executed on the smartwatch. In addition to standard JavaScript, +developers have access to the [rocky](/docs/rockyjs/rocky/) object. Additional +scripts may also be added, see [below](#additional-scripts). + + +### PebbleKit JS + +^CP^ In [CloudPebble]({{ site.links.cloudpebble }}), add a new App Source, the +file type is JavaScript and the target is [PebbleKit JS](/docs/pebblekit-js/). +This file should be named `index.js`. + +^LC^ In the local SDK, our primary [PebbleKit JS](/docs/pebblekit-js/) script is +`/src/pkjs/index.js`. + +All PebbleKit JS code will execute on the mobile device connected to the +smartwatch. In addition to standard JavaScript, developers have access to +[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), +[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), +[Geolocation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation), +[LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/LocalStorage) +and the [Pebble](/docs/pebblekit-js/) object. Additional scripts may also be +added, see [below](#additional-scripts). + + +### Shared JS + +If you need to share code between Rocky.js and PebbleKit JS, you can place +JavaScript files in a shared area. + +^CP^ In [CloudPebble]({{ site.links.cloudpebble }}), add a new App Source, the +file type is JavaScript and the target is `Shared JS`. + +^LC^ In the local SDK, place your shared files in `/src/common/`. + +Shared JavaScript files can be referenced using the +[CommonJS Module](http://www.commonjs.org/specs/modules/1.0/) format. + +```javascript +// Shared JS (index.js) +function test() { + console.log('Hello from shared code'); +} + +module.exports.test = test; +``` + +```javascript +// Rocky JS +var shared = require('../common/index'); +shared.test(); +``` + +```javascript +// PebbleKit JS +var shared = require('../common/index'); +shared.test(); +``` + + +### Additional Scripts + +Both the `Rocky JS` and ` PebbleKit JS` support the use of multiple .js files. +This helps to keep your code clean and modular. Use the +[CommonJS Module](http://www.commonjs.org/specs/modules/1.0/) format for +your additional scripts, then `require()` them within your script. + +```javascript +// additional.js +function test() { + console.log('Additional File'); +} + +module.exports.test = test; +``` + +```javascript +var additional = require('./additional'); +additional.test(); +``` + + +## Available APIs + +In this initial release of Rocky.js, we have focused on the ability to create +watchfaces only. We will be adding more and more APIs as time progresses, and +we're determined for JavaScript to have feature parity with the rest of the +Pebble developer ecosystem. + +We've developed our API in-line with standard +[Web APIs](https://developer.mozilla.org/en-US/docs/Web/API), which may appear +strange to existing Pebble developers, but we're confident that this will +facilitate code re-use and provide a better experience overall. + + +### System Events + +We've provided a series of events which every watchface will likely require, and +each of these events allow you to provide a callback function that is called +when the event occurs. + +Existing Pebble developers will be familiar with the tick style events, +including: + +* [`secondchange`](/docs/rockyjs/rocky/#on) +* [`minutechange`](/docs/rockyjs/rocky/#on) +* [`hourchange`](/docs/rockyjs/rocky/#on) +* [`daychange`](/docs/rockyjs/rocky/#on) + +By using these events, instead of +[`setInterval`](/docs/rockyjs), we're automatically kept in sync with the wall +clock time. + +We also have a +[`message`](/docs/rockyjs/rocky/#on) event for +receiving JavaScript JSON objects from the `pkjs` component, a +[`memorypressure`](/docs/rockyjs/rocky/#on) event when there is a notable change +in available system memory, and a [`draw`](/docs/rockyjs/rocky/#on) event which +you'll use to control the screen updates. + +```javascript +var rocky = require('rocky'); + +rocky.on('minutechange', function(event) { + // Request the screen to be redrawn on next pass + rocky.requestDraw(); +}); +``` + + +### Drawing Canvas + +The canvas is a 2D rendering context and represents the display of the Pebble +smartwatch. We use the canvas context for drawing text and shapes. We're aiming +to support standard Web API methods and properties where possible, so the canvas +has been made available as a +[CanvasRenderingContext2D](/docs/rockyjs/CanvasRenderingContext2D/). + +> Please note that the canvas isn't fully implemented yet, so certain methods +and properties are not available at this time. We're still working on this, so +expect more features in future updates! + +```javascript +rocky.on('draw', function(event) { + var ctx = event.context; + + ctx.fillStyle = 'red'; + ctx.textAlign = 'center'; + ctx.font = '14px Gothic'; + + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + ctx.fillText('Rocky.js Rocks!', w / 2, h / 2); +}); +``` + + +### Messaging + +For Rocky.js projects only, we've added a new simplified communication channel +[`postMessage()`](/docs/rockyjs/rocky/#postMessage) and +[`on('message', ...)`](/docs/rockyjs/rocky/#on) which allows you to send and +receive JavaScript JSON objects between the phone and smartwatch. + +```javascript +// Rocky.js (rocky) + +// Receive data from the phone (pkjs) +rocky.on('message', function(event) { + console.log(JSON.stringify(event.data)); +}); + +// Send data to the phone (pkjs) +rocky.postMessage({command: 'fetch'}); +``` + +```javascript +// PebbleKit JS (pkjs) + +// Receive data from the watch (rocky) +Pebble.on('message', function(event) { + if(event.data.command === 'fetch') { + // doSomething(); + } +}); + +// Send data to the watch (rocky) +Pebble.postMessage({ + 'temperature': 90, + 'condition': 'Hot' +}); +``` + +You can see an example of post message in action in +[part 2](https://github.com/pebble-examples/rocky-watchface-tutorial-part2) of +the Rocky.js tutorial. + + +### Memory Pressure + +The [`memorypressure`](/docs/rockyjs/rocky/#on) event is emitted every time +there is a notable change in available system memory. This provides developers +with an opportunity to free some memory, in order to prevent their application +from being terminated when memory pressure is high. + +```javascript +rocky.on('memorypressure', function(event) { + console.log(event.level); +}); +``` + +A detailed example of the [`memorypressure`](/docs/rockyjs/rocky/#on) event +is provided [here](https://github.com/pebble-examples/rocky-memorypressure). + + +### Content Size + +The [`contentSize`](/docs/rockyjs/rocky/#UserPreferences) property allows +developers to dynamically adapt their watchface design based upon the system +`Text Size` preference (*Settings > Notifications > Text Size*). + +`contentSize` is exposed in Rocky.js via the +[`UserPreferences`](/docs/rockyjs/rocky/#UserPreferences) object. + +```javascript +rocky.on('draw', function(event) { + var ctx = event.context; + // ... + if (rocky.userPreferences.contentSize === 'x-large') { + ctx.font = '42px bold numbers Leco-numbers'; + } else { + ctx.font = '32px bold numbers Leco-numbers'; + } + // ... +}); +``` + +## App Configuration + +[Clay](https://github.com/pebble/clay#) is a JavaScript library that makes it +easy to add offline configuration pages to your Pebble apps. Out of the box, +Clay is not currently compatible with Rocky.js, but it can be made to work by +manually including the clay.js file and overriding the default events. + +You can find a community Rocky.js project which uses Clay +[here](https://github.com/orviwan/rocky-leco-clay). + + +## Limitations + +Although Rocky.js is finally out of beta, there are still some limitations and +restrictions that you need to be aware of: + +* No support for custom fonts, images and other resources, yet. +* No C code allowed. +* No messageKeys. +* Pebble Packages are not supported. +* There are file size and memory constraints for Rocky.js projects. +If you include a large JS library, it probably won't work. + + +## How to Get Started + +We created a [2-part tutorial](/tutorials/js-watchface-tutorial/part1/) for +getting started with Rocky.js watchfaces. It explains everything you need to +know about creating digital and analog watchfaces, plus how to retrieve +weather conditions from the internet. + +If you're looking for more detailed information, check out the +[API Documentation](/docs/rockyjs). diff --git a/devsite/source/_guides/smartstraps/index.md b/devsite/source/_guides/smartstraps/index.md new file mode 100644 index 00000000..8a745432 --- /dev/null +++ b/devsite/source/_guides/smartstraps/index.md @@ -0,0 +1,71 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Smartstraps +description: | + Information on creating and talking to smartstraps. +guide_group: smartstraps +menu: false +permalink: /guides/smartstraps/ +generate_toc: false +hide_comments: true +related_docs: + - Smartstrap +related_examples: + - title: Smartstrap Button Counter + url: https://github.com/pebble-examples/smartstrap-button-counter + - title: Smartstrap Library Test + url: https://github.com/pebble-examples/smartstrap-library-test +--- + +The smart accessory port on the back of Pebble Time, Pebble Time Steel, and +Pebble Time Round makes it possible to create accessories with electronics +built-in to improve the capabilities of the watch itself. Wrist-mounted pieces +of hardware that interface with a Pebble watch are called smartstraps and can +potentially host many electronic components from LEDs, to temperature sensors, +or even external batteries to boost battery life. + +This section of the developer guides details everything a developer +should need to produce a smartstrap for Pebble; from 3D CAD diagrams, to +electrical characteristics, to software API and protocol specification details. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} + + +## Availablility + +The ``Smartstrap`` API is available on the following platforms and firmwares. + +| Platform | Model | Firmware | +|----------|-------|----------| +| Basalt | Pebble Time/Pebble Time Steel | 3.4+ | +| Chalk | Pebble Time Round | 3.6+ | + +Apps that use smartstraps but run on incompatible platforms can use compile-time +defines to provide alternative behavior in this case. Read +{% guide_link best-practices/building-for-every-pebble %} for more information +on supporting multiple platforms with differing capabilities. + + +## Video Introduction + +Watch the video below for a detailed introduction to the Smartstrap API by Brian +Gomberg (Firmware team), given at the +[PebbleSF Meetup](http://www.meetup.com/PebbleSF/). + +[EMBED](//www.youtube.com/watch?v=uB9r2lw7Bt8) diff --git a/devsite/source/_guides/smartstraps/smartstrap-hardware.md b/devsite/source/_guides/smartstraps/smartstrap-hardware.md new file mode 100644 index 00000000..3d9b1b5e --- /dev/null +++ b/devsite/source/_guides/smartstraps/smartstrap-hardware.md @@ -0,0 +1,253 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Hardware Specification +description: | + Details of how to build smartstrap hardware, including 3D printing + instructions and electrical characteristics. +guide_group: smartstraps +order: 0 +--- + +This page describes how to make smartstrap hardware and how to interface it with +the watch. + +The smartstrap connector has four contacts: two for ground, one for power and a +one-wire serial bus. The power pin is bi-directional and can be used to power +the accessory, or for the strap to charge the watch. The amount of power that +can be drawn **must not exceed** 20mA, and will of course impact the battery +life of Pebble Time. + +[Download 3D models of Pebble Time and the DIY Smartstrap >{center,bg-lightblue,fg-white}](https://github.com/pebble/pebble-3d/tree/master/Pebble%20Time) + +> Note: Due to movement of the user the contacts of the DIY Smartstrap may come +> undone from time to time. This should be taken into account when designing +> around the accessory and its protocol. + + +## Electronic Characteristics + +The table below summarizes the characteristics of the accessory port connection +on the back of Pebble Time. + +| Characteristic | Value | +|----------------|-------| +| Pin layout (watch face down, left to right) | Ground, data, power in/out, ground. | +| Type of data connection | Single wire, open drain serial connection with external pull-up required. | +| Data voltage level | 1.8V input logic level with tolerance for up to 5V. | +| Baud rate | Configurable between 9600 and 460800 bps. | +| Output voltage (power pin) | 3.3V (+/- 10%) | +| Maximum output current draw (power pin) | 20mA | +| Minimum charging voltage (power pin) | 5V (+/- 5%) | +| Maximum charging current draw | 500mA | + + +## Battery Smartstraps and Chargers + +If a smartstrap is designed to charge a Pebble smartwatch, simply apply +5V to +the power pin and make sure that it can provide up to 500mA of current. This is +the maximum power draw of Pebble Time when the screen is on, the battery +charging, the radios are on, etc. + + +## Accessories Drawing Power + +If the accessory is drawing power from the watch it will need to include a +pull-up resistor (10kΩ is recommended) so that the watch can detect that a +smartstrap is connected. + +By default, the smartstrap port is turned off. The app will need to turn on the +smartstrap port to actually receive power. Refer to ``smartstrap_subscribe()``. + + +## Example Circuits + +### Single-component Data Interface + +The simplest interface to the smartstrap connector is just a pull-up resistor +between the power and the data pin of the watch. This pull-up is required so +that the watch can detect that something is connected. By default the data bus +will be at +3.3V and the watch or the smartstrap can force the bus to 0V when +sending data. + +> This is the general principle of an open-drain or open-collector bus. Refer to +> an [electronic reference](https://en.wikipedia.org/wiki/Open_collector) for +> more information. + +![software-serial](/images/guides/hardware/software-serial.png =500x) + +On the smartstrap side, choose to use one or two pins of the chosen +micro-controller: + +* If using only one pin, the smartstrap will most likely have to implement the + serial communication in software because most micro-controllers expect + separated TX and RX pins. This is demonstrated in the + [ArduinoPebbleSerial](https://github.com/pebble/arduinopebbleserial) project + when running in 'software serial' mode. + +* If using two pins, simply connect the data line to both the TX and RX pins. + The designer should make sure that the TX pin is in high-impedance mode when + not talking on the bus and that the serial receiver is not active when sending + (otherwise it will receive everything sent). This is demonstrated in the + [ArduinoPebbleSerial](https://github.com/pebble/arduinopebbleserial) project + when running in the 'hardware serial' mode. + + +### Transistor-based Buffers + +When connecting the smartstrap to a micro-controller where the above options are +not possible then a little bit of hardware can be used to separate the TX and RX +signals and emulate a standard serial connection. + +![buffer](/images/guides/hardware/buffer.png =500x) + + +### A More Professional Interface + +Finally, for production ready smartstraps it is recommended to use a more robust +setup that will provide voltage level conversion as well as protect the +smartstraps port from over-voltage. + +The diagram below shows a suggested circuit for interfacing the smartstrap +connector (right) to a traditional two-wire serial connection (left) using a +[SN74LVC1G07](http://www.ti.com/product/sn74lvc1g07) voltage level converter as +an interface with Zener diodes for +[ESD](http://en.wikipedia.org/wiki/Electrostatic_discharge) protection. + +![strap-adapter](/images/more/strap-adapter.png) + + +## Smartstrap Connectors + +Two possible approaches are suggested below, but there are many more potential +ways to create smartstrap connectors. The easiest way involves modifying a +Pebble Time charging cable, which provides a solid magnetized connection at the +cost of wearability. By contrast, 3D printing a connector is a more comfortable +approach, but requires a high-precision 3D printer and additional construction +materials. + + +### Hack a Charging Cable + +The first suggested method to create a smartstrap connector for prototyping +hardware is to adapt a Pebble Time charging cable using common hardware hacking +tools, such as a knife, soldering iron and jumper cables. The end result is a +component that snaps securely to the back of Pebble Time, and connects securely +to common male-to-female prototyping wires, such as those sold with Arduino +kits. + +First, cut off the remainder of the cable below the end containing the magnets. +Next, use a saw or drill to split the malleable outer casing. + +![](/images/guides/hardware/cable-step1.jpg =450x) + +Pull the inner clear plastic part of the cable out of the outer casing, severing +the wires. + +![](/images/guides/hardware/cable-step2.jpg =450x) + +Use the flat blade of a screwdriver to separate the clear plastic from the front +plate containing the magnets and pogo pins. + +![](/images/guides/hardware/cable-step3.jpg =450x) + +Using a soldering iron, remove the flex wire attached to the inner pogo pins. +Ensure that there is no common electrical connection between any two contacts. +In its original state, the two inner pins are connected, and **must** be +separated. + +Next, connect a row of three headers to the two middle pins, and one of the +magnets. + +> Note: Each contact may require tinning in order to make a solid electrical +> connection. + +![](/images/guides/hardware/cable-step4.jpg =450x) + +The newly created connector can now be securely attached to the back of Pebble +Time. + +![](/images/guides/hardware/cable-step5.jpg =450x) + +With the connector in place, the accessory port pins may be easily interfaced +with using male-to-female wires. + +![](/images/guides/hardware/cable-step6.jpg =450x) + + +### 3D Printed Connector + +An alternate method of creating a compatible connector is to 3D print a +connector component and add the electrical connectivity using some additional +components listed below. To make a 3D printed smartstrap connector the following +components will be required: + +![components](/images/more/strap-components.png =400x) + +* 1x Silicone strap or similar, trimmed to size (See + [*Construction*](#construction)). + +* 1x Quick-release style pin or similar + ([Amazon listing](http://www.amazon.com/1-8mm-Release-Spring-Cylindrical-Button/dp/B00Q7XE866)). + +* 1x 3D printed adapter + ([STP file](https://github.com/pebble/pebble-3d/blob/master/Pebble%20Time/Smartstrap-CAD.stp)). + +* 4x Spring loaded pogo pins + ([Mill-Max listing](https://www.mill-max.com/products/pin/0965)). + +* 4x Lengths of no.24 AWG copper wire. + + +#### Construction + +For the 3D printed adapter, it is highly recommended that the part is created +using a relatively high resolution 3D printer (100-200 microns), such as a +[Form 1](http://formlabs.com/products/form-1-plus/) printer. Alternatively there +are plenty of websites that 3D print parts, such as +[Shapeways](http://www.shapeways.com/). Make sure to use a **non-conductive** +material such as ABS, and print a few copies, just to be safe. + +(A lower resolution printer like a Makerbot may not produce the same results. +The 3D part depends on many fine details to work properly). + +For the strap, it is recommend to use a silicone strap (such as the one included +with Pebble Time or a white Pebble Classic), and cut it down. Put the strap +along the left and right side of the lug holes, as shown below. + +> Ensure the strap is cut after receiving the 3D printed part so that it can be +> used as a reference. + +![cutaway](/images/more/strap-measurements.png =300x) + + +#### Assembly + +Slide the quick-release pin into the customized silicone strap. + +![strap-insert-pin](/images/more/strap-insert-pin.png =300x) + +Slide the strap and pin into the 3D printed adapter. + +![strap-into-adapter](/images/more/strap-into-adapter.png =300x) + +Insert the copper wire pieces into the back of the 3D printed adapter. + +![strap-insert-wires](/images/more/strap-insert-wires.png =300x) + +Place the pogo pins into their respective holes, then slide them into place away +from the strap. + +![strap-insert-pogo-pins](/images/more/strap-insert-pogo-pins.png =300x) diff --git a/devsite/source/_guides/smartstraps/smartstrap-protocol.md b/devsite/source/_guides/smartstraps/smartstrap-protocol.md new file mode 100644 index 00000000..855e8b30 --- /dev/null +++ b/devsite/source/_guides/smartstraps/smartstrap-protocol.md @@ -0,0 +1,589 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Protocol Specification +description: | + Reference information on the Pebble smartstrap protocol. +guide_group: smartstraps +order: 1 +--- + +This page describes the protocol used for communication with Pebble smartstraps, +intended to gracefully handle bus contention and allow two-way communication. +The protocol is error-free and unreliable, meaning datagrams either arrive +intact or not at all. + + +## Communication Model + +Most smartstrap communication follows a master-slave model with the watch being +the master and the smartstrap being the slave. This means that the watch will +never receive data from the smartstrap which it isn't expecting. The one +exception to the master/slave model is the notification mechanism, which allows +the smartstrap to notify the watch of an event it may need to respond to. This +is roughly equivalent to an interrupt line on an I2C device, but for smartstraps +is done over the single data line (marked 'Data' on diagrams). + + +## Assumptions + +The following are assumed to be universally true for the purposes of the +smartstrap protocol: + +1. Data is sent in [little-endian](https://en.wikipedia.org/wiki/Endianness) + byte order. +2. A byte is defined as an octet (8 bits). +3. Any undefined values should be treated as reserved and should not be used. + + +## Sample Implementation + +Pebble provides complete working sample implementations for common +micro-controllers platforms such as the [Teensy](https://www.pjrc.com/teensy/) +and [Arduino Uno](https://www.arduino.cc/en/Main/arduinoBoardUno). +This means that when using one of these platforms, it is not necessary to +understand all of the details of the low level communications and thus can rely +on the provided library. + +[Arduino Pebble Serial Sample Library >{center,bg-dark-red,fg-white}](https://github.com/pebble/ArduinoPebbleSerial/) + +Read [*Talking to Pebble*](/guides/smartstraps/talking-to-pebble) for +instructions on how to use this library to connect to Pebble. + + +## Protocol Layers + +The smartstrap protocol is split up into 3 layers as shown in the table below: + +| Layer | Function | +|-------|----------| +| [Profile Layer](#profile-layer) | Determines the format of the high-level message being transmitted. | +| [Link Layer](#link-layer) | Provides framing and error detection to allow transmission of datagrams between the watch and the smartstrap. | +| [Physical Layer](#physical-layer) | Transmits raw bits over the electrical connection. | + + +### Physical Layer + +The physical layer defines the hardware-level protocol that is used to send bits +over the single data wire. In the case of the smartstrap interface, there is a +single data line, with the two endpoints using open-drain outputs with an +external pull-up resistor on the smartstrap side. Frames are transmitted over +this data line as half-duplex asynchronous serial (UART). + +The UART configuration is 8-N-1: eight data bits, no +[parity bit](https://en.wikipedia.org/wiki/Parity_bit), and one +[stop bit](https://en.wikipedia.org/wiki/Asynchronous_serial_communication). The +default baud rate is 9600 bps (bits per second), but can be changed by the +higher protocol layers. The smallest data unit in a frame is a byte. + + +**Auto-detection** + +The physical layer is responsible for providing the smartstrap auto-detection +mechanism. Smartstraps are required to have a pull-up resistor on the data line +which is always active and not dependent on any initialization (i.e. activating +internal pull-ups on microcontroller pins). The value of the pull-up resistor +must be low enough that adding a 30kΩ pull-down resistor to the data line will +leave the line at >=1.26V (10kΩ is generally recommended). Before communication +with the smartstrap is attempted, the watch will check to see if the pull-up is +present. If (and only if) it is, the connection will proceed. + + +**Break Character** + +A break character is defined by the physical layer and used by the +[link layer](#link-layer) for the notification mechanism. The physical layer for +smartstraps defines a break character as a `0x00` byte with an extra low bit +before the stop bit. For example, in 8-N-1 UART, this means the start bit is +followed by nine low (`0`) bits and a stop bit. + + +### Link Layer + +The link layer is responsible for transmitting frames between the smartstrap and +the watch. The goal of the link layer is to detect transmission errors such as +flipped bits (including those caused by bus contention) and to provide a framing +mechanism to the upper layers. + + +#### Frame Format + +The structure of the link layer frame is shown below. The fields are transmitted +from top to bottom. + +> Note: This does not include the delimiting flags or bytes inserted for +> transparency as described in the [encoding](#encoding) section below. + +| Field Name | Length | +|------------|--------| +| `version` | 1 byte | +| `flags` | 4 bytes | +| `profile` | 2 bytes | +| `payload` | Variable length (may be empty) | +| `checksum` | 1 byte | + + +**Version** + +The `version` field contains the current version of the link layer of the +smartstrap protocol. + +| Version | Description | +|---------|-------------| +| 1 | Initial release version. | + + +**Flags** + +The `flags` field is four bytes in length and is made up of the following +fields. + +| Bit(s) | Name | Description | +|--------|------|-------------| +| 0 | `IsRead` | `0`: The smartstrap should not reply to this frame.
`1`: This is a read and the smartstrap should reply.

This field field should only be set by the watch. The smartstrap should always set this field to `0`. | +| 1 | `IsMaster` | `0`: This frame was sent by the smartstrap.
`1`: This frame was sent by the watch. | +| 2 | `IsNotification` | `0`: This is not a notification frame.
`1`: This frame was sent by the smartstrap as part of the notification.

This field should only be set by the smartstrap. The watch should always set this field to `0`. | +| 3-31 | `Reserved` | All reserved bits should be set to `0`. | + + +**Profile** + +The `profile` field determines the specific profile used for communication. The +details of each of the profiles are defined in the +[Profile Layer](#profile-layer) section. + +| Number | Value | Name | +|--------|-------|------| +| 1 | `0x0001` | Link Control Profile | +| 2 | `0x0002` | Raw Data Profile | +| 3 | `0x0003` | Generic Service Profile | + + +**Payload** + +The `payload` field contains the profile layer data. The link layer considers an +empty frame as being valid, and there is no maximum length. + + +**Checksum** + +The checksum is an 8-bit +[CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) with a polynomial +of `x^8 + x^5 + x^3 + x^2 + x + 1`. This is **not** the typical CRC-8 +polynomial. + +An example implementation of this CRC can be found in the +[ArduinoPebbleSerial library](https://github.com/pebble/ArduinoPebbleSerial/blob/master/utility/crc.c). + + +**Frame Length** + +The length of a frame is defined as the number of bytes in the `flags`, +`profile`, `checksum`, and `payload` fields of the link layer frame. This does +not include the delimiting flags or the bytes inserted for transparency as part +of [encoding](#encoding). The smallest valid frame is eight bytes in size: one +byte for the version, four for the flags, two for the profile type, one for the +checksum, and zero for the empty payload. The protocol is designed to work +without the need for fixed buffers. + + +#### Encoding + +A delimiting flag (i.e.: a byte with value of `0x7e`) is used to delimit frames +(indicating the beginning or end of a frame). The byte stream is examined on a +byte-by-byte basis for this flag value. Each frame begins and ends with the +delimiting flag, although only one delimiting flag is required between two +frames. Two consecutive delimiting flags constitute an empty frame, which is +silently discarded by the link layer and is not considered an error. + + +**Transparency** + +A byte-stuffing procedure is used to escape `0x7e` bytes in the frame payload. +After checksum computation, the link layer of the transmitter within the +smartstrap encodes the entire frame between the two delimiting flags. Any +occurrence of `0x7e` or `0x7d` in the frame is escaped with a proceeding `0x7d` +byte and logically-XORed with `0x20`. For example: + +* The byte `0x7d` when escaped is encoded as `0x7d 0x5d`. + +* The byte `0x7e` when escaped is encoded as `0x7d 0x5e`. + +On reception, prior to checksum computation, decoding is performed on the byte +stream before passing the data to the profile layer. + + +#### Example Frames + +The images below show some example frames of the smartstrap protocol under two +example conditions, including the calculated checksum. Click them to see more +detail. + +**Raw Profile Read Request** + + + +**Raw Profile Response** + + + + +#### Invalid Frames + +Frames which are too short, have invalid transparency bytes, or encounter a UART +error (such as an invalid stop bit) are silently discarded. + + +#### Timeout + +If the watch does not receive a response to a message sent with the `IsRead` +flag set (a value of `1`) within a certain period of time, a timeout will occur. +The amount of time before a timeout occurs is always measured by the watch from +the time it starts to send the message to the time it completely reads the +response. + +The amount of time it takes to send a frame (based on the baud rate, maximum +size of the data after encoding, and efficiency of the physical layer UART +implementation) should be taken into account when determining timeout values. +The value itself can be set with ``smartstrap_set_timeout()``, up to a maximum +value of 1000ms. + +> Note: In order to avoid bus contention and potentially corrupting other +> frames, the smartstrap should not respond after the timeout has elapsed. Any frame +> received after a timeout has occurred will be dropped by the watch. + + +### Notification Mechanism + +There are many use-cases where the smartstrap will need to notify the watch of +some event. For example, smartstraps may contain input devices which will be +used to control the watch. These smartstraps require a low-latency mechanism for +notifying the watch upon receiving user-input. The primary goal of this +mechanism is to keep the code on the smartstrap as simple as possible. + +In order to notify the watch, the smartstrap can send a break character +(detailed under [*Physical Layer*](#physical-layer)) to the watch. Notifications +are handled on a per-profile granularity, so the frame immediately following +a break character, called the context frame, is required in order to communicate +which profile is responsible for handling the notification. The context +frame must have the `IsNotification` flag (detailed under [*Flags*](#flags)) set +and have an empty payload. How the watch responds to notifications is dependent +on the profile. + + +### Profile Layer + +The profile layer defines the format of the payload. Exactly which profile a +frame belongs to is determined by the `profile` field in the link layer header. +Each profile type defines three things: a set of requirements, the format of +all messages of that type, and notification handling. + + +#### Link Control Profile + +The link control profile is used to establish and manage the connection with the +smartstrap. It must be fully implemented by all smartstraps in order to be +compatible with the smartstrap protocol as a whole. Any invalid responses or +timeouts encountered as part of link control profile communication will cause +the smartstrap to be marked as disconnected and powered off unless otherwise +specified. The auto-detection mechanism will cause the connection establishment +procedure to restart after some time has passed. + +**Requirements** + +| Name | Value | +|------|-------| +| Notifications Allowed? | No | +| Message Timeout | 100ms. | +| Maximum Payload Length | 6 bytes. | + + +**Payload Format** + +| Field | Length (bytes) | +|-------|----------------| +| Version | 1 | +| Type | 1 | +| Data | Variable length (may be empty) | + + +**Version** + +The Version field contains the current version of link control profile. + +| Version | Notes | +|---------|-------| +| `1` | Initial release version. | + + +**Type** + +| Type | Value | Data | +|------|-------|-------------| +| Status | `0x01` | Watch: *Empty*. Smartstrap: Status (see below). | +| Profiles | `0x02` | Watch: *Empty*. Smartstrap: Supported profiles (see below). | +| Baud rate | `0x03` | Watch: *Empty*. Smartstrap: Baud rate (see below). | + + +**Status** + +This message type is used to poll the status of the smartstrap and allow it to +request a change to the parameters of the connection. The smartstrap should send +one of the following status values in its response. + +| Value | Meaning | Description | +|-------|---------|-------------| +| `0x00` | OK | This is a simple acknowledgement that the smartstrap is still alive and is not requesting any changes to the connection parameters. | +| `0x01` | Baud rate | The smartstrap would like to change the baud rate for the connection. The watch should follow-up with a baud rate message to request the new baud rate. | +| `0x02` | Disconnect | The smartstrap would like the watch to mark it as disconnected. | + +A status message is sent by the watch at a regular interval. If a timeout +occurs, the watch will retry after an interval of time. If an invalid response +is received or the retry also hits a timeout, the smartstrap will be marked as +disconnected. + + +**Profiles** + +This message is sent to determine which profiles the smartstrap supports. The +smartstrap should respond with a series of two byte words representing all the +[profiles](#profile) which it supports. There are the following requirements for +the response. + +* All smartstraps must support the link control profile and should not include + it in the response. + +* All smartstraps must support at least one profile other than the link control + profile, such as the raw data profile. + +* If more than one profile is supported, they should be reported in consecutive + bytes in any order. + +> Note: Any profiles which are not supported by the watch are silently ignored. + + +**Baud Rate** + +This message type is used to allow the smartstrap to request a change in the +baud rate. The smartstrap should respond with a pre-defined value corresponding +to the preferred baud rate as listed below. Any unlisted value is invalid. In +order to conserve power on the watch, the baud rate should be set as high as +possible to keep time spent alive and communicating to a minimum. + +| Value | Baud Rate (bits per second) | +|-------|-----------------------------| +| `0x00` | 9600 | +| `0x01` | 14400 | +| `0x02` | 19200 | +| `0x03` | 28800 | +| `0x04` | 38400 | +| `0x05` | 57600 | +| `0x06` | 62500 | +| `0x07` | 115200 | +| `0x08` | 125000 | +| `0x09` | 230400 | +| `0x0A` | 250000 | +| `0x0B` | 460800 | + +Upon receiving the response from the smartstrap, the watch will change its baud +rate and then send another status message. If the smartstrap does not respond to +the status message at the new baud rate, it is treated as being disconnected. +The watch will revert back to the default baud rate of 9600, and the connection +establishment will start over. The default baud rate (9600) must always be the +lowest baud rate supported by the smartstrap. + + +**Notification Handling** + +Notifications are not supported for this profile. + + +#### Raw Data Service + +The raw data profile provides a mechanism for exchanging raw data with the +smartstrap without any additional overhead. It should be used for any messages +which do not fit into one of the other profiles. + + +**Requirements** + +| Name | Value | +|------|-------| +| Notifications Allowed? | Yes | +| Message Timeout | 100ms from sending to complete reception of the response. | +| Maximum Payload Length | Not defined. | + + +**Payload Format** + +There is no defined message format for the raw data profile. The payload may +contain any number of bytes (including being empty). + +| Field | Value | +|-------|-------| +| `data` | Variable length (may be empty). | + + +**Notification Handling** + +The handling of notifications is allowed, but not specifically defined for the +raw data profile. + + +#### Generic Service Profile + +The generic service profile is heavily inspired by (but not identical to) the +[GATT bluetooth profile](https://developer.bluetooth.org/gatt/Pages/default.aspx). +It allows the watch to write to and read from pre-defined attributes on the +smartstrap. Similar attributes are grouped together into services. These +attributes can be either read or written to, where a read requires the +smartstrap to respond with the data from the requested attribute, and a write +requiring the smartstrap to set the value of the attribute to the value provided +by the watch. All writes require the smartstrap to send a response to +acknowledge that it received the request. The data type and size varies by +attribute. + + +**Requirements** + +| Name | Value | +|------|-------| +| Notifications Allowed? | Yes | +| Message Timeout | Not defined. A maximum of 1000ms is supported. | +| Maximum Payload Length | Not defined. | + + +**Payload Format** + +| Field | Length (bytes) | +|-------|----------------| +| Version | 1 | +| Service ID | 2 | +| Attribute ID | 2 | +| Type | 1 | +| Error Code | 1 | +| Length | 2 | +| Data | Variable length (may be empty) | + + +**Version** + +The Version field contains the current version of generic service profile. + +| Version | Notes | +|---------|-------| +| `1` | Initial release version. | + + +**Service ID** + +The two byte identifier of the service as defined in the Supported Services and +Attributes section below. The available Service IDs are blocked off into five +ranges: + +| Service ID Range (Inclusive) | Service Type | Description | +|------------------------------|--------------|-------------| +| `0x0000` - `0x00FF` | Reserved | These services are treated as invalid by the watch and should never be used by a smartstrap. The `0x0000` service is currently aliased to the raw data profile by the SDK. | +| `0x0100` - `0x0FFF` | Restricted | These services are handled internally in the firmware of the watch and are not available to apps. Smartstraps may (and in the case of the management service, must) support services in this range. | +| `0x1000` - `0x1FFF` | Experimentation | These services are for pre-release product experimentation and development and should NOT be used in a commercial product. When a smartstrap is going to be sold commercially, the manufacturer should contact Pebble to request a Service ID in the "Commerical" range. | +| `0x2000` - `0x7FFF` | Spec-Defined | These services are defined below under [*Supported Services and Attributes*](#supported-services-and-attributes), and any smartstrap which implements them must strictly follow the spec to ensure compatibility. | +| `0x8000` - `0xFFFF` | Commercial | These services are allocated by Pebble to smartstrap manufacturers who will define their own attributes. | + + +**Attribute ID** + +The two byte identifier of the attribute as defined in the Supported Services +and Attributes section below. + + +**Type** + +One byte representing the type of message being transmitted. When the smartstrap +replies, it should preserve this field from the request. + +| Value | Type | Meaning | +|-------|------|---------| +| `0` | Read | This is a read request with the watch not sending any data, but expecting to get data back from the smartstrap. | +| `1` | Write | This is a write request with the watch sending data to the smartstrap, but not expected to get any data back. | +| `2` | WriteRead | This is a write+read request which consists of the watch writing data to the smartstrap **and** expecting to get some data back in response. | + + + +**Error Code** + +The error code is set by the smartstrap to indicate the result of the previous +request and must be one of the following values. + +| Value | Name | Meaning | +|-------|------|---------| +| `0` | OK | The read or write request has been fulfilled successfully. The watch should always use this value when making a request. | +| `1` | Not Supported | The requested attribute is not supported by the smartstrap. | + + +**Length** + +The length of the data in bytes. + + +#### Supported Services and Attributes + +**Management Service (Service ID: `0x0101`)** + +| Attribute ID | Attribute Name | Type | Data Type | Data | +|--------------|----------------|------|-----------|------| +| `0x0001` | Service Discovery | Read | uint16[1..10] | A list of Service ID values for all of the services supported by the smartstrap. A maximum of 10 (inclusive) services may be reported. In order to support the generic service profile, the management service must be supported and should not be reported in the response. | +| `0x0002` | Notification Info | Read | uint16_t[2] | If a read is performed by the watch after the smartstrap issues a notification, the response data should be the IDs of the service and attribute which generated the notification. | + + +**Pebble Control Service (Service ID: `0x0102`)** + +> Note: This service is not yet implemented. + +| Attribute ID | Attribute Name | Type | Data Type | Data | +|--------------|----------------|------|-----------|------| +| `0x0001` | Launch App | Read | uint8[16] | The UUID of the app to launch. | +| `0x0002` | Button Event | Read | uint8[2] | This message allows the smartstrap trigger button events on the watch. The smartstrap should send two bytes: the button being acted on and the click type. The possible values are defined below:

Buttons Values:
`0x00`: No Button
`0x01`: Back button
`0x02`: Up button
`0x03`: Select button
`0x04`: Down button

Click Types:
`0x00`: No Event
`0x01`: Single click
`0x02`: Double click
`0x03`: Long click

The smartstrap can specify a button value of `0x00` and a click type of `0x00` to indicate no pending button events. Any other use of the `0x00` values is invalid. | + + +**Location and Navigation Service (Service ID: `0x2001`)** + +| Attribute ID | Attribute Name | Type | Data Type | Data | +|--------------|----------------|------|-----------|------| +| `0x0001` | Location | Read | sint32[2] | The current longitude and latitude in degrees with a precision of 1/10^7. The latitude comes before the longitude in the data. For example, Pebble HQ is at (37.4400662, -122.1583808), which would be specified as {374400662, -1221583808}. | +| `0x0002` | Location Accuracy | Read | uint16 | The accuracy of the location in meters. | +| `0x0003` | Speed | Read | uint16 | The current speed in meters per second with a precision of 1/100. For example, 1.5 m/s would be specified as 150. | +| `0x0101` | GPS Satellites | Read | uint8 | The number of GPS satellites (typically reported via NMEA. | +| `0x0102` | GPS Fix Quality | Read | uint8 | The quality of the GPS fix (reported via NMEA). The possible values are listed in the [NMEA specification](http://www.gpsinformation.org/dale/nmea.htm#GGA). | + + +**Heart Rate Service (Service ID: `0x2002`)** + +| Attribute ID | Attribute Name | Type | Data Type | Data | +|--------------|----------------|------|-----------|------| +| `0x0001` | Measurement Read | uint8 | The current heart rate in beats per minute. | + + +**Battery Service (Service ID: `0x2003`)** + +| Attribute ID | Attribute Name | Type | Data Type | Data | +|--------------|----------------|------|-----------|------| +| `0x0001` | Charge Level | Read | uint8 | The percentage of charge left in the smartstrap battery (between 0 and 100). | +| `0x0002` | Capacity | Read | uint16 | The total capacity of the smartstrap battery in mAh when fully charged. | + + +**Notification Handling** + +When a notification is received for this profile, a "Notification Info" message +should be sent as described above. diff --git a/devsite/source/_guides/smartstraps/talking-to-pebble.md b/devsite/source/_guides/smartstraps/talking-to-pebble.md new file mode 100644 index 00000000..e640761b --- /dev/null +++ b/devsite/source/_guides/smartstraps/talking-to-pebble.md @@ -0,0 +1,260 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Talking To Pebble +description: | + Information on how to implement the smartstrap protocol to talk to the Pebble + accessory port. +guide_group: smartstraps +order: 2 +related_docs: + - Smartstrap +related_examples: + - title: Smartstrap Button Counter + url: https://github.com/pebble-examples/smartstrap-button-counter + - title: Smartstrap Library Test + url: https://github.com/pebble-examples/smartstrap-library-test +--- + +In order to communicate successfully with Pebble, the smartstrap hardware must +correctly implement the smartstrap protocol as defined in +{% guide_link smartstraps/smartstrap-protocol %}. + + +## Arduino Library + +For developers prototyping with some of the most common Arduino boards +(based on the AVR ATmega 32U4, 2560, 328, or 328P chips), the simplest way of +doing this is to use the +[ArduinoPebbleSerial](https://github.com/pebble/arduinopebbleserial) library. +This open-source reference implementation takes care of the smartstrap protocol +and allows easy communication with the Pebble accessory port. + +Download the library as a .zip file. In the Arduino IDE, go to 'Sketch' -> +'Include Library' -> 'Add .ZIP LIbrary...'. Choose the library .zip file. This +will import the library into Arduino and add the appropriate include statement +at the top of the sketch: + +```c++ +#include +``` + +After including the ArduinoPebbleSerial library, begin the sketch with the +standard template functions (these may already exist): + +```c++ +void setup() { + +} + +void loop() { + +} +``` + + +## Connecting to Pebble + +Declare the buffer to be used for transferring data (of type `uint8_t`), and its +maximum length. This should be large enough for managing the largest possible +request from the watch, but not so large that there is no memory left for the +rest of the program: + +> Note: The buffer **must** be at least 6 bytes in length to handle internal +> protocol messages. + +```c++ +// The buffer for transferring data +static uint8_t s_data_buffer[256]; +``` + +Define which service IDs the strap will support. See +{% guide_link smartstraps/smartstrap-protocol#generic-service-profile "Generic Service Profile" %} +for details on which values may be used here. An example service ID and +attribute ID both of value `0x1001` are shown below: + +```c +static const uint16_t s_service_ids[] = {(uint16_t)0x1001}; +static const uint16_t s_attr_ids[] = {(uint16_t)0x1001}; +``` + +The last decision to be made before connection is which baud rate will be used. +This will be the speed of the connection, chosen as one of the available baud +rates from the `Baud` `enum`: + +```c +typedef enum { + Baud9600, + Baud14400, + Baud19200, + Baud28800, + Baud38400, + Baud57600, + Baud62500, + Baud115200, + Baud125000, + Baud230400, + Baud250000, + Baud460800, +} Baud; +``` + +This should be chosen as the highest rate supported by the board used, to allow +the watch to save power by sleeping as much as possible. The recommended value +is `Baud57600` for most Arduino-like boards. + + +### Hardware Serial + +If using the hardware UART for the chosen board (the `Serial` library), +initialize the ArduinoPebbleSerial library in the `setup()` function to prepare +for connection: + +```c++ +// Setup the Pebble smartstrap connection +ArduinoPebbleSerial::begin_hardware(s_data_buffer, sizeof(s_data_buffer), + Baud57600, s_service_ids, 1); +``` + + +### Software Serial + +Alternatively, software serial emulation can be used for any pin on the chosen +board that +[supports interrupts](https://www.arduino.cc/en/Reference/AttachInterrupt). In +this case, initialize the library in the following manner, where `pin` is the +compatible pin number. For example, using Arduino Uno pin D8, specify a value of +`8`. As with `begin_hardware()`, the baud rate and supported service IDs must +also be provided here: + +```c++ +int pin = 8; + +// Setup the Pebble smartstrap connection using one wire software serial +ArduinoPebbleSerial::begin_software(pin, s_data_buffer, sizeof(s_data_buffer), + Baud57600, s_service_ids, 1); +``` + + +## Checking Connection Status + +Once the smartstrap has been physically connected to the watch and the +connection has been established, calling `ArduinoPebbleSerial::is_connected()` +will allow the program to check the status of the connection, and detect +disconnection on the smartstrap side. This can be indicated to the wearer using +an LED, for example: + +```c++ +if(ArduinoPebbleSerial::is_connected()) { + // Connection is valid, turn LED on + digitalWrite(7, HIGH); +} else { + // Connection is not valid, turn LED off + digitalWrite(7, LOW); +} +``` + + +## Processing Commands + +In each iteration of the `loop()` function, the program must allow the library +to process any bytes which have been received over the serial connection using +`ArduinoPebbleSerial::feed()`. This function will return `true` if a complete +frame has been received, and set the values of the parameters to inform the +program of which type of frame was received: + +```c++ +size_t length; +RequestType type; +uint16_t service_id; +uint16_t attribute_id; + +// Check to see if a frame was received, and for which service and attribute +if(ArduinoPebbleSerial::feed(&service_id, &attribute_id, &length, &type)) { + // We got a frame! + if((service_id == 0) && (attribute_id == 0)) { + // This is a raw data service frame + // Null-terminate and display what was received in the Arduino terminal + s_data_buffer[min(length_read, sizeof(s_data_buffer))] = `\0`; + Serial.println(s_data_buffer); + } else { + // This may be one of our service IDs, check it. + if(service_id == s_service_ids[0] && attribute_id == s_attr_ids[0]) { + // This frame is for our supported service! + s_data_buffer[min(length_read, sizeof(s_data_buffer))] = `\0`; + Serial.print("Write to service ID: "); + Serial.print(service_id); + Serial.print(" Attribute ID: "); + Serial.print(attribute_id); + Serial.print(": "); + Serial.println(s_data_buffer); + } + } +} +``` + +If the watch is requesting data, the library also allows the Arduino to respond +back using `ArduinoPebbleSerial::write()`. This function accepts parameters to +tell the connected watch which service and attribute is responding to the read +request, as well is whether or not the read was successful: + +> Note: A write to the watch **must** occur during processing for a +> `RequestType` of `RequestTypeRead` or `RequestTypeWriteRead`. + +```c++ +if(type == RequestTypeRead || type == RequestTypeWriteRead) { + // The watch is requesting data, send a friendly response + char *msg = "Hello, Pebble"; + + // Clear the buffer + memset(s_data_buffer, 0, sizeof(s_data_buffer)); + + // Write the response into the buffer + snprintf((char*)s_data_buffer, sizeof(s_data_buffer), "%s", msg); + + // Send the data to the watch for this service and attribute + ArduinoPebbleSerial::write(true, s_data_buffer, strlen((char*)s_data_buffer)+1); +} +``` + + +## Notifying the Watch + +To save power, it is strongly encouraged to design the communication scheme in +such a way that avoids needing the watch to constantly query the status of the +smartstrap, allowing it to sleep. To aid in this effort, the ArduinoPebbleSerial +library includes the `ArduinoPebbleSerial::notify()` function to cause the +watchapp to receive a ``SmartstrapNotifyHandler``. + +For example, to notify the watch once a second: + +```c++ +// The last time the watch was notified +static unsigned long s_last_notif_time = 0; + +void loop() { + + /* other code */ + + // Notify the watch every second + if (millis() - s_last_notif_time > 1000) { + // Send notification with our implemented serviceID and attribute ID + ArduinoPebbleSerial::notify(s_service_ids[0], s_attr_ids[0]); + + // Record the time of this notification + s_last_notif_time = millis(); + } +} +``` diff --git a/devsite/source/_guides/smartstraps/talking-to-smartstraps.md b/devsite/source/_guides/smartstraps/talking-to-smartstraps.md new file mode 100644 index 00000000..3336452a --- /dev/null +++ b/devsite/source/_guides/smartstraps/talking-to-smartstraps.md @@ -0,0 +1,377 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Talking To Smartstraps +description: | + Information on how to use the Pebble C API to talk to a connected smartstrap. +guide_group: smartstraps +order: 3 +platforms: + - basalt + - chalk + - diorite + - emery +related_docs: + - Smartstrap +--- + +To talk to a connected smartstrap, the ``Smartstrap`` API is used to establish a +connection and exchange arbitrary data. The exchange protocol is specified in +{% guide_link smartstraps/smartstrap-protocol %} and most of +it is abstracted by the SDK. This also includes handling of the +{% guide_link smartstraps/smartstrap-protocol#link-control-profile "Link Control Profile" %}. + +Read {% guide_link smartstraps/talking-to-pebble %} to learn how to use an +example library for popular Arduino microcontrollers to implement the smartstrap +side of the protocol. + +> Note: Apps running on multiple hardware platforms that may or may not include +> a smartstrap connector should use the `PBL_SMARTSTRAP` compile-time define (as +> well as checking API return values) to gracefully handle when it is not +> available. + + +## Services and Attributes + +### Generic Service Profile + +The Pebble smartstrap protocol uses the concept of 'services' and 'attributes' +to organize the exchange of data between the watch and the smartstrap. Services +are identified by a 16-bit number. Some of these service identifiers have a +specific meaning; developers should read +{% guide_link smartstraps/smartstrap-protocol#supported-services-and-attributes "Supported Services and Attributes" %} +for a complete list of reserved service IDs and ranges of service IDs that can +be used for experimentation. + +Attributes are also identified by a 16-bit number. The meaning of attribute +values is specific to the service of that attribute. The smartstrap protocol +defines the list of attributes for some services, but developers are free to +define their own list of attributes in their own services. + +This abstraction supports read and write operations on any attribute as well as +sending notifications from the strap when an attribute value changes. This is +called the Generic Service Profile and is the recommended way to exchange data +with smartstraps. + + +### Raw Data Service + +Developers can also choose to use the Raw Data Service to minimize the overhead +associated with transmitting data. To use this profile a Pebble developer will +use the same APIs described in this guide with the service ID and attribute ID +set to ``SMARTSTRAP_RAW_DATA_SERVICE_ID`` and +``SMARTSTRAP_RAW_DATA_ATTRIBUTE_ID`` SDK constants respectively. + + +## Manipulating Attributes + +The ``Smartstrap`` API uses the ``SmartstrapAttribute`` type as a proxy for an +attribute on the smartstrap. It includes the service ID of the attribute, the ID +of the attribute itself, as well as a data buffer that is used to store the +latest read or written value of the attribute. + +Before you can read or write an attribute, you need to initialize a +`SmartstrapAttribute` that will be used as a proxy for the attribute on the +smartstrap. The first step developers should take is to decide upon and define +their services and attributes: + +```c +// Define constants for your service ID, attribute ID +// and buffer size of your attribute. +static const SmartstrapServiceId s_service_id = 0x1001; +static const SmartstrapAttributeId s_attribute_id = 0x0001; +static const int s_buffer_length = 64; +``` + +Then, define the attribute globally: + +```c +// Declare an attribute pointer +static SmartstrapAttribute *s_attribute; +``` + +Lastly create the attribute during app initialization, allocating its buffer: + +```c +// Create the attribute, and allocate a buffer for its data +s_attribute = smartstrap_attribute_create(s_service_id, s_attribute_id, + s_buffer_length); +``` + +Later on, APIs such as ``smartstrap_attribute_get_service_id()`` and +``smartstrap_attribute_get_attribute_id()`` can be used to confirm these values +for any ``SmartstrapAttribute`` created previously. This is useful if an app +deals with more than one service or attribute. + +Attributes can also be destroyed when an app is exiting or no longer requires +them by using ``smartstrap_attribute_destroy()``: + +```c +// Destroy this attribute +smartstrap_attribute_destroy(s_attribute); +``` + + +## Connecting to a Smartstrap + +The first thing a smartstrap-enabled app should do is call +``smartstrap_subscribe()`` to register the handler functions (described below) +that will be called when smartstrap-related events occur. Such events can be one +of four types. + +The ``SmartstrapServiceAvailabilityHandler`` handler, used when a smartstrap +reports that a service is available, or has become unavailable. + +```c +static void strap_availability_handler(SmartstrapServiceId service_id, + bool is_available) { + // A service's availability has changed + APP_LOG(APP_LOG_LEVEL_INFO, "Service %d is %s available", + (int)service_id, is_available ? "now" : "NOT"); +} +``` + +See below under [*Writing Data*](#writing-data) and +[*Reading Data*](#reading-data) for explanations of the other callback types. + +With all four of these handlers in place, the subscription to the associated +events can be registered. + +```c +// Subscribe to the smartstrap events +smartstrap_subscribe((SmartstrapHandlers) { + .availability_did_change = strap_availability_handler, + .did_read = strap_read_handler, + .did_write = strap_write_handler, + .notified = strap_notify_handler +}); +``` + +As with the other [`Event Services`](``Event Service``), the subscription can be +removed at any time: + +```c +// Stop getting callbacks +smartstrap_unsubscribe(); +``` + +The availability of a service can be queried at any time: + +```c +if(smartstrap_service_is_available(s_service_id)) { + // Our service is available! + +} else { + // Our service is not currently available, handle gracefully + APP_LOG(APP_LOG_LEVEL_ERROR, "Service %d is not available.", (int)s_service_id); +} +``` + + +## Writing Data + +The smartstrap communication model (detailed under +{% guide_link smartstraps/smartstrap-protocol#communication-model "Communication Model" %}) +uses the master-slave principle. This one-way relationship means that Pebble can +request data from the smartstrap at any time, but the smartstrap cannot. +However, the smartstrap may notify the watch that data is waiting to be read so +that the watch can read that data at the next opportunity. + +To send data to a smartstrap an app must call +``smartstrap_attribute_begin_write()`` which will return a buffer to write into. +When the app is done preparing the data to be sent in the buffer, it calls +``smartstrap_attribute_end_write()`` to actually send the data. + +```c +// Pointer to the attribute buffer +size_t buff_size; +uint8_t *buffer; + +// Begin the write request, getting the buffer and its length +smartstrap_attribute_begin_write(attribute, &buffer, &buff_size); + +// Store the data to be written to this attribute +snprintf((char*)buffer, buff_size, "Hello, smartstrap!"); + +// End the write request, and send the data, not expecting a response +smartstrap_attribute_end_write(attribute, buff_size, false); +``` + +> Another message cannot be sent until the strap responds (a `did_write` +> callback for Write requests, or `did_read` for Read/Write+Read requests) or +> the timeout expires. Doing so will cause the API to return +> ``SmartstrapResultBusy``. Read +> {% guide_link smartstraps/talking-to-smartstraps#timeouts "Timeouts" %} for +> more information on smartstrap timeouts. + +The ``SmartstrapWriteHandler`` will be called when the smartstrap has +acknowledged the write operation (if using the Raw Data Service, there +is no acknowledgement and the callback will be called when Pebble is done +sending the frame to the smartstrap). If a read is requested (with the +`request_read` parameter of ``smartstrap_attribute_end_write()``) then the read +callback will also be called when the smartstrap sends the attribute value. + +```c +static void strap_write_handler(SmartstrapAttribute *attribute, + SmartstrapResult result) { + // A write operation has been attempted + if(result != SmartstrapResultOk) { + // Handle the failure + APP_LOG(APP_LOG_LEVEL_ERROR, "Smartstrap error occured: %s", + smartstrap_result_to_string(result)); + } +} +``` + +If a timeout occurs on a non-raw-data write request (with the `request_read` +parameter set to `false`), ``SmartstrapResultTimeOut`` will be passed to the +`did_write` handler on the watch side. + + +## Reading Data + +The simplest way to trigger a read request is to call +``smartstrap_attribute_read()``. Another way to trigger a read is to set the +`request_read` parameter of ``smartstrap_attribute_end_write()`` to `true`. In +both cases, the response will be received asynchronously and the +``SmartstrapReadHandler`` will be called when it is received. + +```c +static void strap_read_handler(SmartstrapAttribute *attribute, + SmartstrapResult result, const uint8_t *data, + size_t length) { + if(result == SmartstrapResultOk) { + // Data has been read into the data buffer provided + APP_LOG(APP_LOG_LEVEL_INFO, "Smartstrap sent: %s", (char*)data); + } else { + // Some error has occured + APP_LOG(APP_LOG_LEVEL_ERROR, "Error in read handler: %d", (int)result); + } +} + +static void read_attribute() { + SmartstrapResult result = smartstrap_attribute_read(attribute); + if(result != SmartstrapResultOk) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error reading attribute: %s", + smartstrap_result_to_string(result)); + } +} +``` + +> Note: ``smartstrap_attribute_begin_write()`` may not be called within a +> `did_read` handler (``SmartstrapResultBusy`` will be returned). + +Similar to write requests, if a timeout occurs when making a read request the +`did_read` handler will be called with ``SmartstrapResultTimeOut`` passed in the +`result` parameter. + + +## Receiving Notifications + +To save as much power as possible, the notification mechanism can be used by the +smartstrap to alert the watch when there is data that requires processing. When +this happens, the ``SmartstrapNotifyHandler`` handler is called with the +appropriate attribute provided. Developers can use this mechanism to allow the +watch to sleep until it is time to read data from the smartstrap, or simply as a +messsaging mechanism. + +```c +static void strap_notify_handler(SmartstrapAttribute *attribute) { + // The smartstrap has emitted a notification for this attribute + APP_LOG(APP_LOG_LEVEL_INFO, "Attribute with ID %d sent notification", + (int)smartstrap_attribute_get_attribute_id(attribute)); + + // Some data is ready, let's read it + smartstrap_attribute_read(attribute); +} +``` + + +## Callbacks For Each Type of Request + +There are a few different scenarios that involve the ``SmartstrapReadHandler`` +and ``SmartstrapWriteHandler``, where the callbacks to these +``SmartstrapHandlers`` will change depending on the type of request made by the +watch. + +| Request Type | Callback Sequence | +|--------------|-------------------| +| Read only | `did_write` when the request is sent. `did_read` when the response arrives or an error (e.g.: a timeout) occurs. | +| Write+Read request | `did_write` when the request is sent. `did_read` when the response arrives or an error (e.g.: a timeout) occurs. | +| Write (Raw Data Service) | `did_write` when the request is sent. | +| Write (any other service) | `did_write` when the write request is acknowledged by the smartstrap. | + +For Write requests only, `did_write` will be called when the attribute is ready +for another request, and for Reads/Write+Read requests `did_read` will be called +when the attribute is ready for another request. + + +## Timeouts + +Read requests and write requests to an attribute expect a response from the +smartstrap and will generate a timeout error if the strap does not respond +before the expiry of the timeout. + +The maximum timeout value supported is 1000ms, with the default value +``SMARTSTRAP_TIMEOUT_DEFAULT`` of 250ms. A smaller or larger value can be +specified by the developer: + +```c +// Set a timeout of 500ms +smartstrap_set_timeout(500); +``` + + +## Smartstrap Results + +When data is sent to the smartstrap, one of several results is possible. These +are returned by various API functions (such as ``smartstrap_attribute_read()``), +and are enumerated as follows: + +| Result | Value | Description | +|--------|-------|-------------| +| `SmartstrapResultOk` | `0` | No error occured. | +| `SmartstrapResultInvalidArgs` | `1` | The arguments provided were invalid. | +| `SmartstrapResultNotPresent` | `2` | The Smartstrap port is not present on this watch. | +| `SmartstrapResultBusy` | `3` | The connection is currently busy. For example, this can happen if the watch is waiting for a response from the smartstrap. | +| `SmartstrapResultServiceUnavailable` | `4` | Either a smartstrap is not connected or the connected smartstrap does not support the specified service. | +| `SmartstrapResultAttributeUnsupported` | `5` | The smartstrap reported that it does not support the requested attribute. | +| `SmartstrapResultTimeOut` | `6` | A timeout occured during the request. | + +The function shown below returns a human-readable string for each value, useful +for debugging. + +```c +static char* smartstrap_result_to_string(SmartstrapResult result) { + switch(result) { + case SmartstrapResultOk: + return "SmartstrapResultOk"; + case SmartstrapResultInvalidArgs: + return "SmartstrapResultInvalidArgs"; + case SmartstrapResultNotPresent: + return "SmartstrapResultNotPresent"; + case SmartstrapResultBusy: + return "SmartstrapResultBusy"; + case SmartstrapResultServiceUnavailable: + return "SmartstrapResultServiceUnavailable"; + case SmartstrapResultAttributeUnsupported: + return "SmartstrapResultAttributeUnsupported"; + case SmartstrapResultTimeOut: + return "SmartstrapResultTimeOut"; + default: + return "Not a SmartstrapResult value!"; + } +} +``` diff --git a/devsite/source/_guides/tools-and-resources/app-metadata.md b/devsite/source/_guides/tools-and-resources/app-metadata.md new file mode 100644 index 00000000..dfc766b9 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/app-metadata.md @@ -0,0 +1,224 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Metadata +description: | + Details of the metadata that describes the app, such as its name, + resources and capabilities. +guide_group: tools-and-resources +order: 0 +--- + +{% alert notice %} +This page applies only to developers using the SDK on their local machine; +CloudPebble allows you to manages this part of development in 'Settings'. +{% endalert %} + +## Project Defaults + +After creating a new local SDK Pebble project, additional setup of the project +behavior and metadata can be performed in the `package.json` file. By default, +the `pebble new-project` command will create a template `package.json` metadata +file, which can then be modified as required. + +The generated `package.json` metadata looks like this: + +```js +{ + "name": "example-app", + "author": "MakeAwesomeHappen", + "version": "1.0.0", + "keywords": ["pebble-app"], + "private": true, + "dependencies": {}, + "pebble": { + "displayName": "example-app", + "uuid": "5e5b3966-60b3-453a-a83b-591a13ae47d5", + "sdkVersion": "3", + "enableMultiJS": true, + "targetPlatforms": [ + "aplite", + "basalt", + "chalk" + ], + "watchapp": { + "watchface": false + }, + "messageKeys": [ + "dummy" + ], + "resources": { + "media": [] + } + } +} + +``` + +{% alert notice %} +Older projects might have an `appinfo.json` file instead of a `package.json`. +You can run `pebble convert-project` to update these projects. +{% endalert %} + +> Note: The `uuid` (Universally Unique IDentifier) field should be unique (as +> the name implies) and always properly generated. To do this on Mac OS X or +> Linux, use the `uuidgen` tool or a web service. Due to format requirements, +> these should never be manually modified. + +## Available Properties + +Options defined in `package.json` (Required fields shown in **bold**) are +listed in the table below: + +| Property | Type | Default Value |Description | +|:---------|:----:|:--------------|:------------| +| **`pebble.uuid`** | UUID | Developer-defined | Unique identifier [(UUID v4)](http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29) for the app. Generated by `pebble new-project`. | +| **`name`** | String | Developer-defined | App's short name. This is only used by npm and can't contain any non-URL-safe characters. | +| **`pebble.displayName`** | String | Developer-defined | App's long name. This is what the app will appear as on the appstore, phone and watch. | +| **`author`** | String | `"MakeAwesomeHappen"` | Name of the app's developer. | +| **`version`** | String | `"1.0.0"` | Version label for the app. Must use the format `major.minor.0`. | +| **`pebble.sdkVersion`** | String | `"3"` | The major version of the SDK that this app is being written for. | +| `pebble.targetPlatforms` | Array of strings | `["aplite", "basalt", "chalk"]` | Specify which platforms to build this app for. Read {% guide_link tools-and-resources/hardware-information %} for a list of available platforms. Defaults to all if omitted. | +| `pebble.enableMultiJS` | Boolean | `true` | Enables [use of multiple JS files](/blog/2016/01/29/Multiple-JavaScript-Files/) in a project with a CommonJS-style `require` syntax. | +| **`pebble.watchapp`** | Object | N/A | Used to configure the app behavior on the watch. See below for an example object. | +| **`.watchface`** | Boolean | `false` | Field of `watchapp`. Set to `false` to behave as a watchapp. | +| `.hiddenApp` | Boolean | `false` | Field of `watchapp`. Set to `true` to prevent the app from appearing in the system menu. | +| `.onlyShownOnCommunication` | Boolean | `false` | Field of `watchapp`. Set to `true` to hide the app unless communicated with from a companion app. | +| `pebble.capabilities` | Array of strings | None | List of capabilities that the app requires. The supported capabilities are `location`, `configurable` (detailed in {% guide_link communication/using-pebblekit-js %}), and `health` (detailed in {% guide_link events-and-services/health %}). | +| `pebble.messageKeys` | Object | `["dummy"]` | Keys used for ``AppMessage`` and ``AppSync``. This is either a list of mapping of ``AppMessage`` keys. See {% guide_link communication/using-pebblekit-js %} for more information. | +| `pebble.resources.media` | Object | `[]` | Contains an array of all of the media resources to be bundled with the app (Maximum 256 per app). See {% guide_link app-resources %} for more information. | +| `pebble.resources.publishedMedia` | Object | | Used for {% guide_link user-interfaces/appglance-c "AppGlance Slices" %} and {% guide_link pebble-timeline/pin-structure "Timeline Pins" %}. See [Published Media](#published-media) for more information. +> Note: `hiddenApp` and `onlyShownOnCommunication` are mutually exclusive. +> `hiddenApp` will always take preference. + +## Hidden Watchapp + +The example below shows an example `watchapp` object for a watchapp that is +hidden until communicated with from a companion app. + +```js +"watchapp": { + "watchface": false, + "hiddenApp": false, + "onlyShownOnCommunication": true +} +``` +## Published Media + +Introduced in SDK 4.0, `publishedMedia` provides a mechanism for applications to +specify image resources which can be used within +{% guide_link user-interfaces/appglance-c "AppGlance Slices" %} or +{% guide_link pebble-timeline/pin-structure "Timeline Pins" %}. + +The `publishedMedia` object allows your watchapp, Pebble mobile application and +Pebble web services to determine which image resources to use, based on a static +ID. Without this lookup table, the components would not be able to establish the +correct resources to use after an update to the watchapp. + +Let's take a look at the structure of `publishedMedia` in more detail. + +We'll start by declaring some image resources which we can reference in +`publishedMedia` later. In this example we have 'hot' and 'cold' icons, in 3 +different sizes. + +```js +"resources": { + "media": [ + { + "name": "WEATHER_HOT_ICON_TINY", + "type": "bitmap", + "file": "hot_tiny.png" + }, + { + "name": "WEATHER_HOT_ICON_SMALL", + "type": "bitmap", + "file": "hot_small.png" + }, + { + "name": "WEATHER_HOT_ICON_LARGE", + "type": "bitmap", + "file": "hot_large.png" + }, + { + "name": "WEATHER_COLD_ICON_TINY", + "type": "bitmap", + "file": "cold_tiny.png" + }, + { + "name": "WEATHER_COLD_ICON_SMALL", + "type": "bitmap", + "file": "cold_small.png" + }, + { + "name": "WEATHER_COLD_ICON_LARGE", + "type": "bitmap", + "file": "cold_large.png" + } + ] +} +``` + +Next we declare the `publishedMedia`: + +```js +"resources": { + // ..... + "publishedMedia": [ + { + "name": "WEATHER_HOT", + "id": 1, + "glance": "WEATHER_HOT_ICON_TINY", + "timeline": { + "tiny": "WEATHER_HOT_ICON_TINY", + "small": "WEATHER_HOT_ICON_SMALL", + "large": "WEATHER_HOT_ICON_LARGE" + } + }, { + "name": "WEATHER_COLD", + "id": 2, + "glance": "WEATHER_COLD_ICON_TINY", + "timeline": { + "tiny": "WEATHER_COLD_ICON_TINY", + "small": "WEATHER_COLD_ICON_SMALL", + "large": "WEATHER_COLD_ICON_LARGE" + } + } + ] +} + ``` +As you can see above, we can declare multiple entries in `publishedMedia`. + +Let's look at the properties in more detail: + +* `name` - This must be a unique alias to reference each entry of the +`publishedMedia` object. We use the `name` for the AppGlanceSlice +icon or Timeline Pin icon, and the system will load the appropriate resource +when required. The naming convention varies depending on where it's being used. +For example, the AppGlance C API prefixes the name: `PUBLISHED_ID_WEATHER_HOT` +but the other APIs use: `app://images/WEATHER_HOT`. +* `id` - This must be a unique number within the set of `publishedMedia`. It +must be greater than 0. +* `glance` - Optional. This references the `resource` image which will be +displayed if this `publishedMedia` is used as the `icon` of an +``AppGlanceSlice``. Size: 25x25. +* `timeline` - Optional. This references the `resource` images which will be +displayed if this `publishedMedia` is used for the various sizes of a timeline +icon. All three sizes must be specified. Sizes: Tiny 25x25, Small 50x50, Large +80x80. + +> Your `publishedMedia` entry must include either `glance`, `timeline`, or both. +> If you specify `glance` and `timeline`, the `glance` must be the same resource +> as `timeline.tiny`. +> If you remove a `publishedMedia` entry, you must not re-use the ID. diff --git a/devsite/source/_guides/tools-and-resources/cloudpebble.md b/devsite/source/_guides/tools-and-resources/cloudpebble.md new file mode 100644 index 00000000..144956ee --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/cloudpebble.md @@ -0,0 +1,309 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: CloudPebble +description: | + How to use CloudPebble to create apps with no installation required. +guide_group: tools-and-resources +order: 1 +--- + +[CloudPebble]({{ site.data.links.cloudpebble }}) is an online-only IDE +(Integrated Development Environment) for easy creation of Pebble apps. +It can be used as an alternative to the local SDK on Mac OSX and Linux, and is +the recommended app development option for Windows users. Features include: + +* All-in-one project management, code editing, compilation, and installation. + +* Intelligent real-time code completion including project symbols. + +* Browser-based emulators for testing apps and generating screenshots without + physical hardware. + +* Integration with GitHub. + +To get started, log in with a Pebble Account. This is the same account as is +used for the Pebble Forums and the local SDK. Once logged in, a list of projects +will be displayed. + + +## Creating a Project + +To create a new project, click the 'Create' button. + +![create-project](/images/guides/tools-and-resources/create-project.png =480x) + +Fill out the form including the name of the project, which type of project it +is, as well as the SDK version. Optionally begin with a project template. When +done, click 'Create'. + + +## Code Editor + +The next screen will contain the empty project, unless a template was chosen. +This is the code editor screen, with the main area on the right being used to +write source files. + +![empty-project](/images/guides/tools-and-resources/empty-project.png) + +The left-hand menu contains links to other useful screens: + +* Settings - Manage the metadata and behavior of the project as an analogue to + the local SDK's `package.json` file. + +* Timeline - Test inserting and deleting + {% guide_link pebble-timeline "Pebble timeline" %} pins. + +* Compilation - Compile and install the project, view app logs and screenshots. + This screen also contains the emulator. + +* GitHub - Configure GitHub integration for this project. + +In addition, the 'Source Files' and 'Resources' sections will list the +respective files as they are added to the project. As code is written, automatic +code completion will suggest completed symbols as appropriate. + + +## Writing Some Code + +To begin an app from a blank project, click 'Add New' next to 'Source Files'. +Choose an appropriate name for the first file (such as `main.c`), leave the +target as 'App/Watchface', and click 'Create'. + +The main area of the code editor will now display the code in the new file, +which begins with the standard include statement for the Pebble SDK: + +```c +#include +``` + +To begin a runnable Pebble app, simply add the bare bones functions for +initialization, the event loop, and deinitialization. From here everything else +can be constructed: + +```c +void init() { + +} + +void deinit() { + +} + +int main() { + init(); + app_event_loop(); + deinit(); +} +``` + +The right-hand side of the code editor screen contains convenient buttons for +use during development. + +| Button | Description | +|:------:|:------------| +| ![](/images/guides/tools-and-resources/icon-play.png) | Build and run the app as configured on the 'Compilation' screen. By default this will be the Basalt emulator. | +| ![](/images/guides/tools-and-resources/icon-save.png) | Save the currently open file. | +| ![](/images/guides/tools-and-resources/icon-reload.png) | Reload the currently open file. | +| ![](/images/guides/tools-and-resources/icon-rename.png) | Rename the currently open file. | +| ![](/images/guides/tools-and-resources/icon-delete.png) | Delete the currently open file. | + + +## Adding Project Resources + +Adding a resource (such as a bitmap or font) can be done in a similar manner as +a new code file, by clicking 'Add New' next to 'Resources' in the left-hand +pane. Choose the appropriate 'Resource Type' and choose an 'Identifier', which +will be available in code, prefixed with `RESOURCE_ID_`. + +Depending on the chosen 'Resource Type', the remaining fields will vary: + +* If a bitmap, the options pertaining to storage and optimization will be + displayed. It is recommended to use the 'Best' settings, but more information + on specific optimization options can be found in the + [*Bitmap Resources*](/blog/2015/12/02/Bitmap-Resources/#quot-bitmap-quot-to-the-rescue) + blog post. + +* If a font, the specific characters to include (in regex form) and tracking + adjust options are available to adjust the font, as well as a compatibility + option that is best left to 'Latest'. + +* If a raw resource, no extra options are available, save the 'Target Platforms' + option. + +Once the new resource has been configured, browse the the file itself and upload +it by clicking 'Save'. + + +## Installing and Running Apps + +The app under development can be compiled and run using the 'play' button on the +right-hand side of the code editor screen. If the compilation was successful, +the app will be run as configured. If not, the 'Compilation' screen will be +opened and the reason for failure can be seen by clicking 'Build Log' next to +the appropriate item in the build log. The 'Compilation' screen can be thought +of a comprehensive view of what the 'play' buttons does, with more control. Once +all code errors have been fixed, clicking 'Run Build' will do just that. + +When the build is complete, options to install and run the app are presented. +Clicking one of the hardware platform buttons will run the app on an emulator of +that platform, while choosing 'Phone' and 'Install and Run' will install and run +the app on a physical watch connected to any phone logged in with the same +Pebble account. + +In addition to running apps, the 'Compilation' screen also offers the ability to +view app log output and capture screenshots with the appropriate buttons. +Lastly, the `.pbw` bundle can also be obtained for testing and distribution in +the [Developer Portal](https://dev-portal.getpebble.com/). + + +### Interacting with the Emulator + +Once an app is installed and running in the emulator, button input can be +simulated using the highlighted regions of the emulator view decoration. There +are also additional options available under the 'gear' button. + +![](/images/guides/tools-and-resources/gear-options.png) + +From this panel, muliple forms of input can be simulated: + +* Adjust the charge level of the emulated battery with the 'Battery' slider. + +* Set whether the emulated battery is in the charging state with the 'Charging' + checkbox. + +* Set whether the Bluetooth connection is connected with the 'Bluetooth' + checkbox. + +* Set whether the emulated watch is set to use 24- or 12-hour time format with + the '24-hour' checkbox. + +* Open the app's configuration page (if applicable) with the 'App Config' + button. This will use the URL passed to `Pebble.openURL()`. See + {% guide_link user-interfaces/app-configuration %} for more information. + +* Emulate live input of the accelerometer and compass using a mobile device by + clicking the 'Sensors' button and following the instructions. + +* Shut down the emulated watch with the 'Shut Down' button. + + +## UI Editor + +In addition to adding new code files and resources, it is also possible to +create ``Window`` layouts using a GUI interface with the UI editor. To use this +feature, create a new code file by clicking 'Add New' next to 'Source Files' and +set the 'File Type' to 'Window Layout'. Choose a name for the window and click +'Create'. + +The main UI Editor screen will be displayed. This includes a preview of the +window's layout, as well as details about the current element and a toolkit of +new elements. This is shown in the image below: + +![ui-editor](/images/guides/publishing-tools/ui-editor.png) + +Since the only UI element that exists at the moment is the window itself, the +editor shows the options available for customizing this element's properties, +e.g. its background color and whether or not it is fullscreen. + + +### Adding More UI + +Add a new UI element to the window by clicking on 'Toolkit', which contains a +set of standard UI elements. Choose a ``TextLayer`` element, and all the +available properties of the ``Layer`` to change its appearence and position will +be displayed: + +![ui-editor-textlayer](/images/guides/publishing-tools/ui-editor-textlayer.png) + +In addition to manually typing in the ``Layer``'s dimensions, use the anchor +points on the preview of the ``Layer`` on the left of the editor to click and +drag the size and position of the ``TextLayer``. + +![ui-editor-drag](/images/guides/publishing-tools/ui-editor-drag.gif =148x) + +When satisfied with the new ``TextLayer``'s configuration, use the 'UI Editor' +button on the right-hand side of the screen to switch back to the normal code +editor screen, where the C code that represents the ``Layer`` just set up +visually can be seen. An example of this generated code is shown below, along +with the preview of that layout. + +```c +// BEGIN AUTO-GENERATED UI CODE; DO NOT MODIFY +static Window *s_window; +static GFont s_res_gothic_24_bold; +static TextLayer *s_textlayer_1; + +static void initialise_ui(void) { + s_window = window_create(); + window_set_fullscreen(s_window, false); + + s_res_gothic_24_bold = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD); + // s_textlayer_1 + s_textlayer_1 = text_layer_create(GRect(0, 0, 144, 40)); + text_layer_set_text(s_textlayer_1, "Hello, CloudPebble!"); + text_layer_set_text_alignment(s_textlayer_1, GTextAlignmentCenter); + text_layer_set_font(s_textlayer_1, s_res_gothic_24_bold); + layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_1); +} + +static void destroy_ui(void) { + window_destroy(s_window); + text_layer_destroy(s_textlayer_1); +} +// END AUTO-GENERATED UI CODE +``` + +![ui-editor-preview](/images/guides/publishing-tools/ui-editor-preview.png =148x) + +> Note: As marked above, the automatically generated code should **not** be +> modified, otherwise it will not be possible to continue editing it with the +> CloudPebble UI Editor. + + +### Using the New Window + +After using the UI Editor to create the ``Window``'s layout, use the two +functions provided in the generated `.h` file to include it in the app: + +`main_window.h` + +```c +void show_main_window(void); +void hide_main_window(void); +``` + +For example, call the `show_` function as part of the app's initialization +procedure and the `hide_` function as part of its deinitialization. Be sure to +include the new header file after `pebble.h`: + +```c +#include +#include "main_window.h" + +void init() { + show_main_window(); +} + +void deinit() { + hide_main_window(); +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} +``` diff --git a/devsite/source/_guides/tools-and-resources/color-picker.html b/devsite/source/_guides/tools-and-resources/color-picker.html new file mode 100644 index 00000000..1e946d82 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/color-picker.html @@ -0,0 +1,91 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: guides/wide +title: Color Picker Tool +description: | + Preview all the colors available on Pebble with the associated SDK contants + and HTML codes. +guide_group: tools-and-resources +order: 5 +scripts: + - tools/color-dict + - tools/color-picker + - tools/color-mapping-sunlight +--- +

+ Click a hexagon in the color map to see its SDK constant and how to use + it in your Pebble app. +

+
+
+ {% include tools/color-picker-map.svg %} +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name:
Sample:
HTML code:
Uncorrected HTML code:
SDK Constant:
Code (RGB):
Code (Hex):
+

+

Example Code Segment

+
+
+
+

+ See the + + Images + + guide to get compatible palette files for popular image editing + programs. +

+
+
+
diff --git a/devsite/source/_guides/tools-and-resources/developer-connection.md b/devsite/source/_guides/tools-and-resources/developer-connection.md new file mode 100644 index 00000000..7561e273 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/developer-connection.md @@ -0,0 +1,73 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Developer Connection +description: | + How to enable and use the Pebble Developer Connection to install and debug + apps directly from a computer. +guide_group: tools-and-resources +order: 3 +--- + +In order to install apps from the local SDK using the `pebble` tool or from +[CloudPebble]({{ site.links.cloudpebble }}), the Pebble Android or iOS app must +be set up to allow a connection from the computer to the watch. This enables +viewing logs and installing apps directly from the development environment, +speeding up development. + +Follow the steps illustrated below to get started. + + +## Android Instructions + +In the Pebble mobile app: + +* Open the overflow menu by tapping the three dot icon at the top right and tap + *Settings*. + + ![](/images/guides/publishing-tools/enable-dev-android-1.png =300x) + +* Enable the *Developer Mode* option. + + ![](/images/guides/publishing-tools/enable-dev-android-2.png =300x) + +* Tap *Developer Connection* and enable the developer connection using the + toggle at the top right. + + ![](/images/guides/publishing-tools/enable-dev-android-4.png =300x) + +* If using the `pebble` tool, make note of the 'Server IP'. If using + CloudPebble, this will be handled automatically. + + +## iOS Instructions + +In the Pebble mobile app: + +* Open the left-hand menu and tap the *Settings* item. + + ![](/images/guides/publishing-tools/enable-dev-ios-1.png =300x) + +* Enable the Developer Mode using the toggle. + + ![](/images/guides/publishing-tools/enable-dev-ios-2.png =300x) + +* Return to the menu, then tap the *Developer* item. Enable the Developer + Connection using the toggle at the top-right. + + ![](/images/guides/publishing-tools/enable-dev-ios-3.png =300x) + +* If using the `pebble` tool, make note of the phone's 'Listening on' IP + address. If using CloudPebble, this will be handled automatically. diff --git a/devsite/source/_guides/tools-and-resources/hardware-information.md b/devsite/source/_guides/tools-and-resources/hardware-information.md new file mode 100644 index 00000000..5d038e0f --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/hardware-information.md @@ -0,0 +1,37 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Hardware Information +description: | + Details of the the capabilities of the various Pebble hardware platforms. +guide_group: tools-and-resources +order: 4 +--- + +The Pebble watch family comprises of multiple generations of hardware, each with +unique sets of features and capabilities. Developers wishing to reach the +maximum number of users will want to account for these differences when +developing their apps. + +The table below details the differences between hardware platforms: + +{% include hardware-platforms.html %} + +See +{% guide_link best-practices/building-for-every-pebble#available-defines-and-macros "Available Defines and Macros" %} +for a complete list of compile-time defines available. + +**NOTE:** We'll be updating the "Building for Every Pebble Guide" and "Available +Defines and Macros" list when we release the first preview version of SDK 4.0. diff --git a/devsite/source/_guides/tools-and-resources/index.md b/devsite/source/_guides/tools-and-resources/index.md new file mode 100644 index 00000000..48ed3a82 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/index.md @@ -0,0 +1,36 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Tools and Resources +description: | + Information on all the software tools available when writing Pebble apps, as + well as other resources. +guide_group: tools-and-resources +menu: false +permalink: /guides/tools-and-resources/ +generate_toc: false +hide_comments: true +--- + +This section of the developer guides contains information and resources +surrounding management of SDK projects themselves, as well as additional +information on using the Pebble emulator, CloudPebble, and the local SDK +command-line utility. Developers looking to add internationalization support can +also find information and tools for that purpose here. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} \ No newline at end of file diff --git a/devsite/source/_guides/tools-and-resources/internationalization.md b/devsite/source/_guides/tools-and-resources/internationalization.md new file mode 100644 index 00000000..306fc486 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/internationalization.md @@ -0,0 +1,270 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Internationalization +description: | + How to localize an app to multiple languages. +guide_group: tools-and-resources +order: 5 +--- + +When distributing an app in the Pebble appstore, it can be downloaded by Pebble +users all over the world. Depending on the app, this means that developers may +want to internationalize their apps for a varierty of different locales. The +locale is the region in the world in which the user may be using an app. The +strings used in the app to convey information should be available as +translations in locales where the app is used by speakers of a different +language. This is the process of localization. For example, users in France may +prefer to see an app in French, if the translation is available. + + +## Internationalization on Pebble + +The Pebble SDK allows an app to be localized with the ``Internationalization`` +API. Developers can use this to adjust how their app displays and behaves to +cater for users in non-English speaking countries. By default all apps are +assumed to be in English. Choose the user selected locale using +``i18n_get_system_locale()`` or to force one using ``setlocale()``. + +> Note: The app's locale also affects ``strftime()``, which will output +> different strings for different languages. Bear in mind that these strings may +> be different lengths and use different character sets, so the app's layout +> should scale accordingly. + + +## Locales Supported By Pebble + +This is the set of locales that are currently supported: + +| Language | Region | Value returned by ``i18n_get_system_locale()`` | +|----------|--------|------------------------------------------------| +| English | United States | `"en_US"` | +| French | France | `"fr_FR"` | +| German | Germany | `"de_DE"` | +| Spanish | Spain | `"es_ES"` | +| Italian | Italy | `"it_IT"` | +| Portuguese | Portugal | `"pt_PT"` | +| Chinese | China | `"en_CN"` | +| Chinese | Taiwan | `"en_TW"` | + + +When the user's preferred language is set using the Pebble Time mobile app, the +required characters will be loaded onto Pebble and will be used by the +compatible system fonts. **This means that any custom fonts used should include +the necessary locale-specific characters in order to display correctly.** + + +## Translating an App + +By calling `setlocale(LC_ALL, "")`, the app can tell the system that it supports +internationalization. Use the value returned by ``setlocale()`` to translate any +app strings. The implementation follows the +[libc convention](https://www.gnu.org/software/libc/manual/html_node/Setting-the-Locale.html#Setting-the-Locale). +Users may expect text strings, images, time display formats, and numbers to +change if they use an app in a certain language. + +Below is a simple approach to providing a translation: + +```c +// Use selocale() to obtain the system locale for translation +char *sys_locale = setlocale(LC_ALL, ""); + +// Set the TextLayer's contents depending on locale +if (strcmp("fr_FR", sys_locale) == 0) { + text_layer_set_text(s_output_layer, "Bonjour tout le monde!"); +} else if ( /* Next locale string comparison */ ) { + /* other language cases... */ +} else { + // Fall back to English + text_layer_set_text(s_output_layer, "Hello, world!"); +} +``` + +The above method is most suitable for a few strings in a small number of +languages, but may become unwieldly if an app uses many strings. A more +streamlined approach for these situations is given below in +[*Using Locale Dictionaries*](#using-locale-dictionaries). + + +## Using the Locale in JavaScript + +Determine the language used by the user's phone in PebbleKit JS using the +standard +[`navigator`](http://www.w3schools.com/jsref/prop_nav_language.asp) property: + +```js +console.log('Phone language is ' + navigator.language); +``` + +``` +[INFO ] js-i18n-example__1.0/index.js:19 Phone language is en-US +``` + +This will reflect the user's choice of 'Language & Input' -> 'Language' on +Android and 'General' -> 'Language & Region' -> 'i[Pod or Phone] Language' on +iOS. + +> Note: Changing the language on these platforms will require a force close of +> the Pebble app or device restart before changes take effect. + + +## Choosing the App's Locale + +It is also possible to explicitly set an app's locale using ``setlocale()``. +This is useful for providing a language selection option for users who wish to +do so. This function takes two arguments; the `category` and the `locale` +string. To set localization **only** for the current time, use `LC_TIME` as the +`category`, else use `LC_ALL`. The `locale` argument accepts the ISO locale +code, such as `"fr_FR"`. Alternatively, set `locale` to `NULL` to query the +current locale or `""` (an empty string) to set the app's locale to the system +default. + +```c +// Force the app's locale to French +setlocale(LC_ALL, "fr_FR"); +``` + +To adapt the way an application displays time, call `setlocale(LC_TIME, "")`. +This will change the locale used to display time and dates in ``strftime()`` +without translating any strings. + + +## Effect of Setting a Locale + +Besides returning the value of the current system locale, calling +``setlocale()`` will have a few other effects: + +* Translate month and day names returned by ``strftime()`` `%a`, `%A`, `%b`, and + `%B`. + +* Translate the date and time representation returned by ``strftime()`` `%c`. + +* Translate the date representation returned by ``strftime()`` `%x`. + +Note that currently the SDK will not change the decimal separator added by the +`printf()` family of functions when the locale changes; it will always be a `.` +regardless of the currently selected locale. + + +## Using Locale Dictionaries + +A more advanced method to include multiple translations of strings in an app +(without needing a large number of `if`, `else` statements) is to use the +[Locale Framework](https://github.com/pebble-hacks/locale_framework). This +framework works by wrapping every string in the project's `src` directory in +the `_()` and replacing it with with a hashed value, which is then used with +the current locale to look up the translated string from a binary resource file +for that language. + +Instructions for using this framework are as follows: + +* Add `hash.h`, `localize.h` and `localize.c` to the project. This will be the + `src` directory in the native SDK. + +* Include `localize.h` in any file to be localized: + +```c +#include "localize.h" +``` + +* Initalize the framework at the start of the app's execution: + +```c +int main(void) { + // Init locale framework + locale_init(); + + /** Other app setup code **/ +} +``` + +* For every string in each source file to be translated, wrap it in `_()`: + +```c +text_layer_set_text(some_layer, _("Meal application")); +``` + +* Save all source files (this will require downloading and extracting the + project from 'Settings' on CloudPebble), then run `get_dict.py` from the + project root directory to get a JSON file containing the hashed strings to be + translated. This file will look like the one below: + +``` +{ + "1204784839": "Breakfast Time", + "1383429240": "Meal application", + "1426781285": "A fine meal with family", + "1674248270": "Lunch Time", + "1753964326": "Healthy in a hurry", + "1879903262": "Start your day right", + "2100983732": "Dinner Time" +} +``` + +* Create a copy of the JSON file and perform translation of each string, keeping + the equivalent hash values the same. Name the file according to the language, + such as `locale_french.json`. + +* Convert both JSON files into app binary resource files: + +``` +$ python dict2bin.py locale_english.json +$ python dict2bin.py locale_french.json +``` + +* Add the output binary files as project resources as described in + {% guide_link app-resources/raw-data-files %}. + +When the app is compiled and run, the `_()` macro will be replaced with calls +to `locale_str()`, which will use the app's locale to load the translated string +out of the binary resource files. + +> Note: If the translation lookup fails, the framework will fall back to the +> English binary resource file. + +## Publishing Localized Apps + +The appstore does not yet support localizing an app's resources (such as name, +description, images etc), so use this guide to prepare the app itself. To cater +for users viewing appstore listings in a different locale, include a list of +supported languages in the listing description, which itself can be written in +multiple languages. + +The format for a multi-lingual appstore description should display the supported +languages at the top and a include a copy in each language, as shown in the +example below: + +> Now supports - French, German and Spanish. +> +> This compass app allows you to navigate in any directions from your wrist! +> Simply open the app and tilt to show the layout you prefer. +> +> Français +> +> Cette application de la boussole vous permet de naviguer dans toutes les +> directions à partir de votre poignet! Il suffit d'ouvrir l'application et de +> l'inclinaison pour montrer la disposition que vous préférez. +> +> Deutsch +> +> Dieser Kompass App ermöglicht es Ihnen, in jeder Richtung vom Handgelenk zu +> navigieren! Öffnen Sie einfach die App und kippen, um das Layout Sie +> bevorzugen zu zeigen. +> +> Español +> +> Esta aplicación brújula le permite navegar en cualquier dirección de la muñeca +> ! Basta con abrir la aplicación y la inclinación para mostrar el diseño que +> prefiera. diff --git a/devsite/source/_guides/tools-and-resources/pebble-tool.md b/devsite/source/_guides/tools-and-resources/pebble-tool.md new file mode 100644 index 00000000..a3477aa8 --- /dev/null +++ b/devsite/source/_guides/tools-and-resources/pebble-tool.md @@ -0,0 +1,633 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Command Line Tool +description: | + How to use the Pebble command line tool to build, debug, and emulate apps. +guide_group: tools-and-resources +order: 2 +toc_max_depth: 2 +--- + +{% alert notice %} +This page applies only to developers using the SDK on their local machine; +CloudPebble allows you to use many of these features in 'Compilation'. +{% endalert %} + +The Pebble SDK includes a command line tool called `pebble`. This tool allows +developers to create new projects, build projects, install apps on a watch and debug +them. This tool is part of the SDK, but many of these functions are made +available on [CloudPebble]({{ site.links.cloudpebble }}). + + +## Enabling the Developer Connection + +Most `pebble` commands allow interaction with a Pebble watch. This relies on a +communication channel opened between the `pebble` tool on a computer and the +Pebble mobile application on the phone. + +The `pebble` tool requires two configuration steps: + +1. Enable the {% guide_link tools-and-resources/developer-connection %} in the + Pebble mobile application. +2. Give the phone IP address to the `pebble` tool as shown below to communicate + with a watch. This is not required when using the emulator or connecting via + CloudPebble. + + +## Connecting to a Pebble + +There are four connection types possible with the command line tool. These can +be used with any command that involves a watch, for example `install`. + +Connect to a physical watch connected to a phone on the same Wi-Fi network with +`IP` [IP address](#enabling-the-developer-connection): + +```nc|text +$ pebble install --phone IP +``` + +Connect to an already running QEMU instance available on the `HOST` and `PORT` +provided: + +```nc|text +$ pebble install --qemu HOST:PORT +``` + +Connect directly to a watch using a local Bluetooth connection, where `SERIAL` +is the path to the device file. On OS X, this is similar to +`/dev/cu.PebbleTimeXXXX-SerialPo` or `/dev/cu.PebbleXXXX-SerialPortSe`: + +> Note: Replace 'XXXX' with the actual value for the watch in question. + +```nc|text +$ pebble install --serial SERIAL +``` + +Alternatively, connect to a watch connected to the phone via the CloudPebble +proxy, instead of local Wi-Fi. This removes the need for local inter-device +communication, but still requires an internet connection on both devices: + +```nc|text +$ pebble install --cloudpebble +``` + + +## Configure the Pebble Tool + +Save the IP address of the phone in an environment variable: + +```text +$ export PEBBLE_PHONE=192.168.1.42 +``` + +Save the default choice of emulator platform: + +```text +$ export PEBBLE_EMULATOR=aplite +``` + +Save the default instance of QEMU: + +```text +$ export PEBBLE_QEMU=localhost:12344 +``` + +{% comment %} +Always use the CloudPebble proxy: + +```text +$ export PEBBLE_CLOUDPEBBLE +``` +{% endcomment %} + + +## Debugging Output + +Get up to four levels of `pebble` tool log verbosity. This is available for all +commands: + +```nc|text +# Minimum level of verbosity +$ pebble install -v + +# Maximum logging verbosity +$ pebble install -vvvv +``` + + +## Installing Watchapps + +In addition to interacting with a physical Pebble watch, the Pebble emulator +included in the local SDK (as well as on CloudPebble) can be used to run and +debug apps without any physical hardware. + +Build an app, then use `pebble install` with the `--emulator` flag to choose +between an emulator platform with `aplite`, `basalt`, or `chalk`. This allows +developers to test out apps on all platforms before publishing to the Pebble +appstore. For example, to emulate Pebble Time or Pebble Time Steel: + +```nc|text +$ pebble install --emulator basalt +``` + +> Note: If no `--emulator` parameter is specified, the running emulator will +> be used. + +With a suitable color app installed, the emulator will load and display it: + +![](/images/guides/publishing-tools/emulator.png) + +The Pebble physical buttons are emulated with the following mapping: + +* Back - Left arrow key + +* Up - Up arrow key + +* Select - Right arrow key + +* Down - Down arrow key + + +## Pebble Timeline in the Emulator + +With the emulator, it is also possible to view any past and future pins on the +Pebble timeline by pressing Up or Down from the main watchface screen: + + + + + + + + + +
Timeline, past viewTimeline, future view
+ + +## Commands + +This section summarizes the available commands that can be used in conjunction +with the `pebble` tool, on the applicable SDK versions. Most are designed to +interact with a watch with `--phone` or the emulator with `--emulator`. + + +### Project Management + +#### new-project + +```nc|text +$ pebble new-project [--simple] [--javascript] [--worker] [--rocky] NAME +``` + +Create a new project called `NAME`. This will create a directory in the +location the command is used, containing all essential project files. + +There are also a number of optional parameters that allow developers to choose +features of the generated project to be created automatically: + +* `--simple` - Initializes the main `.c` file with a minimal app template. + +* `--javascript` - Creates a new application including a `./src/pkjs/index.js` file + to quickly start an app with a PebbleKit JS component. Read + {% guide_link communication/using-pebblekit-js %} for more information. + +* `--worker` - Creates a new application including a ` + ./worker_src/NAME_worker.c` file to quickly start an app with a background + worker. Read {% guide_link events-and-services/background-worker %} for more + information. + +* `--rocky` - Creates a new Rocky.js application. Do not use any other optional + parameters with this command. Read + [Rocky.js documentation](/docs/rockyjs/) for more information. + + +#### build + +```nc|text +$ pebble build +``` + +Compile and build the project into a `.pbw` file that can be installed on a +watch or the emulator. + + +#### install + +```nc|text +$ pebble install [FILE] +``` + +Install the current project to the watch connected to the phone with the given +`IP` address, or to the emulator on the `PLATFORM`. See +{% guide_link tools-and-resources/hardware-information %} for a list of +available platforms. + +For example, the Aplite platform is specified as follows: + +```nc|text +$ pebble install --emulator aplite +``` + +It is also possible to use this command to install a pre-compiled `.pbw` `FILE`. +In this case, the specified file will be installed instead of the current +project. + +> Note: A `FILE` parameter is not required if running `pebble install` from +> inside a buildable project directory. In this case, the `.pbw` package is +> found automatically in `./build`. + + +#### clean + +```nc|text +$ pebble clean +``` + +Delete all build files in `./build` to prepare for a clean build, which can +resolve some state inconsistencies that may prevent the project from building. + + +#### convert-project + +```nc|text +$ pebble convert-project +``` + +Convert an existing Pebble project to the current SDK. + +> Note: This will only convert the project, the source code will still have to +> updated to match any new APIs. + + +### Pebble Interaction + +#### logs + +```nc|text +$ pebble logs +``` + +Continuously display app logs from the watch connected to the phone with the +given `IP` address, or from a running emulator. + +App log output is colorized according to log level. This behavior can be forced +on or off by specifying `--color` or `--no-color` respectively. + + +#### screenshot + +```nc|text +$ pebble screenshot [FILENAME] +``` + +Take a screenshot of the watch connected to the phone with the given `IP` +address, or from any running emulator. If provided, the output is saved to +`FILENAME`. + +Color correction may be disabled by specifying `--no-correction`. The +auto-opening of screenshots may also be disabled by specifying `--no-open`. + + +#### ping + +```nc|text +$ pebble ping +``` + +Send a `ping` to the watch connected to the phone with the given `IP` address, +or to any running emulator. + + +#### repl + +```nc|text +$ pebble repl +``` + +Launch an interactive python shell with a `pebble` object to execute methods on, +using the watch connected to the phone with the given `IP` address, or to any +running emulator. + + +#### data-logging + +##### list + +```nc|text +$ pebble data-logging list +``` + +List the current data logging sessions on the watch. + +##### download + +```nc|text +$ pebble data-logging download --session-id SESSION-ID FILENAME +``` + +Downloads the contents of the data logging session with the given ID +(as given by `pebble data-logging list`) to the given filename. + +##### disable-sends + +```nc|text +pebble data-logging disable-sends +``` + +Disables automatically sending data logging to the phone. This enables +downloading data logging information without the phone's interference. +It will also suspend any other app depending on data logging, which +includes some firmware functionality. You should remember to enable +it once you are done testing. + +##### enable-sends + + +```nc|text +pebble data-logging enable-sends +``` + +Re-enables automatically sending data logging information to the phone. + +##### get-sends-enabled + +```nc|text +pebble data-logging get-sends-enabled +``` + +Indicates whether data logging is automatically sent to the phone. Change +state with `enable-sends` and `disable-sends`. + + + +### Emulator Interaction + + +#### gdb + +```nc|text +$ pebble gdb +``` + +Use [GDB](https://www.gnu.org/software/gdb/) to step through and debug an app +running in an emulator. Some useful commands are displayed in a cheat sheet +when run with `--help` and summarized in the table below: + +> Note: Emulator commands that rely on interaction with the emulator (e.g. +> `emu-app-config`) will not work while execution is paused with GDB. + +| Command | Description | +|---------|-------------| +| ctrl-C | Pause app execution. | +| `continue` or `c` | Continue app execution. The app is paused while a `(gdb)` prompt is available. | +| ctrl-D or `quit` | Quit gdb. | +| `break` or `b` | Set a breakpoint. This can be either a symbol or a position:
- `b function_name` to break when entering a function.
- `b file_name.c:45` to break on line 45 of `file_name.c`. | +| `step` or `s` | Step forward one line. | +| `next` or `n` | Step *over* the current line, avoiding stopping for any functions it calls into. | +| `finish` | Run forward until exiting the current stack frame. | +| `backtrace` or `bt` | Print out the current call stack. | +| `p [expression]` | Print the result of evaluating the given `expression`. | +| `info args` | Show the values of arguments to the current function. | +| `info locals` | Show local variables in the current frame. | +| `bt full` | Show all local variables in all stack frames. | +| `info break` | List break points (#1 is ``, and is inserted by the `pebble` tool). | +| `delete [n]` | Delete breakpoint #n. | + +Read {% guide_link debugging/debugging-with-gdb %} for help on using GDB to +debug app. + + +#### emu-control + +```nc|text +$ pebble emu-control [--port PORT] +``` + +Send near real-time accelerometer and compass readings from a phone, tablet, or +computer to the emulator. When run, a QR code will be displayed in the terminal +containing the IP address the device should connect to. Scanning the code will +open this address in the device's browser. The IP address itself is also printed +in the terminal for manual entry. `PORT` may be specified in order to make +bookmarking the page easier. + +![qr-code](/images/guides/publishing-tools/qr-code.png =450x) + +After connecting, the device's browser will display a UI with two modes of +operation. + +![accel-ui](/images/guides/publishing-tools/accel-ui.png =300x) + +The default mode will transmit sensor readings to the emulator as they are +recorded. By unchecking the 'Use built-in sensors?' checkbox, manual values for +the compass bearing and each of the accelerometer axes can be transmitted to the +emulator by manipulating the compass rose or axis sliders. Using these UI +elements will automatically uncheck the aforementioned checkbox. + +The values shown in bold are the Pebble SDK values, scaled relative to +``TRIG_MAX_ANGLE`` in the case of the compass bearing value, and in the range of ++/-4000 (+/- 4g) for the accelerometer values. The physical measurements with +associated units are shown beside in parentheses. + + +#### emu-app-config + +```nc|text +$ pebble emu-app-config [--file FILE] +``` + +Open the app configuration page associated with the running app, if any. Uses +the HTML configuration `FILE` if provided. + +See {% guide_link user-interfaces/app-configuration %} to learn about emulator- +specific configuration behavior. + + +#### emu-tap + +```nc|text +$ pebble emu-tap [--direction DIRECTION] +``` + +Send an accelerometer tap event to any running emulator. `DIRECTION` can be one +of `x+`, `x-`, `y+`, `y-`, `z+`, or `z-`. + + +#### emu-bt-connection + +```nc|text +$ pebble emu-bt-connection --connected STATE +``` + +Send a Bluetooth connection event to any running emulator. `STATE` can be either +`yes` or `no` for connected and disconnected events respectively. + +> Note: The disconnected event may take a few seconds to occur. + + +#### emu-compass + +```nc|text +$ pebble emu-compass --heading BEARING [--uncalibrated | --calibrating | --calibrated] +``` + +Send a compass update event to any running emulator. `BEARING` must be a number +between `0` and `360` to be the desired compass bearing. Use any of +`--uncalibrated`, `--calibrating`, or `--calibrated` to set the calibration +state. + + +#### emu-battery + +```nc|text +$ pebble emu-battery [--percent LEVEL] [--charging] +``` + +Send a battery update event to any running emulator. `LEVEL` must be a number +between `0` and `100` to represent the new battery level. The presence of +`--charging` will determine whether the charging cable is plugged in. + + +#### emu-accel + +```nc|text +$ pebble emu-accel DIRECTION [--file FILE] +``` + +Send accelerometer data events to any running emulator. `DIRECTION` can be any +of `tilt_left`, `tilt_right`, `tilt_forward`, `tilt_back`, `gravity+x`, +`gravity-x`, `gravity+y`, `gravity-y`, `gravity+z`, `gravity-z` , and `custom`. +If `custom` is selected, specify a `FILE` of comma-separated x, y, and z +readings. + + +#### transcribe + +```nc|text +$ pebble transcribe [message] [--error {connectivity,disabled,no-speech-detected}] +``` + +Run a server that will act as a transcription service. Run it before +invoking the ``Dictation`` service in an app. If `message` is provided, +the dictation will be successful and that message will be provided. +If `--error` is provided, the dictation will fail with the given error. +`--error` and `message` are mutually exclusive. + +```nc|text +$ pebble transcribe "Hello, Pebble!" +``` + + +#### kill + +```nc|text +$ pebble kill +``` + +Kill both the Pebble emulator and phone simulator. + + +#### emu-time-format + +```nc|text +pebble emu-time-format --format FORMAT +``` + +Set the time format of the emulator to either 12-hour or 24-hour format, with a +`FORMAT` value of `12h` or `24h` respectively. + + +#### emu-set-timeline-quick-view + +```nc|text +$ pebble emu-set-timeline-quick-view STATE +``` + +Show or hide the Timeline Quick View system overlay. STATE can be `on` or `off`. + + +#### wipe + +```nc|text +$ pebble wipe +``` + +Wipe data stored for the Pebble emulator, but not the logged in Pebble account. +To wipe **all** data, specify `--everything` when running this command. + + +### Pebble Account Management + +#### login + +```nc|text +$ pebble login +``` + +Launces a browser to log into a Pebble account, enabling use of `pebble` tool +features such as pushing timeline pins. + + +#### logout + +```nc|text +$ pebble logout +``` + +Logs out the currently logged in Pebble account from this instance of the +command line tool. + + +### Pebble Timeline Interaction + +#### insert-pin + +```nc|text +$ pebble insert-pin FILE [--id ID] +``` + +Push a JSON pin `FILE` to the Pebble timeline. Specify the pin `id` in the +`FILE` as `ID`. + + +#### delete-pin + +```nc|text +$ pebble delete-pin FILE [--id ID] +``` + +Delete a pin previously pushed with `insert-pin`, specifying the same pin `ID`. + + +## Data Collection for Analytics + +When first run, the `pebble` tool will ask for permission to collect usage +information such as which tools are used and the most common errors. Nothing +personally identifiable is collected. + +This information will help us improve the SDK and is extremely useful for us, +allowing us to focus our time on the most important parts of the SDK as +discovered through these analytics. + +To disable analytics collection, run the following command to stop sending +information: + +```text +# Mac OSX +$ touch ~/Library/Application\ Support/Pebble\ SDK/NO_TRACKING + +# Other platforms +$ touch ~/.pebble-sdk/NO_TRACKING +``` diff --git a/devsite/source/_guides/user-interfaces/app-configuration-static.md b/devsite/source/_guides/user-interfaces/app-configuration-static.md new file mode 100644 index 00000000..7bafe840 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/app-configuration-static.md @@ -0,0 +1,321 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Configuration (manual setup) +description: | + How to allow users to customize an app with a static configuration page. +guide_group: +order: 0 +platform_choice: true +--- + +> This guide provides the steps to manually create an app configuration page. +> The preferred approach is to use +> {% guide_link user-interfaces/app-configuration "Clay for Pebble" %} instead. + +Many watchfaces and apps in the Pebble appstore include the ability to customize +their behavior or appearance through the use of a configuration page. This +mechanism consists of an HTML form that passes the user's chosen configuration +data to PebbleKit JS, which in turn relays it to the watchface or watchapp. + +The HTML page created needs to be hosted online, so that it is accessible to +users via the Pebble application. If you do not want to host your own HTML +page, you should follow the +{% guide_link user-interfaces/app-configuration "Clay guide" %} to create a +local config page. + +App configuration pages are powered by PebbleKit JS. To find out more about +PebbleKit JS, +{% guide_link communication/using-pebblekit-js "read the guide" %}. + + + +## Adding Configuration + +^LC^ For an app to be configurable, it must marked as 'configurable' in the +app's {% guide_link tools-and-resources/app-metadata "`package.json`" %} +`capabilities` array. The presence of this value tells the mobile app to +display a gear icon next to the app, allowing users to access the configuration +page. + +
+{% markdown %} +```js +"capabilities": [ "configurable" ] +``` +{% endmarkdown %} +
+ +^CP^ For an app to be configurable, it must include the 'configurable' item in +'Settings'. The presence of this value tells the mobile app to display the +gear icon that is associated with the ability to launch the config page. + + + +## Choosing Key Values + +^LC^ Since the config page must transmit the user's preferred options to the +watchapp, the first step is to decide upon the ``AppMessage`` keys defined in +`package.json` that will be used to represent the chosen value for each option +on the config page: + +
+{% markdown %} +```js +"messageKeys": [ + "BackgroundColor", + "ForegroundColor", + "SecondTick", + "Animations" +] +``` +{% endmarkdown %} +
+ +^CP^ Since the config page must transmit the user's preferred options to the +watchapp, the first step is to decide upon the ``AppMessage`` keys defined in +'Settings' that will be used to represent each option on the config page. An +example set is shown below: + +
+{% markdown %} +* `BackgroundColor` + +* `ForegroundColor` + +* `SecondTick` + +* `Animations` +{% endmarkdown %} +
+ +These keys will automatically be available both in C on the watch and in +PebbleKit JS on the phone. + +Each of these keys will apply to the appropriate input element on the config +page, with the user's chosen value transmitted to the watchapp's +``AppMessageInboxReceived`` handler once the page is submitted. + + + +## Showing the Config Page + +Once an app is marked as `configurable`, the PebbleKit JS component must +implement `Pebble.openURL()` in the `showConfiguration` event handler in +`index.js` to present the developer's HTML page when the user wants to configure +the app: + +```js +Pebble.addEventListener('showConfiguration', function() { + var url = 'http://example.com/config.html'; + + Pebble.openURL(url); +}); +``` + + +## Creating the Config Page + +The basic structure of an HTML config page begins with a template HTML file: + +> Note: This page will be plain and unstyled. CSS styling must be performed +> separately, and is not covered here. + +```html + + + + Example Configuration + + +

This is an example HTML forms configuration page.

+ + +``` + +The various UI elements the user will interact with to choose their preferences +must be placed within the `body` tag, and will most likely take the form of +HTML `input` elements. For example, a text input field for each of the example +color options will look like the following: + +```html + + Background Color + + + Foreground Color + +``` + +Other components include checkboxes, such as the two shown below for each of +the example boolean options: + +```html + + Enable Second Ticks + + + Show Animations + +``` + +The final element should be the 'Save' button, used to trigger the sending of +the user's preferences back to PebbleKit JS. + +```html + +``` + + +## Submitting Config Data + +Once the 'Save' button is pressed, the values of all the input elements should +be encoded and included in the return URL as shown below: + +```html + +``` + +> Note: Remember to use `encodeURIComponent()` and `decodeURIComponent()` to +> ensure the JSON data object is transmitted without error. + + +## Hosting the Config Page + +In order for users to access your configuration page, it needs to be hosted +online somewhere. One potential free service to host your configuration page +is Github Pages: + +[Github Pages](https://pages.github.com/) allow you to host your HTML, CSS and +JavaScript files and directly access them from a special branch within your +Github repo. This also has the added advantage of encouraging the use of +version control. + + +## Relaying Data through PebbleKit JS + +When the user submits the HTML form, the page will close and the result is +passed to the `webviewclosed` event handler in the PebbleKit JS `index.js` file: + +```js +Pebble.addEventListener('webviewclosed', function(e) { + // Decode the user's preferences + var configData = JSON.parse(decodeURIComponent(e.response)); +} +``` + +The data from the config page should be converted to the appropriate keys and +value types expected by the watchapp, and sent via ``AppMessage``: + +```js +// Send to the watchapp via AppMessage +var dict = { + 'BackgroundColor': configData.background_color, + 'ForegroundColor': configData.foreground_color, + 'SecondTick': configData.second_ticks, + 'Animations': configData.animations +}; + +// Send to the watchapp +Pebble.sendAppMessage(dict, function() { + console.log('Config data sent successfully!'); +}, function(e) { + console.log('Error sending config data!'); +}); +``` + + +## Receiving Config Data + +Once the watchapp has called ``app_message_open()`` and registered an +``AppMessageInboxReceived`` handler, that handler will be called once the data +has arrived on the watch. This occurs once the user has pressed the submit +button. + +To obtain the example keys and values shown in this guide, simply look for and +read the keys as ``Tuple`` objects using the ``DictionaryIterator`` provided: + +```c +static void inbox_received_handler(DictionaryIterator *iter, void *context) { + // Read color preferences + Tuple *bg_color_t = dict_find(iter, MESSAGE_KEY_BackgroundColor); + if(bg_color_t) { + GColor bg_color = GColorFromHEX(bg_color_t->value->int32); + } + + Tuple *fg_color_t = dict_find(iter, MESSAGE_KEY_ForegroundColor); + if(fg_color_t) { + GColor fg_color = GColorFromHEX(fg_color_t->value->int32); + } + + // Read boolean preferences + Tuple *second_tick_t = dict_find(iter, MESSAGE_KEY_SecondTick); + if(second_tick_t) { + bool second_ticks = second_tick_t->value->int32 == 1; + } + + Tuple *animations_t = dict_find(iter, MESSAGE_KEY_Animations); + if(animations_t) { + bool animations = animations_t->value->int32 == 1; + } + + // App should now update to take the user's preferences into account + reload_config(); +} +``` + + +Read the {% guide_link communication %} guides for more information about using +the ``AppMessage`` API. + +If you're looking for a simpler option, we recommend using +{% guide_link user-interfaces/app-configuration "Clay for Pebble" %} instead. diff --git a/devsite/source/_guides/user-interfaces/app-configuration.md b/devsite/source/_guides/user-interfaces/app-configuration.md new file mode 100644 index 00000000..0aa570d7 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/app-configuration.md @@ -0,0 +1,348 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Configuration +description: | + How to allow users to customize an app with a configuration page. +guide_group: user-interfaces +order: 0 +platform_choice: true +related_examples: + - title: Clay Example + url: https://github.com/pebble-examples/clay-example +--- + +Many watchfaces and watchapps in the Pebble appstore include the ability to +customize their behavior or appearance through the use of a configuration page. + +[Clay for Pebble](https://github.com/pebble/clay) is the recommended approach +for creating configuration pages, and is what will be covered in this guide. +If you need to host your own configuration pages, please follow our +{% guide_link user-interfaces/app-configuration-static "Manual Setup" %} guide. + +![Clay Sample](/images/guides/user-interfaces/app-configuration/clay-sample.png =200) + +Clay for Pebble dramatically simplifies the process of creating a configuration +page, by allowing developers to define their application settings using a +simple [JSON](https://en.wikipedia.org/wiki/JSON) file. Clay processes the +JSON file and then dynamically generates a configuration page which matches the +existing style of the Pebble mobile application, and it even works without an +Internet connection. + + +## Enabling Configuration + +^LC^ For an app to be configurable, it must include the 'configurable' item in +`package.json`. + +
+{% markdown %} +```js +"capabilities": [ "configurable" ] +``` +{% endmarkdown %} +
+ +^CP^ For an app to be configurable, it must include the 'configurable' item in +'Settings'. + +The presence of this value tells the mobile app to display the gear icon that +is associated with the ability to launch the config page next to the app itself. + +## Installing Clay + +Clay is available as a {% guide_link pebble-packages "Pebble Package" %}, so it +takes minimal effort to install. + +^LC^ Within your project folder, just type: + +
+{% markdown %} +```nc|text +$ pebble package install pebble-clay +``` +{% endmarkdown %} +
+ +^CP^ Go to the 'Dependencies' tab, type 'pebble-clay' into the search box and +press 'Enter' to add the dependency. + + +## Choosing messageKeys + +When passing data between the configuration page and the watch application, we +define `messageKeys` to help us easily identify the different values. + +In this example, we're going to allow users to control the background color, +foreground color, whether the watchface ticks on seconds and whether any +animations are displayed. + +^LC^ We define `messageKeys` in the `package.json` file for each configuration +setting in our application: + +
+{% markdown %} +```js +"messageKeys": [ + "BackgroundColor", + "ForegroundColor", + "SecondTick", + "Animations" +] +``` +{% endmarkdown %} +
+ +^CP^ We define `messageKeys` in the 'Settings' tab for each configuration +setting in our application. The 'Message Key Assignment Kind' should be set to +'Automatic Assignment', then just enter each key name: + +
+{% markdown %} +![CloudPebble Settings](/images/guides/user-interfaces/app-configuration/message-keys.png =400) +{% endmarkdown %} +
+ +## Creating the Clay Configuration + +^LC^ The Clay configuration file (`config.js`) should be created in your +`src/pkjs/` folder. It allows the easy definition of each type of HTML form +entity that is required. These types include: + +^CP^ The Clay configuration file (`config.js`) needs to be added to your project +by adding a new 'Javascript' source file. It allows the easy definition of +each type of HTML form entity that is required. These types include: + +* [Section](https://github.com/pebble/clay#section) +* [Heading](https://github.com/pebble/clay#heading) +* [Text](https://github.com/pebble/clay#text) +* [Input](https://github.com/pebble/clay#input) +* [Toggle](https://github.com/pebble/clay#toggle) +* [Select](https://github.com/pebble/clay#select) +* [Color Picker](https://github.com/pebble/clay#color-picker) +* [Radio Group](https://github.com/pebble/clay#radio-group) +* [Checkbox Group](https://github.com/pebble/clay#checkbox-group) +* [Generic Button](https://github.com/pebble/clay#generic-button) +* [Range Slider](https://github.com/pebble/clay#range-slider) +* [Submit Button](https://github.com/pebble/clay#submit) + +In our example configuration page, we will add some introductory text, and group +our fields into two sections. All configuration pages must have a submit button +at the end, which is used to send the JSON data back to the watch. + +![Clay](/images/guides/user-interfaces/app-configuration/clay-actual.png =200) + +Now start populating the configuration file with the sections you require, then +add the required elements to each section. Be sure to assign the correct +`messageKey` to each field. + +```js +module.exports = [ + { + "type": "heading", + "defaultValue": "App Configuration" + }, + { + "type": "text", + "defaultValue": "Here is some introductory text." + }, + { + "type": "section", + "items": [ + { + "type": "heading", + "defaultValue": "Colors" + }, + { + "type": "color", + "messageKey": "BackgroundColor", + "defaultValue": "0x000000", + "label": "Background Color" + }, + { + "type": "color", + "messageKey": "ForegroundColor", + "defaultValue": "0xFFFFFF", + "label": "Foreground Color" + } + ] + }, + { + "type": "section", + "items": [ + { + "type": "heading", + "defaultValue": "More Settings" + }, + { + "type": "toggle", + "messageKey": "SecondTick", + "label": "Enable Seconds", + "defaultValue": false + }, + { + "type": "toggle", + "messageKey": "Animations", + "label": "Enable Animations", + "defaultValue": false + } + ] + }, + { + "type": "submit", + "defaultValue": "Save Settings" + } +]; +``` + +## Initializing Clay + +To initialize Clay, all you need to do is add the following JavaScript into +your `index.js` file. + +```js +// Import the Clay package +var Clay = require('pebble-clay'); +// Load our Clay configuration file +var clayConfig = require('./config'); +// Initialize Clay +var clay = new Clay(clayConfig); +``` + +
+{% markdown %} +> When using the local SDK, it is possible to use a pure JSON +> configuration file (`config.json`). If this is the case, you must not include +> the `module.exports = []` in your configuration file, and you need to +> `var clayConfig = require('./config.json');` +{% endmarkdown %} +
+ +## Receiving Config Data + +Within our watchapp we need to open a connection with ``AppMessage`` to begin +listening for data from Clay, and also provide a handler to process the data +once it has been received. + +```c +void prv_init(void) { + // ... + + // Open AppMessage connection + app_message_register_inbox_received(prv_inbox_received_handler); + app_message_open(128, 128); + + // ... +} +``` + +Once triggered, our handler will receive a ``DictionaryIterator`` containing +``Tuple`` objects for each `messageKey`. Note that the key names need to be +prefixed with `MESSAGE_KEY_`. + +```c +static void prv_inbox_received_handler(DictionaryIterator *iter, void *context) { + // Read color preferences + Tuple *bg_color_t = dict_find(iter, MESSAGE_KEY_BackgroundColor); + if(bg_color_t) { + GColor bg_color = GColorFromHEX(bg_color_t->value->int32); + } + + Tuple *fg_color_t = dict_find(iter, MESSAGE_KEY_ForegroundColor); + if(fg_color_t) { + GColor fg_color = GColorFromHEX(fg_color_t->value->int32); + } + + // Read boolean preferences + Tuple *second_tick_t = dict_find(iter, MESSAGE_KEY_SecondTick); + if(second_tick_t) { + bool second_ticks = second_tick_t->value->int32 == 1; + } + + Tuple *animations_t = dict_find(iter, MESSAGE_KEY_Animations); + if(animations_t) { + bool animations = animations_t->value->int32 == 1; + } + +} +``` + +## Persisting Settings + +By default, Clay will persist your settings in localStorage within the +mobile application. It is common practice to also save settings within the +persistent storage on the watch. This creates a seemless experience for users +launching your application, as their settings can be applied on startup. This +means there isn't an initial delay while the settings are loaded from the phone. + +You could save each individual value within the persistent storage, or you could +create a struct to hold all of your settings, and save that entire object. This +has the benefit of simplicity, and because writing to persistent storage is +slow, it also provides improved performance. + +```c +// Persistent storage key +#define SETTINGS_KEY 1 + +// Define our settings struct +typedef struct ClaySettings { + GColor BackgroundColor; + GColor ForegroundColor; + bool SecondTick; + bool Animations; +} ClaySettings; + +// An instance of the struct +static ClaySettings settings; + +// AppMessage receive handler +static void prv_inbox_received_handler(DictionaryIterator *iter, void *context) { + // Assign the values to our struct + Tuple *bg_color_t = dict_find(iter, MESSAGE_KEY_BackgroundColor); + if (bg_color_t) { + settings.BackgroundColor = GColorFromHEX(bg_color_t->value->int32); + } + // ... + prv_save_settings(); +} + +// Save the settings to persistent storage +static void prv_save_settings() { + persist_write_data(SETTINGS_KEY, &settings, sizeof(settings)); +} +``` + +You can see a complete implementation of persisting a settings struct in the +[Pebble Clay Example]({{ site.links.examples_org }}/clay-example). + +## What's Next + +If you're thinking that Clay won't be as flexible as hand crafting your own +configuration pages, you're mistaken. + +Developers can extend the functionality of Clay in a number of ways: + +* Define a +[custom function](https://github.com/pebble/clay#custom-function) to enhance the +interactivity of the page. +* [Override events](https://github.com/pebble/clay#handling-the-showconfiguration-and-webviewclosed-events-manually) +and transform the format of the data before it's transferred to the watch. +* Create and share your own +[custom components](https://github.com/pebble/clay#custom-components). + +Why not find out more about [Clay for Pebble](https://github.com/pebble/clay) +and perhaps even +[contribute](https://github.com/pebble/clay/blob/master/CONTRIBUTING.md) to the +project, it's open source! diff --git a/devsite/source/_guides/user-interfaces/app-exit-reason.md b/devsite/source/_guides/user-interfaces/app-exit-reason.md new file mode 100644 index 00000000..4cdfcc93 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/app-exit-reason.md @@ -0,0 +1,57 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: App Exit Reason +description: | + Details on how to use the AppExitReason API +guide_group: user-interfaces +order: 1 +related_docs: + - AppExitReason +--- + +Introduced in SDK v4.0, the ``AppExitReason`` API allows developers to provide a +reason when terminating their application. The system uses these reasons to +determine where the user should be sent when the current application terminates. + +At present there are only 2 ``AppExitReason`` states when exiting an application, +but this may change in future updates. + +### APP_EXIT_NOT_SPECIFIED + +This is the default state and when the current watchapp terminates. The user is +returned to their previous location. If you do not specify an ``AppExitReason``, +this state will be used automatically. + +```c +static void prv_deinit() { + // Optional, default behavior + // App will exit to the previous location in the system + app_exit_reason_set(APP_EXIT_NOT_SPECIFIED); +} +``` + +### APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY + +This state is primarily provided for developers who are creating one click +action applications. When the current watchapp terminates, the user is returned +to the default watchface. + +```c +static void prv_deinit() { + // App will exit to default watchface + app_exit_reason_set(APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY); +} +``` diff --git a/devsite/source/_guides/user-interfaces/appglance-c.md b/devsite/source/_guides/user-interfaces/appglance-c.md new file mode 100644 index 00000000..ddd306cf --- /dev/null +++ b/devsite/source/_guides/user-interfaces/appglance-c.md @@ -0,0 +1,369 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: AppGlance C API +description: | + How to programatically update an app's app glance. +guide_group: user-interfaces +order: 2 +related_docs: + - AppGlanceSlice +related_examples: + - title: Hello World + url: https://github.com/pebble-examples/app-glance-hello-world + - title: Virtual Pet + url: https://github.com/pebble-examples/app-glance-virtual-pet + +--- + +## Overview + +An app's "glance" is the visual representation of a watchapp in the launcher and +provides glanceable information to the user. The ``App Glance`` API, added in SDK +4.0, enables developers to programmatically set the icon and subtitle that +appears alongside their app in the launcher. + +> The ``App Glance`` API is only applicable to watchapps, it is not supported by +watchfaces. + +## Glances and AppGlanceSlices + +An app's glance can change over time, and is defined by zero or more +``AppGlanceSlice`` each consisting of a layout (including a subtitle and icon), +as well as an expiration time. AppGlanceSlices are displayed in the order they +were added, and will persist until their expiration time, or another call to +``app_glance_reload()``. + +> To create an ``AppGlanceSlice`` with no expiration time, use +> ``APP_GLANCE_SLICE_NO_EXPIRATION`` + +Developers can change their watchapp’s glance by calling the +``app_glance_reload()`` method, which first clears any existing app glance +slices, and then loads zero or more ``AppGlanceSlice`` as specified by the +developer. + +The ``app_glance_reload()`` method is invoked with two parameters: a pointer to an +``AppGlanceReloadCallback`` that will be invoked after the existing app glance +slices have been cleared, and a pointer to context data. Developers can add new +``AppGlanceSlice`` to their app's glance in the ``AppGlanceReloadCallback``. + +```c +// ... +// app_glance_reload callback +static void prv_update_app_glance(AppGlanceReloadSession *session, + size_t limit, void *context) { + // Create and add app glance slices... +} + +static void prv_deinit() { + // deinit code + // ... + + // Reload the watchapp's app glance + app_glance_reload(prv_update_app_glance, NULL); +} +``` + +## The app_glance_reload Callback + +The ``app_glance_reload()`` is invoked with 3 parameters, a pointer to an +``AppGlanceReloadSession`` (which is used when invoking +``app_glance_add_slice()``) , the maximum number of slices you are able to add +(as determined by the system at run time), and a pointer to the context data +that was passed into ``app_glance_reload()``. The context data should contain +all the information required to build the ``AppGlanceSlice``, and is typically +cast to a specific type before being used. + +> The `limit` is currently set to 8 app glance slices per watchapp, though there +> is no guarantee that this value will remain static, and developers should +> always ensure they are not adding more slices than the limit. + +![Hello World >{pebble-screenshot,pebble-screenshot--time-black}](/images/guides/appglance-c/hello-world-app-glance.png) + +In this example, we’re passing the string we would like to set as the subtitle, +by using the context parameter. The full code for this example can be found in +the [AppGlance-Hello-World](https://github.com/pebble-examples/app-glance-hello-world) +repository. + +```c +static void prv_update_app_glance(AppGlanceReloadSession *session, + size_t limit, void *context) { + // This should never happen, but developers should always ensure they are + // not adding more slices than are available + if (limit < 1) return; + + // Cast the context object to a string + const char *message = context; + + // Create the AppGlanceSlice + // NOTE: When .icon is not set, the app's default icon is used + const AppGlanceSlice entry = (AppGlanceSlice) { + .layout = { + .icon = APP_GLANCE_SLICE_DEFAULT_ICON, + .subtitle_template_string = message + }, + .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION + }; + + // Add the slice, and check the result + const AppGlanceResult result = app_glance_add_slice(session, entry); + + if (result != APP_GLANCE_RESULT_SUCCESS) { + APP_LOG(APP_LOG_LEVEL_ERROR, "AppGlance Error: %d", result); + } +} +``` + +> **NOTE:** When an ``AppGlanceSlice`` is loaded with the +> ``app_glance_add_slice()`` method, the slice's +> `layout.subtitle_template_string` is copied to the app's glance, meaning the +> string does not need to persist after the call to ``app_glance_add_slice()`` +> is made. + +## Using Custom Icons + +In order to use custom icons within an ``AppGlanceSlice``, you need to use the +new `publishedMedia` entry in the `package.json` file. + +* Create your images as 25px x 25px PNG files. +* Add your images as media resources in the `package.json`. +* Then add the `publishedMedia` declaration. + +You should end up with something like this: + +```js +"resources": { + "media": [ + { + "name": "WEATHER_HOT_ICON_TINY", + "type": "bitmap", + "file": "hot_tiny.png" + } + ], + "publishedMedia": [ + { + "name": "WEATHER_HOT", + "id": 1, + "glance": "WEATHER_HOT_ICON_TINY" + } + ] +} +``` + +Then you can reference the `icon` by `name` in your ``AppGlanceSlice``. You must +use the prefix `PUBLISHED_ID_`. E.g. `PUBLISHED_ID_WEATHER_HOT`. + +## Subtitle Template Strings + +The `subtitle_template_string` field provides developers with a string +formatting language for app glance subtitles. Developers can create a single +app glance slice which updates automatically based upon a timestamp. + +For example, the template can be used to create a countdown until a timestamp +(`time_until`), or the duration since a timestamp (`time_since`). The result +from the timestamp evaluation can be output in various different time-format's, +such as: + +* It's 50 days until New Year +* Your Uber will arrive in 5 minutes +* You are 15515 days old + +### Template Structure + +The template string has the following structure: + +{evaluation(timestamp)|format(parameters)} + +Let's take a look at a simple countdown example: + +`Your Uber will arrive in 1 hr 10 min 4 sec` + +In this example, we need to know the time until our timestamp: +`time_until(1467834606)`, then output the duration using an abbreviated +time-format: `%aT`. + +`Your Uber will arrive in {time_until(1467834606)|format('%aT')}` + +### Format Parameters + +Each format parameter is comprised of an optional predicate, and a time-format, +separated by a colon. The time-format parameter is only output if the predicate +evaluates to true. If a predicate is not supplied, the time-format is output by +default. + +format(predicate:'time-format') + +#### Predicate + +The predicates are composed of a comparator and time value. For example, the +difference between `now` and the timestamp evaluation is: + +* `>1d` Greater than 1 day +* `<12m` Less than 12 months +* `>=6m` Greater than or equal to 6 months +* `<=1d12h` Less than or equal to 1 day, 12 hours. + +The supported time units are: + +* `d` (Day) +* `H` (Hour) +* `M` (Minute) +* `S` (Second) + +#### Time Format + +The time-format is a single quoted string, comprised of a percent sign and an +optional format flag, followed by a time unit. For example: + +`'%aT'` Abbreviated time. e.g. 1 hr 10 min 4 sec + +The optional format flags are: + +* `a` Adds abbreviated units (translated and with proper pluralization) (overrides 'u' flag) +* `u` Adds units (translated and with proper pluralization) (overrides 'a' flag) +* `-` Negates the input for this format specifier +* `0` Pad value to the "expected" number of digits with zeros +* `f` Do not modulus the value + +The following table demonstrates sample output for each time unit, and the +effects of the format flags. + +|Time Unit|No flag|'u' flag|'a' flag|'0' flag|'f' flag| +| --- | --- | --- | --- | --- | --- | +| **y** | <year> | <year> year(s) | <year> yr(s) | <year, pad to 2> | <year, no modulus> | +| output: | 4 | 4 years | 4 yr | 04 | 4 | +| **m** | <month> | <month> month(s) | <month> mo(s) | <month, pad to 2> | <month, no modulus> | +| output: | 8 | 8 months | 8 mo | 08 | 16 | +| **d** | <day> | <day> days | <day> d | <day, pad to 2> | <day, no modulus> | +| output: | 7 | 7 days | 7 d | 07 | 38 | +| **H** | <hour> | <hour> hour(s) | <hour> hr | <hour, pad to 2> | <hour, no modulus> | +| output: | 1 | 1 hour | 1 hr | 01 | 25 | +| **M** | <minute> | <minute> minute(s) | <minute> min | <minute, pad to 2> | <minute, no modulus> | +| output: | 22 | 22 minutes | 22 min | 22 | 82 | +| **S** | <second> | <second> second(s) | <second> sec | <second, pad to 2> | <second, no modulus> | +| output: | 5 | 5 seconds | 5 sec | 05 | 65 | +| **T** | %H:%0M:%0S (if >= 1hr)
%M:%0S (if >= 1m)
%S (otherwise)
| %uH, %uM, and %uS
%uM, and %uS
%uS
| %aH %aM %aS
%aM %aS
%aS
| %0H:%0M:%0S (always) | %fH:%0M:%0S
%M:%0S
%S
| +| output: | 1:53:20
53:20
20
| 1 hour, 53 minutes, and 20 seconds
53 minutes, and 20 seconds
20 seconds
| 1 hr 53 min 20 sec
53 min 20 sec
20 sec
| 01:53:20
00:53:20
00:00:20
| 25:53:20
53:20
20
| +| **R** | %H:%0M (if >= 1hr)
%M (otherwise)
| %uH, and %uM
%uM
| %aH %aM
%aM
| %0H:%0M (always) | %fH:%0M
%M
| +| output: | 23:04
15
| 23 hours, and 4 minutes
15 minutes
| 23 hr 4 min
15 min
| 23:04
00:15
| 47:04
15
| + +> Note: The time units listed above are not all available for use as predicates, +but can be used with format flags. + +#### Advanced Usage + +We've seen how to use a single parameter to generate our output, but for more +advanced cases, we can chain multiple parameters together. This allows for a +single app glance slice to produce different output as each parameter evaluates +successfully, from left to right. + +format(predicate:'time-format', predicate:'time-format', predicate:'time-format') + +For example, we can generate a countdown which displays different output before, +during and after the event: + +* 100 days left +* 10 hr 5 min 20 sec left +* It's New Year! +* 10 days since New Year + +To produce this output we could use the following template: + +`{time_until(1483228800)|format(>=1d:'%ud left',>0S:'%aT left',>-1d:\"It's New Year!\", '%-ud since New Year')}` + + +## Adding Multiple Slices + +An app's glance can change over time, with the slices being displayed in the +order they were added, and removed after the `expiration_time`. In order to add +multiple app glance slices, we simply need to create and add multiple +``AppGlanceSlice`` instances, with increasing expiration times. + +![Virtual Pet >{pebble-screenshot,pebble-screenshot--time-black}](/images/guides/appglance-c/virtual-pet-app-glance.png) + +In the following example, we create a basic virtual pet that needs to be fed (by +opening the app) every 12 hours, or else it runs away. When the app closes, we +update the app glance to display a new message and icon every 3 hours until the +virtual pet runs away. The full code for this example can be found in the +[AppGlance-Virtual-Pet](https://github.com/pebble-examples/app-glance-virtual-pet) +repository. + +```c +// How often pet needs to be fed (12 hrs) +#define PET_FEEDING_FREQUENCY 3600*12 +// Number of states to show in the launcher +#define NUM_STATES 4 + +// Icons associated with each state +const uint32_t icons[NUM_STATES] = { + PUBLISHED_ID_ICON_FROG_HAPPY, + PUBLISHED_ID_ICON_FROG_HUNGRY, + PUBLISHED_ID_ICON_FROG_VERY_HUNGRY, + PUBLISHED_ID_ICON_FROG_MISSING +}; + +// Message associated with each state +const char *messages[NUM_STATES] = { + "Mmm, that was delicious!!", + "I'm getting hungry..", + "I'm so hungry!! Please feed me soon..", + "Your pet ran away :(" +}; + +static void prv_update_app_glance(AppGlanceReloadSession *session, + size_t limit, void *context) { + + // Ensure we have sufficient slices + if (limit < NUM_STATES) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Error: app needs %d slices (%zu available)", + NUM_STATES, limit); + } + + time_t expiration_time = time(NULL); + + // Build and add NUM_STATES slices + for (int i = 0; i < NUM_STATES; i++) { + // Increment the expiration_time of the slice on each pass + expiration_time += PET_FEEDING_FREQUENCY / NUM_STATES; + + // Set it so the last slice never expires + if (i == (NUM_STATES - 1)) expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION; + + // Create the slice + const AppGlanceSlice slice = { + .layout = { + .icon = icons[i], + .subtitle_template_string = messages[i] + }, + .expiration_time = expiration_time + }; + + // add the slice, and check the result + AppGlanceResult result = app_glance_add_slice(session, slice); + if (result != APP_GLANCE_RESULT_SUCCESS) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Error adding AppGlanceSlice: %d", result); + } + } +} + +static void prv_deinit() { + app_glance_reload(prv_update_app_glance, NULL); +} + +void main() { + app_event_loop(); + prv_deinit(); +} +``` diff --git a/devsite/source/_guides/user-interfaces/appglance-pebblekit-js.md b/devsite/source/_guides/user-interfaces/appglance-pebblekit-js.md new file mode 100644 index 00000000..f742c804 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/appglance-pebblekit-js.md @@ -0,0 +1,135 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: AppGlance in PebbleKit JS +description: | + How to update an app's glance using PebbleKit JS. +guide_group: user-interfaces +order: 2 +related_docs: + - AppGlanceSlice +related_examples: + - title: PebbleKit JS Example + url: https://github.com/pebble-examples/app-glance-pebblekit-js-example +--- + +## Overview + +This guide explains how to manage your app's glances via PebbleKit JS. The +``App Glance`` API was added in SDK 4.0 and enables developers to +programmatically set the icon and subtitle that appears alongside their app in +the launcher. + +If you want to learn more about ``App Glance``, please read the +{% guide_link user-interfaces/appglance-c %} guide. + + +#### Creating Slices + +To create a slice, call `Pebble.appGlanceReload()`. The first parameter is an +array of AppGlance slices, followed by a callback for success and one for +failure. + +```javascript + // Construct the app glance slice object + var appGlanceSlices = [{ + "layout": { + "icon": "system://images/HOTEL_RESERVATION", + "subtitleTemplateString": "Nice Slice!" + } + }]; + + function appGlanceSuccess(appGlanceSlices, appGlanceReloadResult) { + console.log('SUCCESS!'); + }; + + function appGlanceFailure(appGlanceSlices, appGlanceReloadResult) { + console.log('FAILURE!'); + }; + + // Trigger a reload of the slices in the app glance + Pebble.appGlanceReload(appGlanceSlices, appGlanceSuccess, appGlanceFailure); +``` + +#### Slice Icons + +There are two types of resources which can be used for AppGlance icons. + +* You can use system images. E.g. `system://images/HOTEL_RESERVATION` +* You can use custom images by utilizing the +{% guide_link tools-and-resources/app-metadata#published-media "Published Media" %} +`name`. E.g. `app://images/*name*` + +#### Subtitle Template Strings + +The `subtitle_template_string` field provides developers with a string +formatting language for app glance subtitles. Read more in the +{% guide_link user-interfaces/appglance-c#subtitle-template-strings "AppGlance C guide" %}. + +#### Expiring Slices + +When you want your slice to expire automatically, just provide an +`expirationTime` in +[ISO date-time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +format and the system will automatically remove it upon expiry. + +```javascript + var appGlanceSlices = [{ + "layout": { + "icon": "system://images/HOTEL_RESERVATION", + "subtitleTemplateString": "Nice Slice!" + }, + "expirationTime": "2016-12-31T23:59:59.000Z" + }]; +``` + +#### Creating Multiple Slices + +Because `appGlanceSlices` is an array, we can pass multiple slices within a +single function call. The system is responsible for displaying the correct +entries based on the `expirationTime` provided in each slice. + +```javascript + var appGlanceSlices = [{ + "layout": { + "icon": "system://images/DINNER_RESERVATION", + "subtitleTemplateString": "Lunchtime!" + }, + "expirationTime": "2017-01-01T12:00:00.000Z" + }, + { + "layout": { + "icon": "system://images/RESULT_MUTE", + "subtitleTemplateString": "Nap Time!" + }, + "expirationTime": "2017-01-01T14:00:00.000Z" + }]; +``` + +#### Updating Slices + +There isn't a concept of updating an AppGlance slice, just call +`Pebble.appGlanceReload()` with the new slices and any existing slices will be +replaced. + + +#### Deleting Slices + +All you need to do is pass an empty slices array and any existing slices will +be removed. + +```javascript +Pebble.appGlanceReload([], appGlanceSuccess, appGlanceFailure); +``` diff --git a/devsite/source/_guides/user-interfaces/appglance-rest.md b/devsite/source/_guides/user-interfaces/appglance-rest.md new file mode 100644 index 00000000..251d63fe --- /dev/null +++ b/devsite/source/_guides/user-interfaces/appglance-rest.md @@ -0,0 +1,189 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: AppGlance REST API +description: | + How to update an app's app glance using the REST API. +guide_group: user-interfaces +order: 2 +related_docs: + - AppGlanceSlice +related_examples: + - title: Node.js Example + url: https://github.com/pebble-examples/app-glance-rest-example +--- + +
+ {% markdown %} + **Important Note** + + This API requires the forthcoming v4.1 of the Pebble mobile application in + order to display App Glances on the connected watch. + {% endmarkdown %} +
+ +## Overview + +This guide explains how to use the AppGlance REST API. The ``App Glance`` API +was added in SDK 4.0 and enables developers to programmatically set the icon and +subtitle that appears alongside their app in the launcher. + +If you want to learn more about ``App Glance``, please read the +{% guide_link user-interfaces/appglance-c %} guide. + + +## The REST API + +The AppGlance REST API shares many similarities with the existing +{% guide_link pebble-timeline/timeline-public "timeline API" %}. +Developers can push slices to the their app's glance using their own backend +servers. Slices are created using HTTPS requests to the Pebble AppGlance REST +API. + + +#### Creating Slices + +To create a slice, send a `PUT` request to the following URL scheme: + +```text +PUT https://timeline-api.getpebble.com/v1/user/glance +``` + +Use the following headers, where `X-User-Token` is the user's +timeline token (read +{% guide_link pebble-timeline/timeline-js#get-a-timeline-token "Get a Timeline Token" %} +to learn how to get a token): + +```text +Content-Type: application/json +X-User-Token: a70b23d3820e9ee640aeb590fdf03a56 +``` + +Include the JSON object as the request body from a file such as `glance.json`. A +sample of an object is shown below: + +```json +{ + "slices": [ + { + "layout": { + "icon": "system://images/GENERIC_CONFIRMATION", + "subtitleTemplateString": "Success!" + } + } + ] +} +``` + +#### Curl Example + +```bash +$ curl -X PUT https://timeline-api.getpebble.com/v1/user/glance \ + --header "Content-Type: application/json" \ + --header "X-User-Token: a70b23d3820e9ee640aeb590fdf03a56" \ + -d @glance.json +OK +``` + +#### Slice Icons + +There are two types of resources which can be used for AppGlance icons. + +* You can use system images. E.g. `system://images/HOTEL_RESERVATION` +* You can use custom images by utilizing the +{% guide_link tools-and-resources/app-metadata#published-media "Published Media" %} +`name`. E.g. `app://images/*name*` + + +#### Subtitle Template Strings + +The `subtitle_template_string` field provides developers with a string +formatting language for app glance subtitles. Read more in the +{% guide_link user-interfaces/appglance-c#subtitle-template-strings "AppGlance C guide" %}. + + +#### Expiring Slices + +When you want your slice to expire automatically, just provide an +`expirationTime` in +[ISO date-time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +format and the system will automatically remove it upon expiry. + +```json +{ + "slices": [ + { + "layout": { + "icon": "system://images/GENERIC_CONFIRMATION", + "subtitleTemplateString": "Success!" + }, + "expirationTime": "2016-12-31T23:59:59.000Z" + } + ] +} +``` + + +#### Creating Multiple Slices + +Because `slices` is an array, you can send multiple slices within a single +request. The system is responsible for displaying the correct entries based on +the `expirationTime` provided in each slice. + +```json +{ + "slices": [ + { + "layout": { + "icon": "system://images/DINNER_RESERVATION", + "subtitleTemplateString": "Lunchtime!" + }, + "expirationTime": "2017-01-01T12:00:00.000Z" + }, + { + "layout": { + "icon": "system://images/RESULT_MUTE", + "subtitleTemplateString": "Nap Time!" + }, + "expirationTime": "2017-01-01T14:00:00.000Z" + } + ] +} +``` + + +#### Updating Slices + +There isn't a concept of updating an AppGlance slice, just send a request to +the REST API with new slices and any existing slices will be replaced. + + +#### Deleting Slices + +All you need to do is send an empty slices array to the REST API and any +existing slices will be removed. + +```json +{ + "slices": [] +} +``` + +### Additional Notes + +We will not display App Glance slices for SDK 3.0 applications under any +circumstances. Your watchapp needs to be compiled with SDK 4.0 in order to +support App Glances. + diff --git a/devsite/source/_guides/user-interfaces/content-size.md b/devsite/source/_guides/user-interfaces/content-size.md new file mode 100644 index 00000000..8853ad60 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/content-size.md @@ -0,0 +1,148 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Content Size +description: | + Details on how to use the ContentSize API to adapt your watchface layout + based on user text size preferences. +guide_group: user-interfaces +order: 6 +related_docs: + - ContentSize + - UnobstructedArea +related_examples: + - title: Simple Example + url: https://github.com/pebble-examples/feature-content-size +--- + +{% alert notice %} +The ContentSize API is currently only available in SDK 4.2-BETA. +{% endalert %} + +The [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API, added in SDK 4.2, allows developers to dynamically +adapt their watchface and watchapp design based upon the system `Text Size` +preference (*Settings > Notifications > Text Size*). + +While this allows developers to create highly accessible designs, it also serves +to provide a mechanism for creating designs which are less focused upon screen +size, and more focused upon content size. + +![ContentSize >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/content-size/anim.gif) + +The `Text Size` setting displays the following options on all platforms: + +* Small +* Medium +* Large + +Whereas, the +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API will return different content sizes based on +the `Text Size` setting, varying by platform. The list of content sizes is: + +* Small +* Medium +* Large +* Extra Large + +An example of the varying content sizes: + +* `Text Size`: `small` on `Basalt` is `ContentSize`: `small` +* `Text Size`: `small` on `Emery` is `ContentSize`: `medium` + +The following table describes the relationship between `Text Size`, `Platform` +and `ContentSize`: + +Platform | Text Size: Small | Text Size: Medium | Text Size: Large +---------|------------------|-------------------|----------------- +Aplite, Basalt, Chalk, Diorite | ContentSize: Small | ContentSize: Medium | ContentSize: Large +Emery | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large + +> *At present the Text Size setting only affects notifications and some system +UI components, but other system UI components will be updated to support +ContentSize in future versions.* + +We highly recommend that developers begin to build and update their applications +with consideration for +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) + to provide the best experience to users. + +## Detecting ContentSize + +In order to detect the current +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) + developers can use the +``preferred_content_size()`` function. + +The [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +will never change during runtime, so it's perfectly +acceptable to check this once during `init()`. + +```c +static PreferredContentSize s_content_size; + +void init() { + s_content_size = preferred_content_size(); + // ... +} +``` + +## Adapting Layouts + +There are a number of different approaches to adapting the screen layout based +upon content size. You could change font sizes, show or hide design elements, or +even present an entirely different UI. + +In the following example, we will change font sizes based on the +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) + + +```c +static TextLayer *s_text_layer; +static PreferredContentSize s_content_size; + +void init() { + s_content_size = preferred_content_size(); + + // ... + switch (s_content_size) { + case PreferredContentSizeMedium: + // Use a medium font + text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); + break; + case PreferredContentSizeLarge: + case PreferredContentSizeExtraLarge: + // Use a large font + text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); + break; + default: + // Use a small font + text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD)); + break; + } + // ... +} +``` + +## Additional Considerations + +When developing an application which dynamically adjusts based on the +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +setting, try to avoid using fixed widths and heights. Calculate +coordinates and dimensions based upon the size of the root layer, +``UnobstructedArea`` and +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) + diff --git a/devsite/source/_guides/user-interfaces/index.md b/devsite/source/_guides/user-interfaces/index.md new file mode 100644 index 00000000..219653b5 --- /dev/null +++ b/devsite/source/_guides/user-interfaces/index.md @@ -0,0 +1,40 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: User Interfaces +description: | + How to build app user interfaces. Includes information on events, + persistent storage, background worker, wakeups and app configuration. +guide_group: user-interfaces +menu: false +permalink: /guides/user-interfaces/ +generate_toc: false +hide_comments: true +--- + +The User Intefaces section of the developer guide contains information on using +other the Pebble SDK elements that contribute to interface with the user in some +way, shape, or form. For example, ``Layer`` objects form the foundation of all +app user interfaces, while a configuration page asks a user for their input in +terms of preferences. + +Graphics-specific UI elements and resources are discussed in the +{% guide_link graphics-and-animations %} and +{% guide_link app-resources %} sections. + + +## Contents + +{% include guides/contents-group.md group=page.group_data %} diff --git a/devsite/source/_guides/user-interfaces/layers.md b/devsite/source/_guides/user-interfaces/layers.md new file mode 100644 index 00000000..bc3d873d --- /dev/null +++ b/devsite/source/_guides/user-interfaces/layers.md @@ -0,0 +1,406 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Layers +description: | + How to use standard Layer components to build an app's UI. +guide_group: user-interfaces +order: 3 +related_docs: + - Layer + - LayerUpdateProc + - Window + - TextLayer + - BitmapLayer + - MenuLayer + - ScrollLayer +--- + +The ``Layer`` and associated subclasses (such as ``TextLayer`` and +``BitmapLayer``) form the foundation of the UI for every Pebble watchapp or +watchface, and are added to a ``Window`` to construct the UI's design. Each +``Layer`` type contains at least three basic elements: + +* Frame - contains the position and dimensions of the ``Layer``, relative to the + parent object. + +* Bounds - contains the drawable bounding box within the frame. This allows only + a portion of the layer to be visible, and is relative to the ``Layer`` frame. + +* Update procedure - the function that performs the drawing whenever the + ``Layer`` is rendered. The subclasses implement a convenience update procedure + with additional data to achieve their specialization. + + +## Layer Heirachy + +Every app must consist of at least one ``Window`` in order to successfully +launch. Mutiple ``Layer`` objects are added as children of the ``Window``, which +itself contains a ``Layer`` known as the 'root layer'. When the ``Window`` is +rendered, each child ``Layer`` is rendered in the order in which they were +added. For example: + +```c +static Window *s_main_window; + +static BitmapLayer *s_background_layer; +static TextLayer *s_time_layer; +``` + +```c +// Get the Window's root layer +Layer *root_layer = window_get_root_layer(s_main_window); + +/* set up BitmapLayer and TextLayer */ + +// Add the background layer first, so that it is drawn behind the time +layer_add_child(root_layer, bitmap_layer_get_layer(s_background_layer)); + +// Add the time layer second +layer_add_child(root_layer, text_layer_get_layer(s_time_layer)); +``` + +Once added to a ``Window``, the ordering of each ``Layer`` cannot be modified, +but one can be placed at the front by removing and re-adding it to the heirachy: + +```c +// Bring a layer to the front +layer_remove_from_parent(s_some_layer); +layer_add_child(root_layer, s_some_layer); +``` + + +## Update Procedures + +For creating custom drawing implementations, the basic ``Layer`` update +procedure can be reassigned to one created by a developer. This takes the form +of a ``LayerUpdateProc``, and provides a [`GContext`](``Graphics Context``) +object which can be used for drawing primitive shapes, paths, text, and images. + +> Note: See {% guide_link graphics-and-animations %} for more information on +> drawing with the graphics context. + +```c +static void layer_update_proc(Layer *layer, GContext *ctx) { + // Custom drawing happens here +} +``` + +This function must then be assigned to the ``Layer`` that will be drawn with it: + +```c +// Set this Layer's update procedure +layer_set_update_proc(s_some_layer, layer_update_proc); +``` + +The update procedure will be called every time the ``Layer`` must be redrawn. +This is typically when any other ``Layer`` requests a redraw, the ``Window`` is +shown/hidden, the heirarchy changes, or a modal (such as a notification) appears. +The ``Layer`` can also be manually marked as 'dirty', and will be redrawn at the +next opportunity (usually immediately): + +```c +// Request a redraw +layer_mark_dirty(s_some_layer); +``` + + +## Layer Subclasses + +For convenience, there are multiple subclasses of ``Layer`` included in the +Pebble SDK to allow developers to easily construct their app's UI. Each should +be created when the ``Window`` is loading (using the `.load` ``WindowHandler``) +and destroyed when it is unloading (using `.the unload` ``WindowHandler``). + +These are briefly outlined below, alongside a simple usage example split into +three code snippets - the element declarations, the setup procedure, and the +teardown procedure. + + +### TextLayer + +The ``TextLayer`` is the most commonly used subclass of ``Layer``, and allows +apps to render text using any available font, with built-in behavior to handle +text color, line wrapping, alignment, etc. + +```c +static TextLayer *s_text_layer; +``` + +```c +// Create a TextLayer +s_text_layer = text_layer_create(bounds); + +// Set some properties +text_layer_set_text_color(s_text_layer, GColorWhite); +text_layer_set_background_color(s_text_layer, GColorBlack); +text_layer_set_overflow_mode(s_text_layer, GTextOverflowModeWordWrap); +text_layer_set_alignment(s_text_layer, GTextAlignmentCenter); + +// Set the text shown +text_layer_set_text(s_text_layer, "Hello, World!"); + +// Add to the Window +layer_add_child(root_layer, text_layer_get_layer(s_text_layer)); +``` + +```c +// Destroy the TextLayer +text_layer_destroy(s_text_layer); +``` + + +### BitmapLayer + +The ``BitmapLayer`` provides an easy way to show images loaded into ``GBitmap`` +objects from an image resource. Images shown using a ``BitmapLayer`` are +automatically centered within the bounds provided to ``bitmap_layer_create()``. +Read {% guide_link app-resources/images %} to learn more about using image +resources in apps. + +> Note: PNG images with transparency should use `bitmap` resource type, and use +> the ``GCompOpSet`` compositing mode when being displayed, as shown below. + +```c +static BitmapLayer *s_bitmap_layer; +static GBitmap *s_bitmap; +``` + +```c +// Load the image +s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE); + +// Create a BitmapLayer +s_bitmap_layer = bitmap_layer_create(bounds); + +// Set the bitmap and compositing mode +bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap); +bitmap_layer_set_compositing_mode(s_bitmap_layer, GCompOpSet); + +// Add to the Window +layer_add_child(root_layer, bitmap_layer_get_layer(s_bitmap_layer)); +``` + +```c +// Destroy the BitmapLayer +bitmap_layer_destroy(s_bitmap_layer); +``` + + +### StatusBarLayer + +If a user needs to see the current time inside an app (instead of exiting to the +watchface), the ``StatusBarLayer`` component can be used to display this +information at the top of the ``Window``. Colors and separator display style can +be customized. + +```c +static StatusBarLayer *s_status_bar; +``` + +```c +// Create the StatusBarLayer +s_status_bar = status_bar_layer_create(); + +// Set properties +status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorBlueMoon); +status_bar_layer_set_separator_mode(s_status_bar, + StatusBarLayerSeparatorModeDotted); + +// Add to Window +layer_add_child(root_layer, status_bar_layer_get_layer(s_status_bar)); +``` + +```c +// Destroy the StatusBarLayer +status_bar_layer_destroy(s_status_bar); +``` + + +### MenuLayer + +The ``MenuLayer`` allows the user to scroll a list of options using the Up and +Down buttons, and select an option to trigger an action using the Select button. +It differs from the other ``Layer`` subclasses in that it makes use of a number +of ``MenuLayerCallbacks`` to allow the developer to fully control how it renders +and behaves. Some minimum example callbacks are shown below: + +```c +static MenuLayer *s_menu_layer; +``` + +```c +static uint16_t get_num_rows_callback(MenuLayer *menu_layer, + uint16_t section_index, void *context) { + const uint16_t num_rows = 5; + return num_rows; +} + +static void draw_row_callback(GContext *ctx, const Layer *cell_layer, + MenuIndex *cell_index, void *context) { + static char s_buff[16]; + snprintf(s_buff, sizeof(s_buff), "Row %d", (int)cell_index->row); + + // Draw this row's index + menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL); +} + +static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, + MenuIndex *cell_index, void *context) { + const int16_t cell_height = 44; + return cell_height; +} + +static void select_callback(struct MenuLayer *menu_layer, + MenuIndex *cell_index, void *context) { + // Do something in response to the button press + +} +``` + +```c +// Create the MenuLayer +s_menu_layer = menu_layer_create(bounds); + +// Let it receive click events +menu_layer_set_click_config_onto_window(s_menu_layer, window); + +// Set the callbacks for behavior and rendering +menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { + .get_num_rows = get_num_rows_callback, + .draw_row = draw_row_callback, + .get_cell_height = get_cell_height_callback, + .select_click = select_callback, +}); + +// Add to the Window +layer_add_child(root_layer, menu_layer_get_layer(s_menu_layer)); +``` + +```c +// Destroy the MenuLayer +menu_layer_destroy(s_menu_layer); +``` + + +### ScrollLayer + +The ``ScrollLayer`` provides an easy way to use the Up and Down buttons to +scroll large content that does not all fit onto the screen at the same time. The +usage of this type differs from the others in that the ``Layer`` objects that +are scrolled are added as children of the ``ScrollLayer``, which is then in turn +added as a child of the ``Window``. + +The ``ScrollLayer`` frame is the size of the 'viewport', while the content size +determines how far the user can scroll in each direction. The example below +shows a ``ScrollLayer`` scrolling some long text, the total size of which is +calculated with ``graphics_text_layout_get_content_size()`` and used as the +``ScrollLayer`` content size. + +> Note: The scrolled ``TextLayer`` frame is relative to that of its parent, the +> ``ScrollLayer``. + +```c +static TextLayer *s_text_layer; +static ScrollLayer *s_scroll_layer; +``` + +```c +GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD); + +// Find the bounds of the scrolling text +GRect shrinking_rect = GRect(0, 0, bounds.size.w, 2000); +char *text = "Example text that is really really really really really \ + really really really really really really long"; +GSize text_size = graphics_text_layout_get_content_size(text, font, + shrinking_rect, GTextOverflowModeWordWrap, GTextAlignmentLeft); +GRect text_bounds = bounds; +text_bounds.size.h = text_size.h; + +// Create the TextLayer +s_text_layer = text_layer_create(text_bounds); +text_layer_set_overflow_mode(s_text_layer, GTextOverflowModeWordWrap); +text_layer_set_font(s_text_layer, font); +text_layer_set_text(s_text_layer, text); + +// Create the ScrollLayer +s_scroll_layer = scroll_layer_create(bounds); + +// Set the scrolling content size +scroll_layer_set_content_size(s_scroll_layer, text_size); + +// Let the ScrollLayer receive click events +scroll_layer_set_click_config_onto_window(s_scroll_layer, window); + +// Add the TextLayer as a child of the ScrollLayer +scroll_layer_add_child(s_scroll_layer, text_layer_get_layer(s_text_layer)); + +// Add the ScrollLayer as a child of the Window +layer_add_child(root_layer, scroll_layer_get_layer(s_scroll_layer)); +``` + +```c +// Destroy the ScrollLayer and TextLayer +scroll_layer_destroy(s_scroll_layer); +text_layer_destroy(s_text_layer); +``` + + +### ActionBarLayer + +The ``ActionBarLayer`` allows apps to use the familiar black right-hand bar, +featuring icons denoting the action that will occur when each button on the +right hand side is pressed. For example, 'previous track', 'more actions', and +'next track' in the built-in Music app. + +For three or fewer actions, the ``ActionBarLayer`` can be more appropriate than +a ``MenuLayer`` for presenting the user with a list of actionable options. Each +action's icon must also be loaded into a ``GBitmap`` object from app resources. +The example below demonstrates show to set up an ``ActionBarLayer`` showing an +up, down, and checkmark icon for each of the buttons. + +```c +static ActionBarLayer *s_action_bar; +static GBitmap *s_up_bitmap, *s_down_bitmap, *s_check_bitmap; +``` + +```c +// Load icon bitmaps +s_up_bitmap = gbitmap_create_with_resource(RESOURCE_ID_UP_ICON); +s_down_bitmap = gbitmap_create_with_resource(RESOURCE_ID_DOWN_ICON); +s_check_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CHECK_ICON); + +// Create ActionBarLayer +s_action_bar = action_bar_layer_create(); +action_bar_layer_set_click_config_provider(s_action_bar, click_config_provider); + +// Set the icons +action_bar_layer_set_icon(s_action_bar, BUTTON_ID_UP, s_up_bitmap); +action_bar_layer_set_icon(s_action_bar, BUTTON_ID_DOWN, s_down_bitmap); +action_bar_layer_set_icon(s_action_bar, BUTTON_ID_SELECT, s_check_bitmap); + +// Add to Window +action_bar_layer_add_to_window(s_action_bar, window); +``` + +```c +// Destroy the ActionBarLayer +action_bar_layer_destroy(s_action_bar); + +// Destroy the icon GBitmaps +gbitmap_destroy(s_up_bitmap); +gbitmap_destroy(s_down_bitmap); +gbitmap_destroy(s_check_bitmap); +``` diff --git a/devsite/source/_guides/user-interfaces/round-app-ui.md b/devsite/source/_guides/user-interfaces/round-app-ui.md new file mode 100644 index 00000000..bf0e568f --- /dev/null +++ b/devsite/source/_guides/user-interfaces/round-app-ui.md @@ -0,0 +1,443 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Round App UI +description: | + Details on how to use the Pebble SDK to create layouts specifically for round + displays. +guide_group: user-interfaces +order: 4 +related_docs: + - Graphics + - LayerUpdateProc +related_examples: + - title: Time Dots + url: https://github.com/pebble-examples/time-dots/ + - title: Text Flow Techniques + url: https://github.com/pebble-examples/text-flow-techniques +platforms: + - chalk +--- + +> This guide is about creating round apps in code. For advice on designing a +> round app, read {% guide_link design-and-interaction/in-the-round %}. + +With the addition of Pebble Time Round (the Chalk platform) to the Pebble +family, developers face a new challenge - circular apps! With this display +shape, traditional layouts will not display properly due to the obscuring of the +corners. Another potential issue is the increased display resolution. Any UI +elements that were not previously centered correctly (or drawn with hardcoded +coordinates) will also display incorrectly. + +However, the Pebble SDK provides additions and functionality to help developers +cope with this way of thinking. In many cases, a round display can be an +aesthetic advantage. An example of this is the traditional circular dial +watchface, which has been emulated on Pebble many times, but also wastes corner +space. With a round display, these watchfaces can look better than ever. + +![time-dots >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/time-dots.png) + + +## Detecting Display Shape + +The first step for any app wishing to correctly support both display shapes is +to use the available compiler directives to conditionally create the UI. This +can be done as shown below: + +```c +#if defined(PBL_RECT) + printf("This code is run on a rectangular display!"); + + /* Rectangular UI code */ +#elif defined(PBL_ROUND) + printf("This code is run on a round display!"); + + /* Round UI code */ +#endif +``` + +Another approach for single value selection is the ``PBL_IF_RECT_ELSE()`` and +``PBL_IF_ROUND_ELSE()`` macros, which accept two parameters for each of the +respective round and rectangular cases. For example, ``PBL_IF_RECT_ELSE()`` will +compile the first parameter on a rectangular display, and the second one +otherwise: + +```c +// Conditionally print out the shape of the display +printf("This is a %s display!", PBL_IF_RECT_ELSE("rectangular", "round")); +``` + + +## Circular Drawing + +In addition to the older ``graphics_draw_circle()`` and +``graphics_fill_circle()`` functions, the Pebble SDK for the chalk platform +contains additional functions to help draw shapes better suited for a round +display. These include: + +* ``graphics_draw_arc()`` - Draws a line arc clockwise between two angles within + a given ``GRect`` area, where 0° is the top of the circle. + +* ``graphics_fill_radial()`` - Fills a circle clockwise between two angles + within a given ``GRect`` area, with adjustable inner inset radius allowing the + creation of 'doughnut-esque' shapes. + +* ``gpoint_from_polar()`` - Returns a ``GPoint`` object describing a point given + by a specified angle within a centered ``GRect``. + +In the Pebble SDK angles between `0` and `360` degrees are specified as values +scaled between `0` and ``TRIG_MAX_ANGLE`` to preserve accuracy and avoid +floating point math. These are most commonly used when dealing with drawing +circles. To help with this conversion, developers can use the +``DEG_TO_TRIGANGLE()`` macro. + +An example function to draw the letter 'C' in a yellow color is shown below for +use in a ``LayerUpdateProc``. + +```c +static void draw_letter_c(GRect bounds, GContext *ctx) { + GRect frame = grect_inset(bounds, GEdgeInsets(30)); + + graphics_context_set_fill_color(ctx, GColorYellow); + graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, 30, + DEG_TO_TRIGANGLE(-225), DEG_TO_TRIGANGLE(45)); +} +``` + +This produces the expected result, drawn with a smooth antialiased filled circle +arc between the specified angles. + +![letter-c >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/letter-c.png) + + +## Adaptive Layouts + +With not only a difference in display shape, but also in resolution, it is very +important that an app's layout not be created using hardcoded coordinates. +Consider the examples below, designed to create a child ``Layer`` to fill the +size of the parent layer. + +```c +// Bad - only works on Aplite and Basalt rectangular displays +Layer *layer = layer_create(GRect(0, 0, 144, 168)); + +// Better - uses the native display size +GRect bounds = layer_get_bounds(parent_layer); +Layer *layer = layer_create(bounds); +``` + +Using this style, the child layer will always fill the parent layer, regardless +of its actual dimensions. + +In a similar vein, when working with the Pebble Time Round display it can be +important that the layout is centered correctly. A set of layout values that are +in the center of the classic 144 x 168 pixel display will not be centered when +displayed on a 180 x 180 display. The undesirable effect of this can be seen in +the example shown below: + +![cut-corners >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/cut-corners.png) + +By using the technique described above, the layout's ``GRect`` objects can +specify their `origin` and `size` as a function of the dimensions of the layer +they are drawn into, solving this problem. + +![centered >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/centered.png) + + +## Text Flow and Pagination + +A chief concern when working with a circular display is the rendering of large +amounts of text. As demonstrated by an animation in +{% guide_link design-and-interaction/in-the-round#pagination %}, continuous +reflowing of text makes it much harder to read. + +A solution to this problem is to render text while flowing within the +constraints of the shape of the display, and to scroll/animate it one page at a +time. There are three approaches to this available to developers, which are +detailed below. For full examples of each, see the +[`text-flow-techniques`](https://github.com/pebble-examples/text-flow-techniques) +example app. + + +### Using TextLayer + +Additions to the ``TextLayer`` API allow text rendered within it to be +automatically flowed according to the curve of the display, and paged correctly +when the layer is moved or animated further. After a ``TextLayer`` is created in +the usual way, text flow can then be enabled: + +```c +// Create TextLayer +TextLayer *s_text_layer = text_layer_create(bounds); + +/* other properties set up */ + +// Add to parent Window +layer_add_child(window_layer, text_layer_get_layer(s_text_layer)); + +// Enable paging and text flow with an inset of 5 pixels +text_layer_enable_screen_text_flow_and_paging(s_text_layer, 5); +``` + +> Note: The ``text_layer_enable_screen_text_flow_and_paging()`` function must be +> called **after** the ``TextLayer`` is added to the view heirachy (i.e.: after +> using ``layer_add_child()``), or else it will have no effect. + +An example of two ``TextLayer`` elements flowing their text within the +constraints of the display shape is shown below: + +![text-flow >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/text-flow.png) + + +### Using ScrollLayer + +The ``ScrollLayer`` UI component also contains round-friendly functionality, +allowing it to scroll its child ``Layer`` elements in pages of the same height +as its frame (usually the size of the parent ``Window``). This allows consuming +long content to be a more consistent experience, whether it is text, images, or +some other kind of information. + +```c +// Enable ScrollLayer paging +scroll_layer_set_paging(s_scroll_layer, true); +``` + +When combined with a ``TextLayer`` as the main child layer, it becomes easy to +display long pieces of textual content on a round display. The ``TextLayer`` can +be set up to handle the reflowing of text to follow the display shape, and the +``ScrollLayer`` handles the paginated scrolling. + +```c +// Add the TextLayer and ScrollLayer to the view heirachy +scroll_layer_add_child(s_scroll_layer, text_layer_get_layer(s_text_layer)); +layer_add_child(window_layer, scroll_layer_get_layer(s_scroll_layer)); + +// Set the ScrollLayer's content size to the total size of the text +scroll_layer_set_content_size(s_scroll_layer, + text_layer_get_content_size(s_text_layer)); + +// Enable TextLayer text flow and paging +const int inset_size = 2; +text_layer_enable_screen_text_flow_and_paging(s_text_layer, inset_size); + +// Enable ScrollLayer paging +scroll_layer_set_paging(s_scroll_layer, true); +``` + + +### Manual Text Drawing + +The drawing of text into a [`Graphics Context`](``Drawing Text``) can also be +performed with awareness of text flow and paging preferences. This can be used +to emulate the behavior of the two previous approaches, but with more +flexibility. This approach involves the use of the ``GTextAttributes`` object, +which is given to the Graphics API to allow it to flow text and paginate when +being animated. + +When initializing the ``Window`` that will do the drawing: + +```c +// Create the attributes object used for text rendering +GTextAttributes *s_attributes = graphics_text_attributes_create(); + +// Enable text flow with an inset of 5 pixels +graphics_text_attributes_enable_screen_text_flow(s_attributes, 5); + +// Enable pagination with a fixed reference point and bounds, used for animating +graphics_text_attributes_enable_paging(s_attributes, bounds.origin, bounds); +``` + +When drawing some text in a ``LayerUpdateProc``: + +```c +static void update_proc(Layer *layer, GContext *ctx) { + GRect bounds = layer_get_bounds(layer); + + // Calculate size of the text to be drawn with current attribute settings + GSize text_size = graphics_text_layout_get_content_size_with_attributes( + s_sample_text, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), bounds, + GTextOverflowModeWordWrap, GTextAlignmentCenter, s_attributes + ); + + // Draw the text in this box with the current attribute settings + graphics_context_set_text_color(ctx, GColorBlack); + graphics_draw_text(ctx, s_sample_text, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), + GRect(bounds.origin.x, bounds.origin.y, text_size.w, text_size.h), + GTextOverflowModeWordWrap, GTextAlignmentCenter, s_attributes + ); +} +``` + +Once this setup is complete, the text will display correctly when moved or +scrolled via a ``PropertyAnimation``, such as one that moves the ``Layer`` that +draws the text upwards, and at the same time extending its height to display +subsequent pages. An example animation is shown below: + +```c +GRect window_bounds = layer_get_bounds(window_get_root_layer(s_main_window)); +const int duration_ms = 1000; + +// Animate the Layer upwards, lengthening it to allow the next page to be drawn +GRect start = layer_get_frame(s_layer); +GRect finish = GRect(start.origin.x, start.origin.y - window_bounds.size.h, + start.size.w, start.size.h * 2); + +// Create and scedule the PropertyAnimation +PropertyAnimation *prop_anim = property_animation_create_layer_frame( + s_layer, &start, &finish); +Animation *animation = property_animation_get_animation(prop_anim); +animation_set_duration(animation, duration_ms); +animation_schedule(animation); +``` + + +## Working With a Circular Framebuffer + +The traditional rectangular Pebble app framebuffer is a single continuous memory +segment that developers could access with ``gbitmap_get_data()``. With a round +display, Pebble saves memory by clipping sections of each line of difference +between the display area and the rectangle it occupies. The resulting masking +pattern looks like this: + +![mask](/images/guides/pebble-apps/display-animations/mask.png) + +> Download this mask by saving the PNG image above, or get it as a +> [Photoshop PSD layer](/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd). + +This has an important implication - the memory segment of the framebuffer can no +longer be accessed using classic `y * row_width + x` formulae. Instead, +developers should use the ``gbitmap_get_data_row_info()`` API. When used with a +given y coordinate, this will return a ``GBitmapDataRowInfo`` object containing +a pointer to the row's data, as well as values for the minumum and maximum +visible values of x coordinate on that row. For example: + +```c +static void round_update_proc(Layer *layer, GContext *ctx) { + // Get framebuffer + GBitmap *fb = graphics_capture_frame_buffer(ctx); + GRect bounds = layer_get_bounds(layer); + + // Write a value to all visible pixels + for(int y = 0; y < bounds.size.h; y++) { + // Get the min and max x values for this row + GBitmapDataRowInfo info = gbitmap_get_data_row_info(fb, y); + + // Iterate over visible pixels in that row + for(int x = info.min_x; x < info.max_x; x++) { + // Set the pixel to black + memset(&info.data[x], GColorBlack.argb, 1); + } + } + + // Release framebuffer + graphics_release_frame_buffer(ctx, fb); +} +``` + + +## Displaying More Content + +When more content is available than fits on the screen at any one time, the user +should be made aware using visual clues. The best way to do this is to use the +``ContentIndicator`` UI component. + +![content-indicator >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/design-and-interaction/content-indicator.png) + +A ``ContentIndicator`` can be obtained in two ways. It can be created from +scratch with ``content_indicator_create()`` and manually managed to determine +when the arrows should be shown, or a built-in instance can be obtained from a +``ScrollLayer``, as shown below: + +```c +// Get the ContentIndicator from the ScrollLayer +s_indicator = scroll_layer_get_content_indicator(s_scroll_layer); +``` + +In order to draw the arrows indicating more information in each direction, the +``ContentIndicator`` must be supplied with two new ``Layer`` elements that will +be used to do the drawing. These should also be added as children to the main +``Window`` root ``Layer`` such that they are visible on top of all other +``Layer`` elements: + +```c +static void window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + /* ... */ + + // Create two Layers to draw the arrows + s_indicator_up_layer = layer_create( + GRect(0, 0, bounds.size.w, STATUS_BAR_LAYER_HEIGHT)); + s_indicator_down_layer = layer_create( + GRect(0, bounds.size.h - STATUS_BAR_LAYER_HEIGHT, + bounds.size.w, STATUS_BAR_LAYER_HEIGHT)); + + /* ... */ + + // Add these Layers as children after all other components to appear below + layer_add_child(window_layer, s_indicator_up_layer); + layer_add_child(window_layer, s_indicator_down_layer); +} +``` + +Once the indicator ``Layer`` elements have been created, each of the up and down +directions for conventional vertical scrolling must be configured with data to +control its behavior. Aspects such as the color of the arrows and background, +whether or not the arrows time out after being brought into view, and the +alignment of the drawn arrow within the ``Layer`` itself are configured with a +`const` ``ContentIndicatorConfig`` object when each direction is being +configured: + +```c +// Configure the properties of each indicator +const ContentIndicatorConfig up_config = (ContentIndicatorConfig) { + .layer = s_indicator_up_layer, + .times_out = false, + .alignment = GAlignCenter, + .colors = { + .foreground = GColorBlack, + .background = GColorWhite + } +}; +content_indicator_configure_direction(s_indicator, ContentIndicatorDirectionUp, + &up_config); + +const ContentIndicatorConfig down_config = (ContentIndicatorConfig) { + .layer = s_indicator_down_layer, + .times_out = false, + .alignment = GAlignCenter, + .colors = { + .foreground = GColorBlack, + .background = GColorWhite + } +}; +content_indicator_configure_direction(s_indicator, ContentIndicatorDirectionDown, + &down_config); +``` + +Unless the ``ContentIndicator`` has been retrieved from another ``Layer`` type +that includes an instance, it should be destroyed along with its parent +``Window``: + +```c +// Destroy a manually created ContentIndicator +content_indicator_destroy(s_indicator); +``` + +For layouts that use the ``StatusBarLayer``, the ``ContentIndicatorDirectionUp`` +`.layer` in the ``ContentIndicatorConfig`` object can be given the status bar's +``Layer`` with ``status_bar_layer_get_layer()``, and the drawing routines for +each will be managed automatically. diff --git a/devsite/source/_guides/user-interfaces/unobstructed-area.md b/devsite/source/_guides/user-interfaces/unobstructed-area.md new file mode 100644 index 00000000..fd691a1e --- /dev/null +++ b/devsite/source/_guides/user-interfaces/unobstructed-area.md @@ -0,0 +1,348 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Unobstructed Area +description: | + Details on how to use the UnobstructedArea API to adapt your watchface layout + when the screen is partially obstructed by a system overlay. +guide_group: user-interfaces +order: 5 +related_docs: + - Graphics + - LayerUpdateProc + - UnobstructedArea +related_examples: + - title: Simple Example + url: https://github.com/pebble-examples/unobstructed-area-example + - title: Watchface Tutorial + url: https://github.com/pebble-examples/watchface-tutorial-unobstructed +--- + +The ``UnobstructedArea`` API, added in SDK 4.0, allows developers to dynamically +adapt their watchface design when an area of the screen is partially obstructed +by a system overlay. Currently, the Timeline Quick View feature is the only +system overlay. + +Developers are not required to adjust their designs to cater for such system +overlays, but by using the ``UnobstructedArea`` API they can detect changes to +the available screen real-estate and then move, scale, or hide their layers to +achieve an optimal layout while the screen is partially obscured. + +![Unobstructed-watchfaces](/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg) +

Sample watchfaces with Timeline Quick View overlay +

+ +![Obstructed-watchfaces](/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg) +

Potential versions of sample watchfaces using the +UnobstructedArea API

+ +### Determining the Unobstructed Bounds + +Prior to SDK 4.0, when displaying layers on screen you would calculate the +size of the display using ``layer_get_bounds()`` and then scale and position +your layers accordingly. Developers can now calculate the size of a layer, +excluding system obstructions, using the new +``layer_get_unobstructed_bounds()``. + +```c +static Layer *s_window_layer; +static TextLayer *s_text_layer; + +static void main_window_load(Window *window) { + s_window_layer = window_get_root_layer(window); + GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer); + s_text_layer = text_layer_create(GRect(0, unobstructed_bounds.size.h / 4, unobstructed_bounds.size.w, 50)); +} +``` + +If you still want a fullscreen entities such as a background image, regardless +of any obstructions, just combine both techniques as follows: + +```c +static Layer *s_window_layer; +static BitmapLayer *s_image_layer; +static TextLayer *s_text_layer; + +static void main_window_load(Window *window) { + s_window_layer = window_get_root_layer(window); + GRect full_bounds = layer_get_bounds(s_window_layer); + GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer); + s_image_layer = bitmap_layer_create(full_bounds); + s_text_layer = text_layer_create(GRect(0, unobstructed_bounds.size.h / 4, unobstructed_bounds.size.w, 50)); +} +``` + +The approach outlined above is perfectly fine to use when your watchface is +initially launched, but you’re also responsible for handling the obstruction +appearing and disappearing while your watchface is running. + +### Rendering with LayerUpdateProc + +If your application controls its own rendering process using a +``LayerUpdateProc`` you can just dynamically adjust your rendering +each time your layer updates. + +In this example, we use ``layer_get_unobstructed_bounds()`` instead of +``layer_get_bounds()``. The graphics are then positioned or scaled based upon +the available screen real-estate, instead of the screen dimensions. + +> You must ensure you fill the entire window, not just the unobstructed +> area, when drawing the screen - failing to do so may cause unexpected +> graphics to be drawn behind the quick view, during animations. + +```c +static void hands_update_proc(Layer *layer, GContext *ctx) { + GRect bounds = layer_get_unobstructed_bounds(layer); + GPoint center = grect_center_point(&bounds); + const int16_t second_hand_length = (bounds.size.w / 2); + time_t now = time(NULL); + struct tm *t = localtime(&now); + int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60; + GPoint second_hand = { + .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x, + .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y, + }; + + // second hand + graphics_context_set_stroke_color(ctx, GColorWhite); + graphics_draw_line(ctx, second_hand, center); + + // minute/hour hand + graphics_context_set_fill_color(ctx, GColorWhite); + graphics_context_set_stroke_color(ctx, GColorBlack); + gpath_rotate_to(s_minute_arrow, TRIG_MAX_ANGLE * t->tm_min / 60); + gpath_draw_filled(ctx, s_minute_arrow); + gpath_draw_outline(ctx, s_minute_arrow); + + gpath_rotate_to(s_hour_arrow, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + + (t->tm_min / 10))) / (12 * 6)); + gpath_draw_filled(ctx, s_hour_arrow); + gpath_draw_outline(ctx, s_hour_arrow); + + // dot in the middle + graphics_context_set_fill_color(ctx, GColorBlack); + graphics_fill_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3, + 3), 0, GCornerNone); +} +``` + +### Using Unobstructed Area Handlers + +If you are not overriding the default rendering of a ``Layer``, you will need to +subscribe to one or more of the ``UnobstructedAreaHandlers`` to adjust the sizes +and positions of layers. + +There are 3 events available using ``UnobstructedAreaHandlers``. +These events will notify you when the unobstructed area is: *about to change*, +*is currently changing*, or *has finished changing*. You can use these handlers +to perform any necessary alterations to your layout. + +`.will_change` - an event to inform you that the unobstructed area size is about +to change. This provides a ``GRect`` which lets you know the size of the screen +after the change has finished. + +`.change` - an event to inform you that the unobstructed area size is currently +changing. This event is called several times during the animation of an +obstruction appearing or disappearing. ``AnimationProgress`` is provided to let +you know the percentage of progress towards completion. + +`.did_change` - an event to inform you that the unobstructed area size has +finished changing. This is useful for deinitializing or destroying anything +created or allocated in the will_change handler. + +These handlers are optional, but at least one must be specified for a valid +subscription. In the following example, we subscribe to two of the three +available handlers. + +> **NOTE**: You must construct the +> ``UnobstructedAreaHandlers`` object *before* passing it to the +> ``unobstructed_area_service_subscribe()`` method. + +```c +UnobstructedAreaHandlers handlers = { + .will_change = prv_unobstructed_will_change, + .did_change = prv_unobstructed_did_change +}; +unobstructed_area_service_subscribe(handlers, NULL); +``` + +#### Hiding Layers + +In this example, we’re going to hide a ``TextLayer`` containing the current +date, while the screen is obstructed. + +Just before the Timeline Quick View appears, we’re going to hide the +``TextLayer`` and we’ll show it again after the Timeline Quick View disappears. + +```c +static Window *s_main_window; +static Layer *s_window_layer; +static TextLayer *s_date_layer; +``` + +Subscribe to the `.did_change` and `.will_change` events: + +```c +static void main_window_load(Window *window) { + // Keep a handle on the root layer + s_window_layer = window_get_root_layer(window); + // Subscribe to the will_change and did_change events + UnobstructedAreaHandlers handlers = { + .will_change = prv_unobstructed_will_change, + .did_change = prv_unobstructed_did_change + }; + unobstructed_area_service_subscribe(handlers, NULL); +} +``` + +The `will_change` event fires before the size of the unobstructed area changes, +so we need to establish whether the screen is already obstructed, or about to +become obstructed. If there isn’t a current obstruction, that means the +obstruction must be about to appear, so we’ll need to hide our data layer. + +```c +static void prv_unobstructed_will_change(GRect final_unobstructed_screen_area, +void *context) { + // Get the full size of the screen + GRect full_bounds = layer_get_bounds(s_window_layer); + if (!grect_equal(&full_bounds, &final_unobstructed_screen_area)) { + // Screen is about to become obstructed, hide the date + layer_set_hidden(text_layer_get_layer(s_date_layer), true); + } +} +``` + +The `did_change` event fires after the unobstructed size changes, so we can +perform the same check to see whether the screen is already obstructed, or +about to become obstructed. If the screen isn’t obstructed when this event +fires, then the obstruction must have just cleared and we’ll need to display +our date layer again. + +```c +static void prv_unobstructed_did_change(void *context) { + // Get the full size of the screen + GRect full_bounds = layer_get_bounds(s_window_layer); + // Get the total available screen real-estate + GRect bounds = layer_get_unobstructed_bounds(s_window_layer); + if (grect_equal(&full_bounds, &bounds)) { + // Screen is no longer obstructed, show the date + layer_set_hidden(text_layer_get_layer(s_date_layer), false); + } +} +``` + +#### Animating Layer Positions + +The `.change` event will fire several times while the unobstructed area is +changing size. This allows us to use this event to make our layers appear to +slide-in or slide-out of their initial positions. + +In this example, we’re going to use percentages to position two text layers +vertically. One layer at the top of the screen and one layer at the bottom. When +the screen is obstructed, these two layers will shift to be closer together. +Because we’re using percentages, it doesn’t matter if the unobstructed area is +increasing or decreasing, our text layers will always be relatively positioned +in the available space. + +```c +static const uint8_t s_offset_top_percent = 33; +static const uint8_t s_offset_bottom_percent = 10; +``` + +A simple helper function to simulate percentage based coordinates: + +```c +uint8_t relative_pixel(int16_t percent, int16_t max) { + return (max * percent) / 100; +} +``` + +Subscribe to the change event: + +```c +static void main_window_load(Window *window) { + UnobstructedAreaHandlers handler = { + .change = prv_unobstructed_change + }; + unobstructed_area_service_subscribe(handler, NULL); +} +``` + +Move the text layer each time the unobstructed area size changes: + +```c +static void prv_unobstructed_change(AnimationProgress progress, void *context) { + // Get the total available screen real-estate + GRect bounds = layer_get_unobstructed_bounds(s_window_layer); + // Get the current position of our top text layer + GRect frame = layer_get_frame(text_layer_get_layer(s_top_text_layer)); + // Shift the Y coordinate + frame.origin.y = relative_pixel(s_offset_top_percent, bounds.size.h); + // Apply the new location + layer_set_frame(text_layer_get_layer(s_top_text_layer), frame); + // Get the current position of our bottom text layer + GRect frame2 = layer_get_frame(text_layer_get_layer(s_top_text_layer)); + // Shift the Y coordinate + frame2.origin.y = relative_pixel(s_offset_bottom_percent, bounds.size.h); + // Apply the new position + layer_set_frame(text_layer_get_layer(s_bottom_text_layer), frame2); +} +``` + +### Toggling Timeline Quick View + +The `pebble` tool which shipped as part of [SDK 4.0](/sdk4), +allows developers to enable and disable Timeline Quick View, which is +incredibly useful for debugging purposes. + +![Unobstructed animation >{pebble-screenshot,pebble-screenshot--time-black}](/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif) + +To enable Timeline Quick View, you can use: + +```nc|text +$ pebble emu-set-timeline-quick-view on +``` + +To disable Timeline Quick View, you can use: + +```nc|text +$ pebble emu-set-timeline-quick-view off +``` + +> [CloudPebble]({{site.links.cloudpebble}}) does not currently support toggling +> Timeline Quick View, but it will be added as part of a future update. + + +### Additional Considerations + +If you're scaling or moving layers based on the unobstructed area, you must +ensure you fill the entire window, not just the unobstructed area. Failing to do +so may cause unexpected graphics to be drawn behind the quick view, during +animations. + +At present, Timeline Quick View is not currently planned for the Chalk platform. + +For design reference, the height of the Timeline Quick View overlay will be +*51px* in total, which includes a 2px border, but this may vary on newer +platforms and and the height should always be calculated at runtime. + +```c +// Calculate the actual height of the Timeline Quick View +s_window_layer = window_get_root_layer(window); +GRect fullscreen = layer_get_bounds(s_window_layer); +GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer); + +int16_t obstruction_height = fullscreen.size.h - unobstructed_bounds.size.h; +``` diff --git a/devsite/source/_includes/blog/buttons.html b/devsite/source/_includes/blog/buttons.html new file mode 100644 index 00000000..6ee99c00 --- /dev/null +++ b/devsite/source/_includes/blog/buttons.html @@ -0,0 +1,29 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + + + diff --git a/devsite/source/_includes/blog/index-post.html b/devsite/source/_includes/blog/index-post.html new file mode 100644 index 00000000..1496112b --- /dev/null +++ b/devsite/source/_includes/blog/index-post.html @@ -0,0 +1,37 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +
+
+ {% if include.post.image.size > 0 %} + + {% else %} + {% author_photo include.post.author 80 %} + {% endif %} +

{{ include.post.date | date: "%b %d, %Y" }}

+
+
+

{{ include.post.title }}

+

+ {% author_link include.post.author %} · + {% for tag in include.post.tags %} + {{ tag }}{% if forloop.last %}{% else %}, {% endif %} + {% endfor %} +

+
{{ include.post.excerpt }}
+ +
+
diff --git a/devsite/source/_includes/blog/meta.html b/devsite/source/_includes/blog/meta.html new file mode 100644 index 00000000..b75f20e2 --- /dev/null +++ b/devsite/source/_includes/blog/meta.html @@ -0,0 +1,30 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +
+
    +
  • {{ include.post.date | date_to_long_string }}
  • +
  • {% author_link include.post.author %}
  • + {% if include.post.tags.size > 0 %} +
  • + {% for tag in include.post.tags %} + {{ tag }}{% if forloop.last %}{% else %}, {% endif %} + {% endfor %} +
  • +
  • Comments
  • + {% endif %} +
+
\ No newline at end of file diff --git a/devsite/source/_includes/blog/newsletter.html b/devsite/source/_includes/blog/newsletter.html new file mode 100644 index 00000000..f86b555f --- /dev/null +++ b/devsite/source/_includes/blog/newsletter.html @@ -0,0 +1,32 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +

Subscribe to the Pebble Developers Newsletter

+
+
+ + +
+
+ + +
+
+ +
+ +
+ diff --git a/devsite/source/_includes/community/events/calendar.html b/devsite/source/_includes/community/events/calendar.html new file mode 100644 index 00000000..ae6042c3 --- /dev/null +++ b/devsite/source/_includes/community/events/calendar.html @@ -0,0 +1,40 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +
+ + + + + + + + + + + + + + + + + + + + +
diff --git a/devsite/source/_includes/community/events/form.html b/devsite/source/_includes/community/events/form.html new file mode 100644 index 00000000..00f2e2e0 --- /dev/null +++ b/devsite/source/_includes/community/events/form.html @@ -0,0 +1,64 @@ +{% comment %} +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +
+
+

Add an Event

+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+
+ +

+
+
+
+
diff --git a/devsite/source/_includes/community/events/retreat-2014.svg b/devsite/source/_includes/community/events/retreat-2014.svg new file mode 100644 index 00000000..c56de03c --- /dev/null +++ b/devsite/source/_includes/community/events/retreat-2014.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/docs/c/define.html b/devsite/source/_includes/docs/c/define.html new file mode 100644 index 00000000..ea3973ad --- /dev/null +++ b/devsite/source/_includes/docs/c/define.html @@ -0,0 +1,35 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% assign data = include.define.data[include.platform] %} +
+ {% if data.params.size > 0 %} +
+ #define {{ data.type }} {{ include.define.name }} ({% for param in data.params %}{% if param.type %}{{ param.type }}{% if param.name %} {% endif %}{% endif %}{% if param.name %} {{ param.name }}{% endif %}{% unless forloop.last %}, {% endunless %}{% endfor %} 1 %} class="docs__item__param__end"{% endif %}>) +
+
+ {% else %} +
#define {{ include.define.name }} {{ data.initializer }}
+ {% endif %} +
+ {% include docs/c/elements/summary.html item=data %} + {% include docs/c/elements/description.html item=data %} + {% include docs/c/elements/note.html data=data %} + {% include docs/c/elements/parameters.html data=data %} + {% include docs/c/elements/returns.html data=data %} + {% include docs/c/elements/see.html data=data %} +
+
diff --git a/devsite/source/_includes/docs/c/elements/description.html b/devsite/source/_includes/docs/c/elements/description.html new file mode 100644 index 00000000..9484e050 --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/description.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.item.description.size > 0 %} +
+ {{ include.item.description }} +
+{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/missing.html b/devsite/source/_includes/docs/c/elements/missing.html new file mode 100644 index 00000000..6214dc7b --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/missing.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
+
+

The {{ include.type }} {{ include.name }} does not exist in {{ include.platform | fake_platform }}.

+
+
diff --git a/devsite/source/_includes/docs/c/elements/note.html b/devsite/source/_includes/docs/c/elements/note.html new file mode 100644 index 00000000..7ee93dbf --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/note.html @@ -0,0 +1,22 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.data.note.size > 0 %} +
+
Note{% if include.data.note.size > 1 %}s{% endif %}
+ {% for note in include.data.note %}{{ note }}{% endfor %} +
+{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/parameters.html b/devsite/source/_includes/docs/c/elements/parameters.html new file mode 100644 index 00000000..d084a576 --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/parameters.html @@ -0,0 +1,25 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.data.parameters.size > 0 %} +

Parameters

+
+ {% for param in include.data.parameters %} +
{{ param.type}} {{ param.name }}
+
{{ param.summary }} + {% endfor %} +
+{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/returns.html b/devsite/source/_includes/docs/c/elements/returns.html new file mode 100644 index 00000000..181e38d8 --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/returns.html @@ -0,0 +1,22 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.data.return.size > 0 %} +

Returns

+ {% for value in include.data.return %} + {{ value }} + {% endfor %} +{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/see.html b/devsite/source/_includes/docs/c/elements/see.html new file mode 100644 index 00000000..a661f1d3 --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/see.html @@ -0,0 +1,20 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.data.see.size > 0 %} +

See Also

+ {% for value in include.data.see %}{{ value }}{% unless forloop.last %}
{% endunless %}{% endfor %} +{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/summary.html b/devsite/source/_includes/docs/c/elements/summary.html new file mode 100644 index 00000000..a235c77c --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/summary.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.item.summary.size > 0 %} +
+ {{ include.item.summary }} +
+{% endif %} diff --git a/devsite/source/_includes/docs/c/elements/tabs.html b/devsite/source/_includes/docs/c/elements/tabs.html new file mode 100644 index 00000000..8d0587f7 --- /dev/null +++ b/devsite/source/_includes/docs/c/elements/tabs.html @@ -0,0 +1,23 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + diff --git a/devsite/source/_includes/docs/c/enum.html b/devsite/source/_includes/docs/c/enum.html new file mode 100644 index 00000000..74f97825 --- /dev/null +++ b/devsite/source/_includes/docs/c/enum.html @@ -0,0 +1,49 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% assign data = include.enum.data[include.platform] %} +
+
enum {{ include.enum.name }}
+
+ {% include docs/c/elements/summary.html item=data %} + {% include docs/c/elements/description.html item=data %} + {% include docs/c/elements/note.html data=data %} + {% if include.enum.children.size > 0 %} +

Enumerators

+ {% assign children = include.enum.children %} +
+ {% for child in children %} + + {% if child.platforms contains include.platform %} +
{{ child.name }}
+
+ {% if child.data[include.platform].note.size > 0 %} +
+
Note
+ {% for note in child.data[include.platform].note %} + {{ note }} + {% endfor %} +
+ {% endif %} + {{ child.data[include.platform].summary }} +
+ {% endif %} + {% endfor %} +
+ {% endif %} + {% include docs/c/elements/see.html data=data %} +
+
diff --git a/devsite/source/_includes/docs/c/function.html b/devsite/source/_includes/docs/c/function.html new file mode 100644 index 00000000..2e5b3ff4 --- /dev/null +++ b/devsite/source/_includes/docs/c/function.html @@ -0,0 +1,31 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% assign data = include.function.data[include.platform] %} +
+
+ {{ data.type }} {{ include.function.name }}({% for param in data.params %}{{ param.type }}{% if param.name %} {{ param.name }}{% endif %}{% unless forloop.last %}, {% endunless %}{% endfor %} 1 %} class="docs__item__param__end"{% endif %}>) +
+
+
+ {% include docs/c/elements/summary.html item=data %} + {% include docs/c/elements/description.html item=data %} + {% include docs/c/elements/note.html data=data %} + {% include docs/c/elements/parameters.html data=data %} + {% include docs/c/elements/returns.html data=data %} + {% include docs/c/elements/see.html data=data %} +
+
diff --git a/devsite/source/_includes/docs/c/preview.html b/devsite/source/_includes/docs/c/preview.html new file mode 100644 index 00000000..bb439983 --- /dev/null +++ b/devsite/source/_includes/docs/c/preview.html @@ -0,0 +1,25 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + +
+

+ This is the documentation for the developer preview of Pebble SDK 4.2. +

+

+ You should not publish apps built with this SDK to the Pebble appstore. +

+
diff --git a/devsite/source/_includes/docs/c/struct.html b/devsite/source/_includes/docs/c/struct.html new file mode 100644 index 00000000..c5acadf4 --- /dev/null +++ b/devsite/source/_includes/docs/c/struct.html @@ -0,0 +1,38 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% assign data = include.struct.data[include.platform] %} +
+
struct {{ include.struct.name }}
+
+ {% include docs/c/elements/summary.html item=data %} + {% include docs/c/elements/description.html item=data %} + {% include docs/c/elements/note.html data=data %} +

Data Fields

+
+ {% for child in include.struct.children %} + {% if child.platforms contains include.platform %} + {% assign child_data=child.data[include.platform] %} +
{{ child_data['type'] }} {{ child.name }}
+
{{ child_data['summary'] }} + {{ child_data['description'] }} + {% include docs/c/elements/note.html data=child_data %} + {% endif %} + {% endfor %} +
+ {% include docs/c/elements/see.html data=data %} +
+
diff --git a/devsite/source/_includes/docs/c/typedef.html b/devsite/source/_includes/docs/c/typedef.html new file mode 100644 index 00000000..c1453265 --- /dev/null +++ b/devsite/source/_includes/docs/c/typedef.html @@ -0,0 +1,31 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% assign data = include.typedef.data[include.platform] %} +
+
+ typedef {{ data.type }} {{ include.typedef.name }}{{ data.argsstring }} +
+
+
+ {% include docs/c/elements/summary.html item=data %} + {% include docs/c/elements/description.html item=data %} + {% include docs/c/elements/note.html data=data %} + {% include docs/c/elements/parameters.html data=data %} + {% include docs/c/elements/returns.html data=data %} + {% include docs/c/elements/see.html data=data %} +
+
diff --git a/devsite/source/_includes/docs/js/desc.html b/devsite/source/_includes/docs/js/desc.html new file mode 100644 index 00000000..3f9b66a9 --- /dev/null +++ b/devsite/source/_includes/docs/js/desc.html @@ -0,0 +1,65 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.desc.type == "root" %} +{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %} + +{% elsif include.desc.type == "paragraph" %} +{% capture description %}{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}{% endcapture %} +

{{ description | strip_newlines }}

+ +{% elsif include.desc.type == "heading" %} +{% capture description %}

{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}

{% endcapture %} +{{ description | strip_newlines }} + +{% elsif include.desc.type == "list" %} +{% capture description %}{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}{% endcapture %} +<{% if include.desc.ordered %}ol{% else %}ul{% endif %} class="desc-list"> +{{ description | strip_newlines }} + + +{% elsif include.desc.type == "listItem" %} +{% capture description %}{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}{% endcapture %} +
  • {{ description | strip_newlines }}
  • + +{% elsif include.desc.type == "strong" %} +{% capture description %}{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}{% endcapture %} +{{ description | strip_newlines }} + +{% elsif include.desc.type == "link" %} +{% capture title %}{% for child in include.desc.children %} +{% include docs/js/desc.html desc=child %} +{% endfor %}{% endcapture %} +{{ title | strip_newlines }} + +{% elsif include.desc.type == "inlineCode" %} +{{ include.desc.value }} + +{% elsif include.desc.type == "text" %} +{{ include.desc.value }} +{% endif %} \ No newline at end of file diff --git a/devsite/source/_includes/docs/js/examples.html b/devsite/source/_includes/docs/js/examples.html new file mode 100644 index 00000000..3f332e28 --- /dev/null +++ b/devsite/source/_includes/docs/js/examples.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +

    {{ include.title }}

    + diff --git a/devsite/source/_includes/docs/js/function.html b/devsite/source/_includes/docs/js/function.html new file mode 100644 index 00000000..be1fc141 --- /dev/null +++ b/devsite/source/_includes/docs/js/function.html @@ -0,0 +1,41 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    + {% unless include.global %} {{ page.js_module.name }}.{% endunless %}{{ include.child.name }}({% for param in include.child.params %}{{ param.name }}{% unless forloop.last %}, {% endunless %}{% endfor %}) +
    +
    + +
    + {% include docs/js/desc.html desc=include.child.description %} + {% if include.child.params.size > 0 %} +

    Parameters

    +
    + {% for param in include.child.params %} +
    {{ param.type.name }} {{ param.name }}
    +
    {% include docs/js/desc.html desc=param.description %} + {% endfor %} +
    + {% endif %} + + {% if include.child.returns.size > 0 %} +

    Returns

    + {% for return in include.child.returns %} + {% include docs/js/desc.html desc=return.description %} + {% endfor %} + {% endif %} + +
    diff --git a/devsite/source/_includes/docs/js/mozilla.html b/devsite/source/_includes/docs/js/mozilla.html new file mode 100644 index 00000000..46209545 --- /dev/null +++ b/devsite/source/_includes/docs/js/mozilla.html @@ -0,0 +1,23 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    +

    + Web API by Mozilla Contributors is licensed under + CC-BY-SA 2.5. +

    diff --git a/devsite/source/_includes/docs/js/warning.html b/devsite/source/_includes/docs/js/warning.html new file mode 100644 index 00000000..d9126d30 --- /dev/null +++ b/devsite/source/_includes/docs/js/warning.html @@ -0,0 +1,24 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + diff --git a/devsite/source/_includes/docs/menu.html b/devsite/source/_includes/docs/menu.html new file mode 100644 index 00000000..44c54720 --- /dev/null +++ b/devsite/source/_includes/docs/menu.html @@ -0,0 +1,47 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
      +{% for group in include.tree %} + {% if page.docs_language == 'pebblekit_ios' %} + {% assign open = true %} + {% elsif include.group.path contains group.name %} + {% assign open = true %} + {% else %} + {% assign open = false %} + {% endif %} + {% if group.children.size > 0 %} +
    • + {{ group.name }} +
        + {% for item in group.children %} +
      • + {{ item.name }} + {% if item.children.size > 0 %} +
          + {% for child in item.children %} + {% capture full_url %}{{ child.url }}index.html{% endcapture %} +
        • {{ child.name }}
        • + {% endfor %} +
        + {% endif %} +
      • + {% endfor %} +
      +
    • + {% endif %} + {% endfor %} +
    diff --git a/devsite/source/_includes/docs/shared/graybox-list.html b/devsite/source/_includes/docs/shared/graybox-list.html new file mode 100644 index 00000000..1edcdd57 --- /dev/null +++ b/devsite/source/_includes/docs/shared/graybox-list.html @@ -0,0 +1,24 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.items.size > 0 %} +

    {{ include.title }}

    + +{% endif %} diff --git a/devsite/source/_includes/docs/shared/select-list.html b/devsite/source/_includes/docs/shared/select-list.html new file mode 100644 index 00000000..d4a8a7bd --- /dev/null +++ b/devsite/source/_includes/docs/shared/select-list.html @@ -0,0 +1,22 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.items.size > 0 %} + +{% for item in include.items %} + +{% endfor %} +{% endif %} diff --git a/devsite/source/_includes/docs/submenu.html b/devsite/source/_includes/docs/submenu.html new file mode 100644 index 00000000..52bfe6d6 --- /dev/null +++ b/devsite/source/_includes/docs/submenu.html @@ -0,0 +1,26 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.items && include.items.size? %} +
      + {% for item in include.items %} +
    • + {{ item.name }} + {% include docs/submenu.html items=item.children %} +
    • + {% endfor %} +
    +{% endif %} \ No newline at end of file diff --git a/devsite/source/_includes/docs/tree.html b/devsite/source/_includes/docs/tree.html new file mode 100644 index 00000000..52c46f08 --- /dev/null +++ b/devsite/source/_includes/docs/tree.html @@ -0,0 +1,25 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
      +{% for item in include.items %} +
    • {{ item.name }} + {% if item.children.size > 0 %} + {% include docs/tree.html items=item.children %} + {% endif %} +
    • +{% endfor %} +
    \ No newline at end of file diff --git a/devsite/source/_includes/guides/contents-group.md b/devsite/source/_includes/guides/contents-group.md new file mode 100644 index 00000000..38ab2406 --- /dev/null +++ b/devsite/source/_includes/guides/contents-group.md @@ -0,0 +1,26 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% for sub_group in include.group.subgroups %} +{% assign sub_grp = sub_group[1] %} +* [**{{ sub_grp.title }}**]({{sub_grp.url}}) - {{ sub_grp.description }} +{% endfor %} +{% if include.group.guides.size > 0 %} +{% assign guides = include.group.guides | sort: 'title' | where:'menu',true %} +{% for guide in guides %} +* [**{{ guide.title }}**]({{guide.url}}) - {{ guide.summary }} +{% endfor %} +{% endif %} diff --git a/devsite/source/_includes/guides/images/pebble-diagram1.svg b/devsite/source/_includes/guides/images/pebble-diagram1.svg new file mode 100644 index 00000000..387b18c8 --- /dev/null +++ b/devsite/source/_includes/guides/images/pebble-diagram1.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/guides/images/pebble-diagram2.svg b/devsite/source/_includes/guides/images/pebble-diagram2.svg new file mode 100644 index 00000000..07382ab2 --- /dev/null +++ b/devsite/source/_includes/guides/images/pebble-diagram2.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/guides/images/pebble-diagram3.svg b/devsite/source/_includes/guides/images/pebble-diagram3.svg new file mode 100644 index 00000000..1231e526 --- /dev/null +++ b/devsite/source/_includes/guides/images/pebble-diagram3.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/guides/images/pebble-diagram4.svg b/devsite/source/_includes/guides/images/pebble-diagram4.svg new file mode 100644 index 00000000..9497aea0 --- /dev/null +++ b/devsite/source/_includes/guides/images/pebble-diagram4.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/guides/images/pebble-diagram5.svg b/devsite/source/_includes/guides/images/pebble-diagram5.svg new file mode 100644 index 00000000..3b5f1d4d --- /dev/null +++ b/devsite/source/_includes/guides/images/pebble-diagram5.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/guides/owm-api-key-notice.html b/devsite/source/_includes/guides/owm-api-key-notice.html new file mode 100644 index 00000000..be6b517f --- /dev/null +++ b/devsite/source/_includes/guides/owm-api-key-notice.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    + API KEY REQUIRED +

    As of October 2015, an API key is required to fetch OpenWeatherMap data. + These can be freely obtained from OpenWeatherMap.org.

    +
    \ No newline at end of file diff --git a/devsite/source/_includes/guides/sidebar_docs.html b/devsite/source/_includes/guides/sidebar_docs.html new file mode 100644 index 00000000..57b817eb --- /dev/null +++ b/devsite/source/_includes/guides/sidebar_docs.html @@ -0,0 +1,23 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% for doc in include.docs %} + {% if doc.menu %} + + {% endif %} +{% endfor %} \ No newline at end of file diff --git a/devsite/source/_includes/guides/timeline-notice.html b/devsite/source/_includes/guides/timeline-notice.html new file mode 100644 index 00000000..73dded61 --- /dev/null +++ b/devsite/source/_includes/guides/timeline-notice.html @@ -0,0 +1,22 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    +

    NOTICE

    +

    + The timeline APIs are currently only available on SDK 3.0 and above. +

    +
    diff --git a/devsite/source/_includes/hardware-platforms.html b/devsite/source/_includes/hardware-platforms.html new file mode 100644 index 00000000..bd5a24ee --- /dev/null +++ b/devsite/source/_includes/hardware-platforms.html @@ -0,0 +1,112 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FeatureClassic,
    Steel
    Time,
    Time Steel
    Time RoundPebble 2Time 2
    PlatformApliteBasaltChalkDioriteEmery
    CPUCortex-M3
    64 MHz
    Cortex-M4
    100 MHz
    Cortex-M7
    144 MHz
    Max. resource size96k256k512k
    Max. app size (code + heap)24k64k128k
    Display shapeRectangleRoundRectangle
    Display resolution144 x 168180 x 180144 x 168200 x 228
    Display PPI175182175202
    Supported colors264264
    Heart Rate MonitorNoYes
    (with smartstrap)
    Yes
    (except SE model)
    Yes
    MicrophoneNoYes
    SensorsAccelerometer, CompassAccelerometerAccelerometer, Compass
    Buttons4
    Charging portPower onlySmart accessory portSmart accessory port (data only)Smart accessory port
    diff --git a/devsite/source/_includes/mainmenu.html b/devsite/source/_includes/mainmenu.html new file mode 100644 index 00000000..5fafbe32 --- /dev/null +++ b/devsite/source/_includes/mainmenu.html @@ -0,0 +1,23 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + + + + + + + diff --git a/devsite/source/_includes/platform-choice.html b/devsite/source/_includes/platform-choice.html new file mode 100644 index 00000000..6a0b0be1 --- /dev/null +++ b/devsite/source/_includes/platform-choice.html @@ -0,0 +1,46 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    +

    + This page contains some instructions that are different if you're using + CloudPebble or if you're using the SDK locally on your computer. +

    +

    + Select whether you're using CloudPebble or the SDK below to show the + relevant instructions! +

    + +
    +
    +

    + + Showing instructions for CloudPebble. Not using CloudPebble? +

    +

    + + Showing instructions for the SDK. Using CloudPebble? +

    +
    diff --git a/devsite/source/_includes/retreat-2015.svg b/devsite/source/_includes/retreat-2015.svg new file mode 100644 index 00000000..25b3e86d --- /dev/null +++ b/devsite/source/_includes/retreat-2015.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/devsite/source/_includes/retreat_2014.svg b/devsite/source/_includes/retreat_2014.svg new file mode 100644 index 00000000..c56de03c --- /dev/null +++ b/devsite/source/_includes/retreat_2014.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/sdk/beta_instructions_no_platform.md b/devsite/source/_includes/sdk/beta_instructions_no_platform.md new file mode 100644 index 00000000..f3f0de90 --- /dev/null +++ b/devsite/source/_includes/sdk/beta_instructions_no_platform.md @@ -0,0 +1,67 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +### Windows + +Installing the Pebble SDK directly on Windows is not supported at this time. We +recommend you use [CloudPebble]({{ site.links.cloudpebble }}) instead. + +Alternatively, you can run a Linux virtual machine: + +1. Install a virtual machine manager such as + [VirtualBox](http://www.virtualbox.org/) (free) or + [VMWare Workstation](http://www.vmware.com/products/workstation/). +2. Install [Ubuntu Linux](http://www.ubuntu.com/) in a new virtual machine. +3. Follow the [manual installation instructions](/sdk/install/linux/), but skip + "Download and install the Pebble ARM toolchain", as the toolchain is + included. + + +### Mac OS X + +If you previously used Homebrew to install the Pebble SDK, run: + +```bash +$ brew update && brew upgrade --devel pebble-sdk +``` + +If you've never used Homebrew to install the Pebble SDK, run: + +```bash +$ brew update && brew install --devel pebble/pebble-sdk/pebble-sdk +``` + +If you would prefer to not use Homebrew and would like to manually install the +Pebble SDK: + +1. Download the + [SDK package](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2). + +2. Follow the [manual installation instructions](/sdk/install/), but skip + "Download and install the Pebble ARM toolchain", as the toolchain is + included. + + +### Linux + +1. Download the relevant package: + [Linux (32-bit)](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) | + [Linux (64-bit)](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2) + +2. Install the SDK by following the + [manual installation instructions](/sdk/install/linux/), but skip + "Download and install the Pebble ARM toolchain", as the toolchain is + included. diff --git a/devsite/source/_includes/sdk/homebrew-legacy.html b/devsite/source/_includes/sdk/homebrew-legacy.html new file mode 100644 index 00000000..6f65b4fe --- /dev/null +++ b/devsite/source/_includes/sdk/homebrew-legacy.html @@ -0,0 +1,19 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% highlight { "language": "sh", "classes": "text-left" } %} +brew install pebble/pebble-sdk/pebble-sdk-legacy +{% endhighlight %} diff --git a/devsite/source/_includes/sdk/homebrew.html b/devsite/source/_includes/sdk/homebrew.html new file mode 100644 index 00000000..df97634a --- /dev/null +++ b/devsite/source/_includes/sdk/homebrew.html @@ -0,0 +1,19 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% highlight { "language": "sh", "classes": "text-center" } %} +brew install pebble/pebble-sdk/pebble-sdk +{% endhighlight %} diff --git a/devsite/source/_includes/sdk/steps_getting_started.md b/devsite/source/_includes/sdk/steps_getting_started.md new file mode 100644 index 00000000..d0f4f108 --- /dev/null +++ b/devsite/source/_includes/sdk/steps_getting_started.md @@ -0,0 +1,23 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +## Next Steps + +Now that you have the Pebble SDK downloaded and installed on your computer, +it is time to learn how to write your first app! + +You should checkout the [Tutorials](/tutorials/) for a step-by-step look at how +to write a simple C Pebble application. diff --git a/devsite/source/_includes/sdk/steps_help.md b/devsite/source/_includes/sdk/steps_help.md new file mode 100644 index 00000000..4fcf217d --- /dev/null +++ b/devsite/source/_includes/sdk/steps_help.md @@ -0,0 +1,25 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +### Installation Problems? + +If you have any issues with downloading or installing the Pebble SDK, you +should take a look at the +[SDK Help category](https://forums.getpebble.com/categories/watchface-sdk-help) +on our forums. + +Alternatively, you can [send us a message](/contact/) letting us know what +issues you're having and we will try and help you out. diff --git a/devsite/source/_includes/sdk/steps_install_sdk.md b/devsite/source/_includes/sdk/steps_install_sdk.md new file mode 100644 index 00000000..7b13717e --- /dev/null +++ b/devsite/source/_includes/sdk/steps_install_sdk.md @@ -0,0 +1,55 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.mac %} +1. If you have not already, download the [latest version of the SDK]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2). +{% else %} +1. If you have not already, download the latest version of the SDK - + [Linux 32-bit]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) | + [Linux 64-bit]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2). +{% endif %} + +2. Open {% if include.mac %}Terminal{% else %}a terminal{% endif %} window and + create a directory to host all Pebble tools: + + ```bash + mkdir {{ site.data.sdk.path }} + ``` + +3. Change into that directory and extract the Pebble SDK that you just + downloaded, for example: + + ```bash + cd {{ site.data.sdk.path }} + tar -jxf ~/Downloads/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}.tar.bz2 + ``` + + {% unless include.mac %} + > Note: If you are using 32-bit Linux, the path shown above will be + > different as appropriate. + {% endunless %} + + You should now have the directory + `{{ site.data.sdk.path }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}` with the SDK files and directories inside it. + +4. Add the `pebble` tool to your path and reload your shell configuration: + + ```bash + echo 'export PATH=~/pebble-dev/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}/bin:$PATH' >> ~/.bash_profile + . ~/.bash_profile + ``` + +You can now continue on and install the rest of the dependencies. diff --git a/devsite/source/_includes/sdk/steps_python.md b/devsite/source/_includes/sdk/steps_python.md new file mode 100644 index 00000000..ad35c6a8 --- /dev/null +++ b/devsite/source/_includes/sdk/steps_python.md @@ -0,0 +1,47 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +### Download and install Python libraries + +The Pebble SDK depends on Python libraries to convert fonts and images from your +computer into Pebble resources. + +{% if include.mac %} +You need to use the standard Python `easy_install` package manager to install +the alternative `pip` package manager. This is then used to install other Python +dependencies. + +Follow these steps in Terminal: +{% endif %} + +1. Install `pip` and `virtualenv`: + + ```bash + {% if include.mac %}sudo easy_install pip{% else %}sudo apt-get install python-pip python2.7-dev{% endif %} + sudo pip install virtualenv + ``` + +2. Install the Python library dependencies locally: + + ```bash + cd {{ site.data.sdk.path }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %} + virtualenv --no-site-packages .env + source .env/bin/activate + {% if include.mac %}CFLAGS="" {% endif %}pip install -r requirements.txt + deactivate + ``` + +> **Note: virtualenv is not optional.** diff --git a/devsite/source/_includes/search.html b/devsite/source/_includes/search.html new file mode 100644 index 00000000..264b26ec --- /dev/null +++ b/devsite/source/_includes/search.html @@ -0,0 +1,22 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + + + + diff --git a/devsite/source/_includes/tools/color-picker-map.svg b/devsite/source/_includes/tools/color-picker-map.svg new file mode 100644 index 00000000..abef4bd4 --- /dev/null +++ b/devsite/source/_includes/tools/color-picker-map.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/_includes/tutorials/rocky-js-warning.html b/devsite/source/_includes/tutorials/rocky-js-warning.html new file mode 100644 index 00000000..76fe6ec6 --- /dev/null +++ b/devsite/source/_includes/tutorials/rocky-js-warning.html @@ -0,0 +1,24 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +
    + {% markdown %} + **Device Compatibility** + + Rocky.js requires Pebble OS v4.x and therefore will not support Pebble Classic + or Pebble Steel. + {% endmarkdown %} +
    diff --git a/devsite/source/_includes/utils/toc_large.html b/devsite/source/_includes/utils/toc_large.html new file mode 100644 index 00000000..69f15adc --- /dev/null +++ b/devsite/source/_includes/utils/toc_large.html @@ -0,0 +1,24 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.toc %} +

    Overview

    + +{% endif %} \ No newline at end of file diff --git a/devsite/source/_includes/utils/toc_small.html b/devsite/source/_includes/utils/toc_small.html new file mode 100644 index 00000000..9c6b3bef --- /dev/null +++ b/devsite/source/_includes/utils/toc_small.html @@ -0,0 +1,31 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% if include.toc %} +
    +
    +
    +
    + +
    +
    +
    +
    +{% endif %} diff --git a/devsite/source/_js/templates/color-picker-sample-window.tpl b/devsite/source/_js/templates/color-picker-sample-window.tpl new file mode 100644 index 00000000..1b757adb --- /dev/null +++ b/devsite/source/_js/templates/color-picker-sample-window.tpl @@ -0,0 +1,4 @@ +
    Window *window = window_create();
    +window_set_background_color(window, {{ color_name }});
    +window_stack_push(window, true);
    +
    diff --git a/devsite/source/_js/templates/events-info-none.tpl b/devsite/source/_js/templates/events-info-none.tpl new file mode 100644 index 00000000..f8da5f97 --- /dev/null +++ b/devsite/source/_js/templates/events-info-none.tpl @@ -0,0 +1,5 @@ +
    +

    There are currenly no events scheduled for {{ month }}.

    +

    Try changing the month using the calendar controls on the map above.

    +

    If you know about an event happening in {{ month }}, use the submission form to let us know!

    +
    diff --git a/devsite/source/_js/templates/events-info.tpl b/devsite/source/_js/templates/events-info.tpl new file mode 100644 index 00000000..34fe21be --- /dev/null +++ b/devsite/source/_js/templates/events-info.tpl @@ -0,0 +1,7 @@ +
    + +
    diff --git a/devsite/source/_js/templates/quicksearch-no-results.tpl b/devsite/source/_js/templates/quicksearch-no-results.tpl new file mode 100644 index 00000000..186e2705 --- /dev/null +++ b/devsite/source/_js/templates/quicksearch-no-results.tpl @@ -0,0 +1,5 @@ +
    +
    +

    There were no search results.

    +
    +
    diff --git a/devsite/source/_js/templates/quicksearch-result-group.tpl b/devsite/source/_js/templates/quicksearch-result-group.tpl new file mode 100644 index 00000000..41630a57 --- /dev/null +++ b/devsite/source/_js/templates/quicksearch-result-group.tpl @@ -0,0 +1,10 @@ +

    {{ title }}

    +
      + {{#each results}} +
    • + {{ title }} +

      {{{ section }}}

      +

      {{{ summary }}}

      +
    • +{{/each}} +
    diff --git a/devsite/source/_layouts/blog/author_page.html b/devsite/source/_layouts/blog/author_page.html new file mode 100644 index 00000000..f287a1d4 --- /dev/null +++ b/devsite/source/_layouts/blog/author_page.html @@ -0,0 +1,28 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: blog/master +--- +

    + Pebble Developer Blog +

    +

    {{ page.author_name }}

    +
    + {% assign posts = page.posts | sort: 'date' %} + {% for post in posts reversed %} + {% include blog/index-post.html post=post %} + {% endfor %} +
    diff --git a/devsite/source/_layouts/blog/master.html b/devsite/source/_layouts/blog/master.html new file mode 100644 index 00000000..8295de7c --- /dev/null +++ b/devsite/source/_layouts/blog/master.html @@ -0,0 +1,81 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: default +menu_section: blog +--- +
    +
    +
    + {% include utils/toc_small.html toc=page.toc %} +
    + {% include blog/buttons.html %} +
    + {{ content }} +
    +
    + +
    + {% include blog/buttons.html %} + + {% include utils/toc_large.html toc=page.toc %} +

    Categories

    + + +

    Authors

    + +
    +
    +
    +
    + diff --git a/devsite/source/_layouts/blog/post.html b/devsite/source/_layouts/blog/post.html new file mode 100644 index 00000000..e03493d2 --- /dev/null +++ b/devsite/source/_layouts/blog/post.html @@ -0,0 +1,31 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: blog/master +--- +

    {{ page.title }}

    +
    +{% include blog/meta.html post=page %} +{% if page.banner %} +

    +{% endif %} +{{ content }} +
    + +
    + You need JavaScript enabled to read and post comments. +
    +
    diff --git a/devsite/source/_layouts/blog/tag_page.html b/devsite/source/_layouts/blog/tag_page.html new file mode 100644 index 00000000..2fa94dd4 --- /dev/null +++ b/devsite/source/_layouts/blog/tag_page.html @@ -0,0 +1,28 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: blog/master +--- +

    + Pebble Developer Blog +

    +

    {{ page.tag }}

    +
    + {% assign posts = page.posts | sort: 'date' %} + {% for post in posts reversed %} + {% include blog/index-post.html post=post %} + {% endfor %} +
    diff --git a/devsite/source/_layouts/default.html b/devsite/source/_layouts/default.html new file mode 100644 index 00000000..cd1cc929 --- /dev/null +++ b/devsite/source/_layouts/default.html @@ -0,0 +1,24 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_narrow +sidebar_only: true +--- +
    +
    + {% include search.html %} + {{ content }} +
    \ No newline at end of file diff --git a/devsite/source/_layouts/docs.html b/devsite/source/_layouts/docs.html new file mode 100644 index 00000000..f4b8fffd --- /dev/null +++ b/devsite/source/_layouts/docs.html @@ -0,0 +1,85 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sidebar_narrow +menu_section: docs +search_primary: docs +--- +
    +
    + {% case page.docs_language %} + {% when 'c' %} +

    Pebble C API

    + {% when 'c_preview' %} +

    Pebble C API (SDK 4 Preview)

    + {% when 'rockyjs' %} +

    Pebble JavaScript API

    + {% when 'pebblekit_js' %} +

    PebbleKit JS

    + {% when 'pebblekit_ios' %} +

    PebbleKit iOS

    + {% when 'pebblekit_android' %} +

    PebbleKit Android

    + {% else %} +

    Documentation

    + {% endcase %} + +
    + {% case page.docs_language %} + {% when 'c' %} + {% include docs/menu.html tree=site.data.docs_tree.c group=page.group %} + {% when 'c_preview' %} + {% include docs/menu.html tree=site.data.docs_tree.c_preview group=page.group %} + {% when 'rockyjs' %} + {% include docs/menu.html tree=site.data.docs_tree.rockyjs group=page.js_module %} + {% when 'pebblekit_js' %} + {% include docs/menu.html tree=site.data.docs_tree.pebblekit_js group=page.js_module %} + {% when 'pebblekit_ios' %} + {% include docs/menu.html tree=site.data.docs_tree.pebblekit_ios group=page.group %} + {% when 'pebblekit_android' %} + {% include docs/menu.html tree=site.data.docs_tree.pebblekit_android group=page.group %} + {% else %} + + {% endcase %} +
    +
    +
    + {% include search.html %} +
    + {{ content }} +
    +
    diff --git a/devsite/source/_layouts/docs/c.html b/devsite/source/_layouts/docs/c.html new file mode 100644 index 00000000..85051bd1 --- /dev/null +++ b/devsite/source/_layouts/docs/c.html @@ -0,0 +1,243 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: docs +docs_language: c +scripts: + - docs/c +--- +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    + + {% if page.group.basalt_only %} +
    +

    The APIs on this page will only work with SDK 3.x.

    +
    + {% endif %} + + {% if_starts_with page.path 'docs/c/preview/' %} + {% include docs/c/preview.html %} + {% endif_starts_with %} + +

    {{ page.title }}

    + + {{ page.group.summary }} + {{ page.group.description }} + + {% include docs/c/elements/note.html data=page.group.data %} + {% include docs/c/elements/see.html data=page.group.data %} + + {% if page.group.groups.size > 0 %} +

    Modules

    + {% for group in page.group.groups %} +
    +

    {{ group.name }}

    + {% if group.summary.size > 0 %} + {{ group.summary }} + {% else %} +

     

    + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.functions.size > 0 %} +

    Function

    + {% for function in page.group.functions %} +
    + {% if function.uniform %} + {% include docs/c/function.html function=function platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if function.platforms contains platform %} +
    + {% include docs/c/function.html function=function platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=function.name type='function' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.structs.size > 0 %} +

    Data Structure

    + {% for struct in page.group.structs %} +
    + {% if struct.uniform %} + {% include docs/c/struct.html struct=struct platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if struct.platforms contains platform %} +
    + {% include docs/c/struct.html struct=struct platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=struct.name type='struct' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.unions.size > 0 %} +

    Union

    + {% for union in page.group.unions %} +
    + {% if union.uniform %} + {% include docs/c/struct.html struct=union platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if union.platforms contains platform %} +
    + {% include docs/c/struct.html struct=union platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=union.name type='union' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.enums.size > 0 %} +

    Enum

    + {% for enum in page.group.enums %} +
    + {% if enum.uniform %} + {% include docs/c/enum.html enum=enum platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if enum.platforms contains platform %} +
    + {% include docs/c/enum.html enum=enum platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=enum.name type='enum' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.typedefs.size > 0 %} +

    Typedef

    + {% for typedef in page.group.typedefs %} +
    + {% if typedef.uniform %} + {% include docs/c/typedef.html typedef=typedef platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if typedef.platforms contains platform %} +
    + {% include docs/c/typedef.html typedef=typedef platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=typedef.name type='typedef' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + + {% if page.group.defines.size > 0 %} +

    Macro Definition

    + {% for define in page.group.defines %} +
    + {% if define.uniform %} + {% include docs/c/define.html define=define platform='basalt' %} + {% else %} + {% include docs/c/elements/tabs.html %} + {% for platform in page.platforms %} + {% if define.platforms contains platform %} +
    + {% include docs/c/define.html define=define platform=platform %} +
    + {% else %} + {% include docs/c/elements/missing.html platform=platform name=define.name type='define' %} + {% endif %} + {% endfor %} + {% endif %} +
    + {% endfor %} + {% endif %} + +
    +
    + +
    + + diff --git a/devsite/source/_layouts/docs/js.html b/devsite/source/_layouts/docs/js.html new file mode 100644 index 00000000..00573b00 --- /dev/null +++ b/devsite/source/_layouts/docs/js.html @@ -0,0 +1,131 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: docs +--- +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + {% include docs/js/warning.html %} + +

    {{ page.js_module.name }}

    + + {% include docs/js/desc.html desc=page.js_module.description %} + + {% if page.js_module.processed_functions.size > 0 %} +

    Methods

    + {% for child in page.js_module.processed_functions %} +
    +
    + {% include docs/js/function.html child=child %} +
    +
    + {% endfor %} + {% endif %} + + {% if page.js_module.processed_members.size > 0 %} +

    Members

    + {% for child in page.js_module.processed_members %} +
    +
    +
    + {{ page.js_module.name }}.{{ child.name }} +
    +
    +
    + {% include docs/js/desc.html desc=child.description %} +
    +
    +
    + {% endfor %} + {% endif %} + + {% if page.js_module.processed_typedefs.size > 0 %} +

    Typedefs

    + {% for child in page.js_module.processed_typedefs %} +
    +
    + {% if child.type.name == "Object" %} +
    + {{ child.name }} +
    +
    +
    + {% include docs/js/desc.html desc=child.description %} + {% if child.properties.size > 0 %} +

    Properties

    +
    + {% for prop in child.properties %} +
    {{ prop.type.names | first | escape }} {{ prop.name }}
    + {% include docs/js/desc.html desc=prop.description %} + {% endfor %} +
    + {% endif %} +
    + {% elsif child.type.name == "Function" %} + {% include docs/js/function.html child=child %} + {% endif %} +
    +
    + {% endfor %} + {% endif %} + +
    +
    + +
    + diff --git a/devsite/source/_layouts/docs/markdown.html b/devsite/source/_layouts/docs/markdown.html new file mode 100644 index 00000000..63aa83ca --- /dev/null +++ b/devsite/source/_layouts/docs/markdown.html @@ -0,0 +1,21 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: docs +--- +
    + {{ content }} +
    \ No newline at end of file diff --git a/devsite/source/_layouts/docs/pebblekit-android.html b/devsite/source/_layouts/docs/pebblekit-android.html new file mode 100644 index 00000000..8fcdcb88 --- /dev/null +++ b/devsite/source/_layouts/docs/pebblekit-android.html @@ -0,0 +1,24 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: docs +docs_language: pebblekit_android +--- +

    {{ page.title }}

    + +
    +{{ page.contents }} +
    \ No newline at end of file diff --git a/devsite/source/_layouts/docs/pebblekit-ios.html b/devsite/source/_layouts/docs/pebblekit-ios.html new file mode 100644 index 00000000..d8ef1343 --- /dev/null +++ b/devsite/source/_layouts/docs/pebblekit-ios.html @@ -0,0 +1,24 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: docs +docs_language: pebblekit_ios +--- +

    {{ page.title }}

    + +
    +{{ page.contents }} +
    \ No newline at end of file diff --git a/devsite/source/_layouts/guides/default.html b/devsite/source/_layouts/guides/default.html new file mode 100644 index 00000000..52f3ae21 --- /dev/null +++ b/devsite/source/_layouts/guides/default.html @@ -0,0 +1,107 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: guides/master +sidebar_only: false +regenerate: true +--- +{% include utils/toc_small.html toc=page.toc %} +
    +
    +

    {{ page.title }}

    + {% if page.platforms contains 'basalt' and page.platforms contains 'chalk' and page.platforms contains 'diorite' and page.platforms contains 'emery' %} +
    +

    + PLATFORM NOTICE
    + This guide does not apply to apps built to run on the Aplite platform + (Pebble Classic, Pebble Steel). +

    +
    + {% elsif page.platforms contains 'basalt' and page.platforms contains 'chalk' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Basalt or Chalk platforms + (Pebble Time, Pebble Time Steel and Pebble Time Round). +

    +
    + {% elsif page.platforms contains 'basalt' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Basalt platform + (Pebble Time and Pebble Time Steel). +

    +
    + {% elsif page.platforms contains 'chalk' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Chalk platform + (Pebble Time Round watches). +

    +
    + {% endif %} + +
    + {% if page.platform_choice %} + {% include platform-choice.html %} + {% endif %} + {{ content }} +
    + {% unless page.hide_comments %} + +
    + You need JavaScript enabled to read and post comments. +
    + {% endunless %} + {% comment %} +
    +
    + +

    Contributors

    + {% git_contributors page %} +
    + {% endcomment %} +
    + {% if page.toc or page.related_docs or page.related_examples %} + + {% endif %} +
    diff --git a/devsite/source/_layouts/guides/design-wip.html b/devsite/source/_layouts/guides/design-wip.html new file mode 100644 index 00000000..682ed84e --- /dev/null +++ b/devsite/source/_layouts/guides/design-wip.html @@ -0,0 +1,30 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: guides/default +--- +
    +
    Work In Progress
    +

    + These guides are still a work in progress and are subject to changes and + improvements over time. +

    +

    + See the changelog + for update information. Check back soon! +

    +
    +{{ content }} diff --git a/devsite/source/_layouts/guides/index.md b/devsite/source/_layouts/guides/index.md new file mode 100644 index 00000000..26595f23 --- /dev/null +++ b/devsite/source/_layouts/guides/index.md @@ -0,0 +1,5 @@ +--- +layout: guides/default +title: Dummy +description: Details +--- \ No newline at end of file diff --git a/devsite/source/_layouts/guides/master-wide.html b/devsite/source/_layouts/guides/master-wide.html new file mode 100644 index 00000000..51fddf1f --- /dev/null +++ b/devsite/source/_layouts/guides/master-wide.html @@ -0,0 +1,61 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_narrow +menu_section: guides +--- +
    +
    +

    Guides

    +
    +
      +
    • + Table of Contents +
    • + {% for group in site.data.guides %} + {% assign grp_name = group[0] %} + {% assign grp = group[1] %} + {% capture grp_url %}/guides/{{ grp_name }}/{% endcapture %} +
    • + + {% if grp.starred == true %} {% endif %} + {{ grp.title }} + + {% if page.guide_group == grp_name %} +
        + {% if grp.guides.size > 0 %} + {% assign group_docs = grp.guides | sort: grp.sort_by %} + {% for doc in group_docs %} + {% if doc.menu %} +
      • + {{ doc.title }} +
      • + {% endif %} + {% endfor %} + {% endif %} +
      + {% endif %} +
    • + {% endfor %} +
    +
    + +
    + {% include search.html %} +
    + {{content}} +
    +
    diff --git a/devsite/source/_layouts/guides/master.html b/devsite/source/_layouts/guides/master.html new file mode 100644 index 00000000..6434c631 --- /dev/null +++ b/devsite/source/_layouts/guides/master.html @@ -0,0 +1,62 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_narrow +menu_section: guides +sidebar_only: false +--- +
    +
    +

    Guides

    +
    +
      +
    • + Table of Contents +
    • + {% for group in site.data.guides %} + {% assign grp_name = group[0] %} + {% assign grp = group[1] %} + {% capture grp_url %}/guides/{{ grp_name }}/{% endcapture %} +
    • + + {% if grp.starred == true %} {% endif %} + {{ grp.title }} + + {% if page.guide_group == grp_name %} +
        + {% if grp.guides.size > 0 %} + {% assign group_docs = grp.guides | sort: grp.sort_by %} + {% for doc in group_docs %} + {% if doc.menu %} +
      • + {{ doc.title }} +
      • + {% endif %} + {% endfor %} + {% endif %} +
      + {% endif %} +
    • + {% endfor %} +
    +
    + +
    + {% include search.html %} +
    + {{content}} +
    +
    diff --git a/devsite/source/_layouts/guides/wide.html b/devsite/source/_layouts/guides/wide.html new file mode 100644 index 00000000..3e4c6b10 --- /dev/null +++ b/devsite/source/_layouts/guides/wide.html @@ -0,0 +1,98 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: guides/master +--- +{% include utils/toc_small.html toc=page.toc %} +
    +
    +

    {{ page.title }}

    + {% if page.platforms contains 'basalt' and page.platforms contains 'chalk' and page.platforms contains 'diorite' and page.platforms contains 'emery' %} +
    +

    + PLATFORM NOTICE
    + This guide does not apply to apps built to run on the Aplite platform + (Pebble Classic, Pebble Steel). +

    +
    + {% elsif page.platforms contains 'basalt' and page.platforms contains 'chalk' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Basalt or Chalk platforms + (Pebble Time, Pebble Time Steel and Pebble Time Round). +

    +
    + {% elsif page.platforms contains 'basalt' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Basalt platform + (Pebble Time and Pebble Time Steel). +

    +
    + {% elsif page.platforms contains 'chalk' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Chalk platform + (Pebble Time Round watches). +

    +
    + {% endif %} + {% if page.platform_choice %} + {% include platform-choice.html %} + {% endif %} +
    {{ content }}
    + {% unless page.hide_comments %} + +
    + You need JavaScript enabled to read and post comments. +
    + {% endunless %} + {% comment %} +
    +
    + +

    Contributors

    + {% git_contributors page %} +
    + {% endcomment %} +
    + {% if page.toc or page.related_docs or page.related_examples %} + + {% endif %} +
    diff --git a/devsite/source/_layouts/master.html b/devsite/source/_layouts/master.html new file mode 100644 index 00000000..742e7b7b --- /dev/null +++ b/devsite/source/_layouts/master.html @@ -0,0 +1,88 @@ + + + + + + + + {% if page.title %}{{ page.title }} // {{ site.title }}{% else %}{{ site.title }}{% endif %} + + + + + + + + {% for style in page.styles %} + {% asset_css style %} + {% endfor %} + + {% unless site.rack_env == 'development' %} + + + {% endunless %} + + + + + {% if site.rack_env == 'development' %} + {% for script in site.data.js.libs %} + + {% endfor %} + {% else %} + + {% endif %} + + + + + + {% for script in page.scripts %} + {% asset_js script %} + {% endfor %} + + diff --git a/devsite/source/_layouts/more.html b/devsite/source/_layouts/more.html new file mode 100644 index 00000000..b4912726 --- /dev/null +++ b/devsite/source/_layouts/more.html @@ -0,0 +1,42 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_narrow +menu_section: more +sidebar_only: false +--- +
    +
    +

    More

    +
    + +
    + +
    + {% include search.html %} + {{ content }} +
    diff --git a/devsite/source/_layouts/more/markdown.html b/devsite/source/_layouts/more/markdown.html new file mode 100644 index 00000000..306881b3 --- /dev/null +++ b/devsite/source/_layouts/more/markdown.html @@ -0,0 +1,37 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: more +sidebar_only: false +--- +
    + {% include utils/toc_small.html toc=page.toc %} +
    +
    +

    {{ page.title }}

    +
    + {{ content }} +
    +
    + {% if page.toc.size > 0 %} + + {% endif %} +
    +
    diff --git a/devsite/source/_layouts/sdk/changelog.html b/devsite/source/_layouts/sdk/changelog.html new file mode 100644 index 00000000..7fbb63cc --- /dev/null +++ b/devsite/source/_layouts/sdk/changelog.html @@ -0,0 +1,22 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sdk/markdown +--- +

    + Release Date: {{ page.date | date: "%B %-d %Y" }} +

    +{{ content }} diff --git a/devsite/source/_layouts/sdk/markdown.html b/devsite/source/_layouts/sdk/markdown.html new file mode 100644 index 00000000..016705ac --- /dev/null +++ b/devsite/source/_layouts/sdk/markdown.html @@ -0,0 +1,37 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sdk/master +--- +{% include utils/toc_small.html toc=page.toc %} +
    +
    +
    +

    {{ page.title }}

    +
    + {% if page.platform_choice %} + {% include platform-choice.html %} + {% endif %} + {{ content }} +
    +
    + +
    +
    diff --git a/devsite/source/_layouts/sdk/master.html b/devsite/source/_layouts/sdk/master.html new file mode 100644 index 00000000..d1ebda93 --- /dev/null +++ b/devsite/source/_layouts/sdk/master.html @@ -0,0 +1,50 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_narrow +menu_section: sdk +--- +
    +
    +

    SDK

    +
    + +
    + +
    + {% include search.html %} + {{ content }} +
    diff --git a/devsite/source/_layouts/sidebar_narrow.html b/devsite/source/_layouts/sidebar_narrow.html new file mode 100644 index 00000000..cea1e22f --- /dev/null +++ b/devsite/source/_layouts/sidebar_narrow.html @@ -0,0 +1,34 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: master +--- + +
    + {% include search.html %} + {{ content }} +
    diff --git a/devsite/source/_layouts/tutorials/tutorial.html b/devsite/source/_layouts/tutorials/tutorial.html new file mode 100644 index 00000000..eee556d5 --- /dev/null +++ b/devsite/source/_layouts/tutorials/tutorial.html @@ -0,0 +1,56 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: tutorials/master +generate_toc: true +menu_section: tutorials +--- +
    + {% include utils/toc_small.html toc=page.toc %} +
    +
    +

    {{ page.title }}

    + {% if page.platforms contains 'basalt' %} +
    +

    + PLATFORM NOTICE
    + This guide only applies for apps built to run on the Basalt platform + (Pebble Time watches). +

    +
    + {% endif %} +
    + {% if page.platform_choice %} + {% include platform-choice.html %} + {% endif %} + {{ content }} +
    +
    + +
    +
    diff --git a/devsite/source/_layouts/utils/redirect_permanent.html b/devsite/source/_layouts/utils/redirect_permanent.html new file mode 100644 index 00000000..ac318f85 --- /dev/null +++ b/devsite/source/_layouts/utils/redirect_permanent.html @@ -0,0 +1,29 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% comment %} +This layout file is used for generating permanent redirects from old URLs to +new ones. +{% endcomment %} + +{% capture redirect_url %}{{ page.redirect_to | prepend: site.baseurl | prepend: site.url }}{% endcapture %} + +{% if page.redirect_to | downcase | slice: 0, 6 == "http://" or page.redirect_to | downcase | slice: 0, 7 == "https://" %} +{% capture redirect_url %}{{ page.redirect_to }}{% endcapture %} +{% endif %} + + + \ No newline at end of file diff --git a/devsite/source/_layouts/utils/redirect_temporary.html b/devsite/source/_layouts/utils/redirect_temporary.html new file mode 100644 index 00000000..6e5fbea9 --- /dev/null +++ b/devsite/source/_layouts/utils/redirect_temporary.html @@ -0,0 +1,21 @@ +{% comment %} + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{% endcomment %} + +{% comment %} +This layout file is used for generating temporary redirects from old URLs to +new ones. +{% endcomment %} + \ No newline at end of file diff --git a/devsite/source/_posts/2013-07-24-Using-Pebble-System-Fonts.md b/devsite/source/_posts/2013-07-24-Using-Pebble-System-Fonts.md new file mode 100644 index 00000000..9da032fb --- /dev/null +++ b/devsite/source/_posts/2013-07-24-Using-Pebble-System-Fonts.md @@ -0,0 +1,120 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +author: thomas +tags: +- Beautiful Code +--- + +_(2014 09: This article was updated to add links for SDK 2.0 users and add the resource identifier of the fonts as suggested by Timothy Gray in the comments.)_ + +Just like any modern platform, typography is a very important element of Pebble user interface. As Designers and Developers, you have the choice to use one of Pebble system fonts or to embed your own. + +Pebble system fonts were chosen for their readability and excellent quality on Pebble black & white display, it's a good idea to know them well before choosing to embed your own. + + + +## Using Pebble system fonts +To use one of the system font, you need to call `fonts_get_system_font()` and pass it the name of a system font. Those are defined in `pebble_fonts.h` in the `include` directory of your project. + +This function returns a `GFont` object that you can use with `text_layer_set_text()` and `graphics_text_draw()`. + +For example, in our Hello World example we used the _Roboto Condensed 21_ font: + +```c +void handle_init(AppContextRef ctx) { + window_init(&window, "Window Name"); + window_stack_push(&window, true /* Animated */); + + text_layer_init(&hello_layer, GRect(0, 65, 144, 30)); + text_layer_set_text_alignment(&hello_layer, GTextAlignmentCenter); + text_layer_set_text(&hello_layer, "Hello World!"); + text_layer_set_font(&hello_layer, fonts_get_system_font(FONT_KEY_ROBOTO_CONDENSED_21)); + layer_add_child(&window.layer, &hello_layer.layer); +} +``` + +> **Update for SDK 2 users**: +> +> Refer to the [second part of the watchface tutorial](/getting-started/watchface-tutorial/part2/) for updated sample code. + +## An illustrated list of Pebble system fonts + +{% alert notice %} +A more up-to-date list of fonts can be found by reading {% guide_link app-resources/system-fonts %} +in the developer guides. +{% endalert %} + +To facilitate your choice, here is a series of screenshot of Pebble system fonts used to display digits and some text. + +{% comment %}HTML below is acceptable according to Matthew{% endcomment %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Gothic 14
    Gothic 14 Bold
    Gothic 18
    Gothic 18 Bold
    Gothic 24
    Gothic 24 Bold
    Gothic 28
    Gothic 28 Bold
    Bitham 30 Black
    Bitham 42 Bold
    Bitham 42 Light
    Bitham 34 Medium Numbers
    Bitham 42 Medium Numbers
    Roboto 21 Condensed
    Roboto 49 Bold Subset
    Droid 28 Bold
    + +## A word of caution + +To save space on Pebble's memory, some of the system fonts only contain a subset of the default character set: + + * _Roboto 49 Bold Subset_ only contains digits and a colon; + * _Bitham 34/42 Medium Numbers_ only contain digits and a colon; + * _Bitham 18/34 Light Subset_ only contains a few characters and you should not use them (this why they are not included in the screenshots). + +## Browse the fonts on your watch + +To help you decide which font is best, you will probably want to test those fonts directly on a watch. + +We have added a new demo application called [`app_font_browser`]({{site.links.examples_org}}/app-font-browser) to the SDK to help you do that. This application uses a ``MenuLayer`` to navigate through the different options and when you have chosen a font, you can use the _Select_ button to cycle through different messages. + +![app_font_browser](/images/blog/app_font_browser.png) + +You can very easily customize the list of messages for your needs and as an exercise for the reader, you can adapt the app to show custom fonts as well! + +This example is available in the `Examples/watchapps/app_font_browser` of the SDK and on [Github]({{site.links.examples_org}}/app-font-browser). diff --git a/devsite/source/_posts/2013-12-20-Pebble-Javascript-Tips-and-Tricks.md b/devsite/source/_posts/2013-12-20-Pebble-Javascript-Tips-and-Tricks.md new file mode 100644 index 00000000..2761aaa6 --- /dev/null +++ b/devsite/source/_posts/2013-12-20-Pebble-Javascript-Tips-and-Tricks.md @@ -0,0 +1,196 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +author: team pebble +tags: +- Beautiful Code +--- + +PebbleKit JavaScript is one of the most popular additions in Pebble SDK 2.0. We are already seeing lots of amazing apps being built that take advantage of http requests, configuration UI, GPS and local storage on the phone. + +Here are some Tips & Tricks to get the most out of JavaScript in your Pebble apps. + + + +### Lint it! + +Use [JSLint](http://jslint.com/) ([GitHub](https://github.com/douglascrockford/JSLint)) to check that your Javascript is valid. JSLint with default settings has been successful in catching most of the JS errors that have been reported to us. + +If you lint it, there's a much better chance that your app will run smoothly on both the Android and iOS platforms. + +### Make the best use of your data storage + +There are two types of storage that are used with the Pebble, persistent storage and local storage. [Persistent storage](``Storage``) is available through a C API and saves data on Pebble. [Local storage](/guides/communication/using-pebblekit-js) is a JavaScript API and saves data on the phone. These two long-term storage systems are not automatically connected and you will need to use the [AppMessage](``AppMessage``) or [AppSync](``AppSync``) API's to transfer data from one to the other. + +Persistent storage is a key-value storage where the values are limited to 256 bytes and the total space used cannot exceed 4 kb. Persistent storage is not erased when you upgrade the app - but it is erased if you uninstall the app. (Please not that this will be true in Pebble SDK 2.0-BETA4 but was not before). + +Local storage exists on your mobile phone and will persist between app upgrades. It is important that you periodically review the data that your app is storing locally and remove any data that is unneeded or no longer relevant. This can be especially important when your internal app data structures change in an update to your app, as your new code will be expecting data formatted in a deprecated form. + +We strongly recommend that you version your data (adding a key `version` for example) so that a new version of your app can upgrade the format of your data and erase obsolete data.. + +### Check for ACK/NACK responses + +It is imperative that you check for ACK/NACK responses to your last message between your Pebble and phone before initiating another message. Only one message can be sent at any one time reliably and if you try to send additional messages before ACK/NACK responses are received, you will start seeing failed messages and will have to resend them. + +[Pebble-faces](https://github.com/pebble-examples/pebble-faces/blob/master/src/js/pebble-js-app.js) is a good example that includes a `transferImageBytes()` function that sends an image to Pebble, waiting for an ACK after each packet. + +### Familiarize yourself with the Geolocation API + +The W3C has [extensive documentation](http://www.w3.org/TR/geolocation-API/) on how to use the API, as well as best practices. + +Please be aware that support of this API is dependent on the underlying OS and results may vary wildly depending on the type of phone, OS version and environment of the user. There are situations where you may not get a response right away, you may get cached data, or you may never get a callback from your phone. + +We recommend that you: + + * Always pass an error handler to `getCurrentPosition()` and `watchPosition()` - and test the error scenarios! + * Set up a timeout (with `setTimeout()`) when you make a geolocation request. Set it to 5 or 10 seconds and let the user know that something is wrong if the timeout fires before any of the callbacks. + * Take advantage of the different options you can pass to customize the precision and freshness of data. + * Test what happens if you disable location services on Pebble (on iOS, remove or refuse the authorization to get location, or turn off the iPhone's radio and Wifi ; on Android disable all the location services). + * On your watch app, communicate to the user when the system is looking for geolocation information, and gracefully handle the situation when Geolocation information is not available. + +### Double check your strings for malformed data + +Not all valid strings are valid in the context in which they're used. For instance, spaces in an URL are a bad idea and will throw exceptions. (It may also crash the iOS app in some earlier version of the SDK - Dont do it!) JSLint will not catch errors in quoted strings. + +### Use the `ready` event + +The `ready` event is fired when the JS VM is loaded and ready to run. Do not start network request, try to read localstorage, communication with Pebble or perform similar operations before the `ready` event is fired! + +In practice this means that instead of writing: + + console.log("My app has started - Doing stuff..."); + Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!"); + +You should do: + + Pebble.addEventListener("ready", function() { + console.log("My app has started - Doing stuff..."); + Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!"); + }); + +### Do not use recurring timers such as setInterval() + +Your Pebble JavaScript can be killed at any time and you should not rely on it to provide long running services. In the future we may also limit the running time of your app and/or limit the use of intervals. + +Follow these guidelines to get the most out of long running apps: + + - Design your app so that it can be killed at any time. Save any important configuration or state in local storage when you get it - and reload it in the `ready` event. + - If you need some function to run at regular intervals, set a timer on Pebble and send a message to the JavaScript. If the JavaScript is dead, the mobile app will re-initialize it and run the message handler. + - Do not use `setInterval()`. And of course, do not use `setTimeout()` to simulate intervals. + +### Identify Users using the PebbleKit JS account token + +PebbleKit JavaScript provides a unique account token that is associated with the Pebble account of the current user. The account token is a string that is guaranteed to be identical across devices that belong to the user, but is unique to your app and cannot be used to track users across applications. + +If the user is not logged in, the function will return an empty string (""). The account token can be accessed via the `Pebble.getAccountToken()` function call. + +Please note that PebbleKit JavaScript does not have access to the serial number of the Pebble connected. + +### Determine a User's timezone using a simple JS script + +Pebble does not support timezones yet so when Pebble syncs the current time with your phone, it retrieves your local time and stores the local time as a GMT time (ex. 9AM PST -> 9AM GMT), regardless of where you are located in the world. + +If you need to get the current timezone in your app, you can use this simple JavaScript code snippet: + + function sendTimezoneToWatch() { + // Get the number of seconds to add to convert localtime to utc + var offsetMinutes = new Date().getTimezoneOffset() * 60; + // Send it to the watch + Pebble.sendAppMessage({ timezoneOffset: offsetMinutes }) + } + +You will need to assign a value to the `timezoneOffset` appKey in your `appinfo.json`: + + { + ... + "appKeys": { + "timezoneOffset": 42, + }, + ... + } + +And this is how you would use it on Pebble: + + #define KEY_TIMEZONE_OFFSET 42 + + static void appmsg_in_received(DictionaryIterator *received, void *context) { + + Tuple *timezone_offset_tuple = dict_find(received, KEY_TIMEZONE_OFFSET); + + if (timezone_offset_tuple) { + int32_t timezone_offset = timezone_offset_tuple->value->int32; + + // Calculate UTC time + time_t local, utc; + time(&local); + utc = local + timezone_offset; + } + } + + +### Use the source! + +We have a growing number of JS examples in the SDK and in our [`pebble-hacks`](https://github.com/pebble-hacks) GitHub account. Check them out! + +You can also [search for `Pebble.addEventListener` on GitHub](https://github.com/search?l=&q=Pebble.addEventListener+in%3Afile&ref=advsearch&type=Code) to find more open-source examples. + + +## Best Practices for the JS Configuration UI + +The new configuration UI allows you to display a webview on the phone to configure your Pebble watchface or app. Here are some specific recommendations to development of that UI. + +If you have not read it yet, you might want to take a look at our [Using PebbleKit JS for Configuration](/blog/2013/11/21/Using-PebbleKit-JS-Configuration/) blog post. + +### Immediately start loading your Web UI + +To avoid confusion, make sure UI elements are displayed immediately after the user clicks on settings. This means you should not do any http request or complex processing before calling `Pebble.openURL()` when you get the `showConfiguration` event: + + Pebble.addEventListener("showConfiguration", function() { + Pebble.openURL('http://www.example.com/mypebbleurl'); + }); + +If the UI rendering is delayed, the user will get no feedbacks for their action (a press on the configure icon) and this would result in a poor user experience. + +### Apply mobile & web best practices + +There is a lot of information on the web on how to build webpages that display nicely on mobile devices. iOS and Android both use WebKit based browsers which makes things easier, but it is safe to assume that you will never get the exact same look - and you will always need to test on both platforms. Even if you do not own both devices, you can use the built-in browser in the iOS or Android emulators to test your configuration webpage. + +Also consider using framework such as [JQuery Mobile](http://jquerymobile.com/) or [Sencha Touch](http://www.sencha.com/products/touch/) to make your life easier. They provide easy to use components and page templates. If you do not want to write any html, [setpebble.com](http://www.setpebble.com) is another great option that we have [discussed on the blog](/blog/2013/12/02/SetPebble-By-Matt-Clark/) previously. + +### Be prepared for the network to be unreachable during a UI configuration attempt + +UI configuration HTML is not stored locally on the phone and must be accessed from a hosted server. This introduces situations where a user's phone may not be connected to the network successfully, or your server may be unreachable, in which case the UI configuration screen will not load. + +In this scenario, a user will likely cancel the configuration load, which means a blank string is returned to your application `webviewclosed` event. Your watchapp should be designed to handle this such that the watchapp does not erase all settings or crash. + +### Display existing settings + +When displaying your configuration UI, remember to pass existing settings to the webview and use them to pre-populate the UI! + +### Name your configuration submission button "Save" + +If you want to ensure that the settings a user has entered are saved correctly, make sure you call your button _Save_ so users understand that it is required to save data. If your configuration page is long, consider putting the _Save_ button at both the top and bottom of the page. + +If the user clicks the Exit button (X), a blank string will be passed back to your application. You should be prepared to handle a blank string should a user decide to cancel the configuration process. + +### Prompt users to complete app configuration in your Pebble app + +Users may not be aware that they need to complete a configuration for your watchapp. It is best to advise users in the watchface or watchapp to go to their phone and configure your app before proceeding. + +Use persistent storage or local storage to save a token indicating the user has completed configuration, and consider putting a version string in, to allow you to track which version of your configuration webpage the user has accessed (to assist future upgrades of your app). + +## Questions? More tips? + +Have more questions? Or want to share more tips? Please post them in the comments below! diff --git a/devsite/source/_posts/2014-01-12-Using-JSHint-For-Pebble-Development.md b/devsite/source/_posts/2014-01-12-Using-JSHint-For-Pebble-Development.md new file mode 100644 index 00000000..7b93d95d --- /dev/null +++ b/devsite/source/_posts/2014-01-12-Using-JSHint-For-Pebble-Development.md @@ -0,0 +1,129 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Using JSHint for Pebble Development +author: thomas +tags: +- Beautiful Code +--- + +In our post on [JavaScript Tips and Tricks](/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks/) we strongly recommended the use of JSLint or [JSHint](http://www.jshint.com/) to help you detect JavaScript problems at compile time. + +We believe JSHint can really increase the quality of your code and we will probably enforce JSHint correct-ness for all the apps on the appstore very soon. In this post we show you how to setup jshint and integrate it in your Pebble development process. + + + +## Installing JSHint + +JSHint is a modern alternative to JSLint. It is more customizable and you can install it and run it on your machine. This allows you to run JSHint every time you build your Pebble application and fix errors right away. This will save you a lot of install/crash/fix cycles! + +JSHint is a [npm package](https://npmjs.org/package/jshint). NPM is included in all recent NodeJS installations. On Mac and Windows you can [download an installer package](http://nodejs.org/download/). On most Linux distribution, you will install nodejs with the standard package manage (`apt-get install nodejs`). + +To install the JSHint package, run this command: + + sudo npm install -g jshint + +The `-g` flag tells npm to install this package globally, so that the `jshint` command line tool is always available. + +## Configuring jshint + +JSHint offers a large selection of options to customize its output and disable some rules if you absolutely have to. We have prepared a default configuration file that we recommend for Pebble development. The first half should not be changed as they are the rules that we may enforce on the appstore in the future. The second half is our recommended best practices but you can change them to suit your coding habits. + +Save this configuration in a `pebble-jshintrc` file in your Pebble project. + + /* + * Example jshint configuration file for Pebble development. + * + * Check out the full documentation at http://www.jshint.com/docs/options/ + */ + { + // Declares the existence of a global 'Pebble' object + "globals": { "Pebble" : true }, + + // And standard objects (XMLHttpRequest and console) + "browser": true, + "devel": true, + + // Do not mess with standard JavaScript objects (Array, Date, etc) + "freeze": true, + + // Do not use eval! Keep this warning turned on (ie: false) + "evil": false, + + /* + * The options below are more style/developer dependent. + * Customize to your liking. + */ + + // All variables should be in camelcase + "camelcase": true, + + // Do not allow blocks without { } + "curly": true, + + // Prohibits the use of immediate function invocations without wrapping them in parentheses + "immed": true, + + // Enforce indentation + "indent": true, + + // Do not use a variable before it's defined + "latedef": "nofunc", + + // Spot undefined variables + "undef": "true", + + // Spot unused variables + "unused": "true" + } + +## Automatically running jshint + +To automatically run jshint every time you compile your application, you will need to edit your `wscript` build file. This is the modified `wscript` file: + + + # Use the python sh module to run the jshint command + from sh import jshint + + top = '.' + out = 'build' + + def options(ctx): + ctx.load('pebble_sdk') + + def configure(ctx): + ctx.load('pebble_sdk') + # Always pass the '--config pebble-jshintrc' option to jshint + jshint.bake(['--config', 'pebble-jshintrc']) + + def build(ctx): + ctx.load('pebble_sdk') + + # Run jshint before compiling the app. + jshint("src/js/pebble-js-app.js") + + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + target='pebble-app.elf') + + ctx.pbl_bundle(elf='pebble-app.elf', + js=ctx.path.ant_glob('src/js/**/*.js')) + +Now simply run `pebble build` and if there is anything wrong in your JS file, the build will stop and the errors will be displayed on the console. You won't believe how much time you will save! + +## CloudPebble users + +CloudPebble users are encouraged to download and install JSHint on their machine. You can use the [web interface](http://www.jshint.com/) and set most of the recommended options but you will never get 100% correctness because there is no way to declare an extra global variable (the `Pebble` object). + +Good news is [CloudPebble is opensource](https://github.com/CloudPebble/CloudPebble/)! We would be happy to offer a brand new [Pebble Steel](http://www.getpebble.com/steel) to the developer who will submit and get through Katherine's approval a pull request to add jshint support in CloudPebble ;) diff --git a/devsite/source/_posts/2014-05-23-FreeRTOS-Modifications-From-Pebble.md b/devsite/source/_posts/2014-05-23-FreeRTOS-Modifications-From-Pebble.md new file mode 100644 index 00000000..667e112c --- /dev/null +++ b/devsite/source/_posts/2014-05-23-FreeRTOS-Modifications-From-Pebble.md @@ -0,0 +1,104 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: FreeRTOS™ Code Revisions from Pebble +author: brad +tags: +- Down the Rabbit Hole +--- + +Today Pebble is releasing its recent modifications to the FreeRTOS project. +Pebble has made a few minor changes to FreeRTOS to enable its new sandboxed +application environment for PebbleOS 2.0 as well as to make Pebble easier to +monitor and debug. + +The changes are available +[as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.tar.gz) +. + +Read on to learn more about the changes and why they were made. + + +The new PebbleOS 2.0 application sandbox environment allows running third party +native code (Pebble Apps) in a safe and secure manner. In the sandbox, any +application errors should not negatively impact the host operating system. + +Because of the hardware restrictions of a Pebble watch, the implementation is +achieved in a basic manner. Pebble Apps are run in their own FreeRTOS task that +is unprivileged. This means that the instructions the app is authorized to run +are restricted. For example, it’s not allowed to change the `CONTROL` register +(which changes whether the app is privileged or not) or change interrupt +settings. Furthermore, Pebble restricts the memory the app can access to a small +fixed region. This is done using the MPU (Memory Protection Unit) hardware +available in the Pebble microcontroller. Accesses outside of this region will +cause the application to be stopped. This way Pebble can make sure the +application is only interacting with the kernel in ways that will not interfere +with other features and functions. + +The FreeRTOS implementation includes a port that uses the MPU (ARM_CM3_MPU) +which is incompatible with the project goals. This port appears meant for safety +critical environments where tasks shouldn’t be allowed to accidentally interact +with each other. However, there doesn’t seem to be any protection from a +malicious task. For example, raising a task's privilege level is as easy as +triggering a software interrupt, which unprivileged tasks are free to use. “MPU +wrapper” functions are provided to allow any task to call a FreeRTOS API +function from any privilege level and operate as a privileged function, ignoring +any MPU settings. The sandbox is intended to restrict how the application +FreeRTOS task is allowed to interact with the kernel, so modifications were +necessary. + +To solve this, Pebble has created its own port named ARM_CM3_PEBBLE. This port +is based on the ARM_CM3_MPU port, but is modified to use the MPU in a different +way. No wrappers are provided (you need to be privileged to directly call +FreeRTOS functions) and the `portSVC_RAISE_PRIVILEGE` software interrupt is +removed. + +To permit the app to interact with the system in a safe manner, Pebble added a +new software interrupt, called `portSVC_SYSCALL`. Unprivileged code can use it +to jump into the operating system in a privileged state but only using a system- +provided jump-table. The jump-table contains the address to landing functions +(we refer to them as syscalls) that are individually responsible for checking +that the operation is permitted and the parameters are safe and valid. + +Pebble has also made some minor changes to how tasks are created and destroyed. +In order for the application to keep working inside its sandbox, it needs access +to certain resources like its own stack memory. FreeRTOS allows a programmer to +specify a buffer to be used as the stack, so a buffer that's allocated inside +the app sandbox memory region is used. However, FreeRTOS has a bug where it +tries to deallocate the stack memory when a task is destroyed, regardless of +where that memory came from. Pebble changed this code to only free the stack +when it was not provided by the system. + +Pebble also added the ability for the system to designate its own buffer for the +`_reent` struct. This struct is a feature of the c library we use - newlib - +that contains buffers that are used by various libc functions. For example, +`localtime` returns a pointer to a `struct tm` structure. This struct is +actually stored in the `_reent` struct. FreeRTOS has `_reent` support so which +`_reent` struct is currently being used is swapped around per task, so each task +has their own `_reent` struct to play with and you don’t have issues if two +threads call `localtime` at the same time. To ensure the `_reent` struct for the +application task is available within the sandboxed memory region, Pebble pre- +allocated it and passed it through to `xTaskCreate`. + +Finally, Pebble has added some functionality to inspect the saved registers of +tasks that aren’t currently running. This allows Pebble to collect additional +diagnostics if the watch exhibits any errors and to provide basic crash +reporting for apps. For example, App developers will get a dump of the app’s PC +(Program Counter) and LR (Link Register) registers at the time of the crash. + +Pebble is incredibly thankful for all the work that the FreeRTOS community has +put into the code. It has been a huge asset when building PebbleOS. We'll be +releasing additional updates as we continue to modify FreeRTOS in the future. If +you have any questions, don’t hesitate to [contact us](/contact). diff --git a/devsite/source/_posts/2014-06-10-Pebble-SDK-Tutorial-1.md b/devsite/source/_posts/2014-06-10-Pebble-SDK-Tutorial-1.md new file mode 100644 index 00000000..628288cd --- /dev/null +++ b/devsite/source/_posts/2014-06-10-Pebble-SDK-Tutorial-1.md @@ -0,0 +1,333 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK Tutorial (Part 1) +author: Chris Lewis +excerpt: | + This is the first in a series of articles that will hopefully help newcomers + and seasoned programmers alike begin to flex their creative muscles and make + their own Pebble watchapps. If you prefer a more visual approach, Thomas has + made videos you can watch to help you get started. +--- + +##Your First Watchapp + +###Introduction + +This is the first in a series of articles that will hopefully help newcomers and +seasoned programmers alike begin to flex their creative muscles and make their +own Pebble watchapps. If you prefer a more visual approach, Thomas has made +videos you can watch +[here](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ) to help you get +started. + +Firstly, make sure you are familiar with the following basic C language +concepts. Pebble has [Developer Guides](/guides/) that may help: + +- Variables and variable scope (local or global?) +- Functions (definitions and calls) +- Structures +- Pointers +- Program flow (if, else etc.) +- Preprocessor statements (`#define`, `#include` etc.) + +Up to speed? Good! Let's create our first watchapp. + + + +###Development Environment +The easiest way to get started is to use [CloudPebble]({{site.links.cloudpebble}}) +, a free online IDE for Pebble. This approach requires no installations to start +making watchapps. Advanced users can install the SDK on Linux, OS X or Windows +(via VM such as [VirtualBox](https://www.virtualbox.org/)) for a more hands-on +approach. Instructions for installing the native SDK can be found on the +[Pebble Developer site](/sdk/install/linux/) +. For this tutorial, we will be using CloudPebble. + +###First Steps +To get started, log into [CloudPebble]({{site.links.cloudpebble}}) and choose +'Create Project'. + +1. Enter a suitable name for the project such as 'My First App'. +2. Set the project type to 'Pebble C SDK'. +3. Set the template as 'Empty project'. +4. Confirm with 'Create'. + +To start entering code, choose 'New C file' on the left hand pane. C language +source files typically have the extension '.c'. A suggested standard name is +`main.c`, although this can be anything you like. + +###Setting Up the Basics +The power behind the C language comes from its ability to be adapted for many +different devices and platforms, such as the Pebble watch, through the use of +SDKs such as this one. Therefore, to be able to use all the features the Pebble +SDK has to offer us, we must include it at the start of the file by using the +`#include` preprocessor statement (meaning it is processed before the rest of +the code): + +```c +#include +``` + +All C applications begin with a call to a function called `main()` by the host +operating system, also known as the ‘entry point’ of the program. This must +contain ``app_event_loop()``, and by convention also contains two other +functions to manage the start and end of the watchapp's life: + +- `init()` - Used to create (or initialize) our app. +- ``app_event_loop()`` - Handles all events that happen from the start of the + app to the end of its life. +- `deinit()` - Used to clean up after ourselves and make sure any memory we use + is free for apps that are run after us. + +Following these rules, our first function, `main()` looks like this: + +```c +int main(void) +{ + init(); + app_event_loop(); + deinit(); +} +``` + +All functions we call must be defined before they are used so that they are +known to the compiler when the first call is encountered. To this end we will +define our `init()` and `deinit()` functions before they are called in `main()` +by placing them after the `#include` preprocessor statement and before the +definition of `main()` itself. The resulting code file now looks like this: + +```c +#include + +void init() +{ + //Create app elements here +} + +void deinit() +{ + //Destroy app elements here +} + +int main(void) +{ + init(); + app_event_loop(); + deinit(); +} +``` + +###Using the SDK +With the boilerplate app structure done, let's begin using the Pebble C SDK to +create our first watchapp. The first element we will use is the ``Window``. We +will declare our first instance of a ``Window`` outside the scope of any +function in what is known as a 'global variable', before it is first used. The +reason for this is for us to be able to use this reference from any location +within the program. By convention, a global variable's name is prefixed with +`g_` to make it easily identifiable as such. An ideal place for these +declarations is below the preprocessor statements in the source file. + +Declare a pointer to a ``Window`` structure to be created later like so: + +```c +Window *g_window; +``` + +At the moment, this pointer does not point anywhere and is unusable. The next +step is to use the ``window_create()`` function to construct a ``Window`` +element for the pointer to point to. Once this is done, we also register two +references to the `window_load()` and `window_unload()` functions (known as +handlers) which will manage the creation and destruction of the elements within +that window. This is shown below: + +```c +void init() +{ + //Create the app elements here! + g_window = window_create(); + window_set_window_handlers(g_window, (WindowHandlers) { + .load = window_load, + .unload = window_unload, + }); +} +``` + +The next logical step is to define what we mean by `window_load()` and +`window_unload()`. In a similar fashion to `init()` and `deinit()`, these must +be defined before they are first used; above `init()`, but below the definition +of `g_window` at the top of the file. Think of them as the `init()` and +`deinit()` equivalent functions for the ``Window``: + +```c +void window_load(Window *window) +{ + //We will add the creation of the Window's elements here soon! +} + +void window_unload(Window *window) +{ + //We will safely destroy the Window's elements here! +} +``` + +This modular approach to app creation allows a developer to create apps with all +relevant sub-elements managed within the life of that particular ``Window``. +This process is shown visually in the diagram below: + +![Lifecycle](/images/blog/tut_1_lifecycle.png "Lifecycle") + +As a responsible app developer, it is a good practice to free up any memory we +use to the system when our app is closed. Pebble SDK elements use functions +suffixed with `_destroy` for this purpose, which can be done for our ``Window`` +element in `deinit()`: + +```c +void deinit() +{ + //We will safely destroy the Window's elements here! + window_destroy(g_window); +} +``` + +The final step in this process is to make the app actually appear when it is +chosen from the Pebble menu. To do this we push our window to the top of the +window stack using ``window_stack_push()``, placing it in the foreground in +front of the user. This is done after the ``Window`` is created, and so we will +place this call at the end of `init()`: + +```c +window_stack_push(g_window, true); +``` + +The second argument denotes whether we want the push to be animated. This looks +cool, so we use `true`! The app will now launch, but will be completely blank. +Let's rectify that. + +###Displaying Content +So far we have created and pushed an empty ``Window`` element. So far so good. +Now let's add our first sub-element to make it show some text. Introducing the +``TextLayer``! This is an element used to show any standard string of characters +in a pre-defined area, called a 'frame'. As with any element in the SDK, we +begin with a global pointer to a structure-to-be of that type: + +```c +TextLayer *g_text_layer; +``` + +The rest of the process of using the ``TextLayer`` is very similar to that of +the ``Window``. This is by design, and means it is easy to tell which functions +to use as they are named in a 'plain English' style. Creating the ``TextLayer`` +will be done within the `window_load()` function, as it will be shown within the +parent ``Window`` (here called `g_window`). + +The functions we call to perform this setup are almost self-explanatory, but I +will go through them after the following segment. Can you spot the pattern in +the SDK function names? + +```c +g_text_layer = text_layer_create(GRect(0, 0, 144, 168)); +text_layer_set_background_color(g_text_layer, GColorClear); +text_layer_set_text_color(g_text_layer, GColorBlack); + +layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer)); +``` + +Now the explanation: + +1. ``text_layer_create()`` - This creates the ``TextLayer`` and sets its frame + to that in the ``GRect`` supplied as its only argument to x = 0, y = 0, width + = 144 pixels and height = 168 pixels (this is the size of the entire screen). + Just like ``window_create()``, this function returns a pointer to the newly + created ``TextLayer`` +2. ``text_layer_set_background_color()`` - This also does what it says: sets the + ``TextLayer``'s background color to ``GColorClear``, which is transparent. +3. ``text_layer_set_text_color()`` - Similar to the last function, but sets the + text color to ``GColorBlack``. +4. ``layer_add_child()`` - This is used to add the ``TextLayer``'s "root" + ``Layer`` (which is the part drawn to the screen) as a 'child' of the + ``Window``'s root layer. Simply put, all child ``Layer``s are drawn in front + of their 'parents' and allows us to control layering of ``Layer``s and in + which order they are drawn. + +As should always be the case, we must add the required destruction function +calls to free up the memory we used in creating the ``TextLayer``. This is done +in the parent ``Window``'s `window_unload()` function, which now looks like +this: + +```c +void window_unload(Window *window) +{ + //We will safely destroy the Window's elements here! + text_layer_destroy(g_text_layer); +} +``` + +Now for what we have been working towards all this time - making the app show +any text we want! After creating the ``TextLayer``, add a call to +``text_layer_set_text()`` to set the text it should display, such as the example +below: + +```c +text_layer_set_text(g_text_layer, "Anything you want, as long as it is in quotes!"); +``` + +Now you should be ready to see the fruits of your labor on your wrist! To do +this we must compile our C source code into a `.pbw` package file that the +Pebble app will install for us. + +###Compilation and Installation + +To do this, make sure you save your C file. Then go to the 'Compilation' screen +in the left pane and click 'Run build'. After the compiler returns 'Succeeded', +you can use either of the following options to install the app on your Pebble: + +- Ensure you have enabled the + [Pebble Developer Connection](/guides/tools-and-resources/developer-connection/) + and enter the IP address shown into the 'Phone IP' box. Click 'Install and + Run' to install your app. +- Use the 'Get PBW' button in your phone's browser to install via the Pebble app. + +If your code does not compile, compare it to the +[sample code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) to see +where you might have made an error. Once this is successful, you should see +something similar to the screenshot below: + +![Screenshot](/images/blog/tut_1_preview.png "Screenshot") + +###Conclusions +There you have it, a *very* simple watchapp to show some text of your choosing. +This was done by: + +1. Setting up boilerplate app structure. +2. Creating a ``Window`` with a child layer, in this case the ``TextLayer``. +3. Creating a ``TextLayer`` to show text on the screen. + +By adding more types of layers such as ``BitmapLayer`` and `InverterLayer` we +create much more sophisticated apps. By extension with ``AppMessage``s and +``Clicks`` we can begin to respond to user data and input. All this and more to +come in future instalments! + +###Source Code +The full source code file we have built up over the course of this article can +be found [here](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) and +directly imported into CloudPebble +[as a new project](https://cloudpebble.net/ide/gist/a911f0b053260f209390). If +your code doesn't compile, have a look at this and see where you may have gone +wrong. + +Best of luck, and let me know what you come up with! Send any feedback or +questions to [@PebbleDev](http://twitter.com/PebbleDev) or +[@Chris_DL](http://twitter.com/Chris_DL). diff --git a/devsite/source/_posts/2014-06-18-Your-First-Watchface.md b/devsite/source/_posts/2014-06-18-Your-First-Watchface.md new file mode 100644 index 00000000..82dd29f5 --- /dev/null +++ b/devsite/source/_posts/2014-06-18-Your-First-Watchface.md @@ -0,0 +1,237 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble SDK Tutorial (Part 2) +author: Chris Lewis +excerpt: | + This is the second in a series of articles aimed at helping developers new and + experienced begin creating their own watchapps. In this section we will be + using the TickTimerService to display the current time, which is the basis of + all great watchfaces. After this, we will use GBitmaps and BitmapLayers to + improve the aesthetics of our watchface. +--- + +##Your First Watchface + +###Previous Tutorial Parts +[Pebble SDK Tutorial (Part 1): Your First Watchapp](/blog/2014/06/10/Pebble-SDK-Tutorial-1/) + +###Introduction + +This is the second in a series of articles aimed at helping developers new and +experienced begin creating their own watchapps. In this section we will be using +the ``TickTimerService`` to display the current time, which is the basis of all +great watchfaces. After this, we will use ``GBitmap``s and ``BitmapLayer``s to +improve the aesthetics of our watchface. By the end of this section, our app +will look like this: + +![final](/images/blog/tut_2_frame_added.png "final") + + + +###Getting Started + +To begin creating this watchface, we will be using the +[source code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) from the +last post as a starting point, which you can import into +[CloudPebble]({{site.links.cloudpebble}}) as a new project +[here](https://cloudpebble.net/ide/gist/a911f0b053260f209390). + +As you may have noticed, the current watchapp is started from the Pebble main +menu, and not on the watchface carousel like other watchfaces. To change this, +go to 'Settings' in your CloudPebble project and change the 'App Kind' to +'Watchface'. This will cause the app to behave in the same way as any other +watchface. If you were using the native SDK, this change would be done in the +`appinfo.json` file. + +Once this is done, we will modify the main C file to prepare it for showing the +time. Remove the call to ``text_layer_set_text()`` to prevent the watchface +showing anything irrelevant before the time is shown. For reference, +`window_load()` should now look like this: + +```c +void window_load(Window *window) +{ + //We will add the creation of the Window's elements here soon! + g_text_layer = text_layer_create(GRect(0, 0, 144, 168)); + text_layer_set_background_color(g_text_layer, GColorClear); + text_layer_set_text_color(g_text_layer, GColorBlack); + + layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer)); +} +``` + +###Telling the Time + +Now we can use the ``TextLayer`` we created earlier to display the current time, +which is provided to us once we register a ``TickHandler`` with the system. The +first step to do this is to create an empty function in the format dictated by +the ``TickHandler`` documentation page. This is best placed before it will be +used, which is above `init()`: + +```c +void tick_handler(struct tm *tick_time, TimeUnits units_changed) +{ + +} +``` + +This function is called by the system at a fixed tick rate depending on the +``TimeUnits`` value supplied when we register the handler. Let's do this now in +`init()` before ``window_stack_push()``, using the ``MINUTE_UNIT`` value to get +an update every minute change: + +```c +tick_timer_service_subscribe(MINUTE_UNIT, (TickHandler)tick_handler); +``` + +Now that we have subscribed to the ``TickTimerService``, we can add code to the +`tick_handler()` function to use the time information provided in the +`tick_time` parameter to update the ``TextLayer``. The data stored within this +structure follows the +[ctime struct tm format](http://www.cplusplus.com/reference/ctime/tm/). This +means that we can access the current hour by using `tick_time->tm_hour` and the +current minute by using `tick_time->tm_min`, for example. Instead, we will use a +function called `strftime()` that uses the `tick_time` parameter to write a +time-formatted string into a buffer we provide, called `buffer`. Therefore the +new tick handler will look like this: + +```c +void tick_handler(struct tm *tick_time, TimeUnits units_changed) +{ + //Allocate long-lived storage (required by TextLayer) + static char buffer[] = "00:00"; + + //Write the time to the buffer in a safe manner + strftime(buffer, sizeof("00:00"), "%H:%M", tick_time); + + //Set the TextLayer to display the buffer + text_layer_set_text(g_text_layer, buffer); +} +``` + +Now every time a minute passes `tick_handler()` will create a new string in the +buffer and display it in the ``TextLayer``. This is the basis of every +watchface. However, the text contained in the ``TextLayer`` is difficult to read +because the default system font is very samll, so we will change some of the +layout properties to better suit its purpose. Modify `window_load()` to add the +relevant ``TextLayer`` functions like so: + +```c +void window_load(Window *window) +{ + //Create the TextLayer + g_text_layer = text_layer_create(GRect(0, 59, 144, 50)); + text_layer_set_background_color(g_text_layer, GColorClear); + text_layer_set_text_color(g_text_layer, GColorBlack); + + //Improve the layout to be more like a watchface + text_layer_set_font(g_text_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD)); + text_layer_set_text_alignment(g_text_layer, GTextAlignmentCenter); + + layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer)); +} +``` + +Now the watchface should look more like a classic watchface with a larger font +and centered text: + +![largetext](/images/blog/tut_2_largetext.png "large text") + +###Adding Bitmaps +The next logical way to improve the watchface is to add some complementary +images. These take the form of ``GBitmap``s, and are referenced by a +`RESOURCE_ID` specified in CloudPebble Settings or `appinfo.json` when working +with the native SDK. The first bitmap we wil add will be a smart border for the +``TextLayer``, shown for you to use below: + +![frame](/images/blog/tut_2_frame.png "frame") + +To add this image to our project in CloudPebble, click "Add New" next to +Resources, navigate to your stored copy of the image above and give it an ID +such as "FRAME", then click Save. + +Once this is done, we can add it to the watchface. The first step is to declare +two new global items: A ``GBitmap`` to hold the loaded image and a +``BitmapLayer`` to show it. Add these to the top of the file, alongside the +existing global variables: + +```c +GBitmap *g_frame_bitmap; +BitmapLayer *g_frame_layer; +``` + +The next step is to allocate the ``GBitmap``, show it using the ``BitmapLayer`` +and add it as a child of the main ``Window``. ``Layer``s are drawn in the order +they are added to their parent, so be sure to add the ``BitmapLayer`` **before** +the ``TextLayer``. This will ensure that the time is drawn on top of the image, +and not the other way around: + +```c +//Create and add the image +g_frame_bitmap = gbitmap_create_with_resource(RESOURCE_ID_FRAME); +g_frame_layer = bitmap_layer_create(GRect(7, 56, 129, 60)); +bitmap_layer_set_bitmap(g_frame_layer, g_frame_bitmap); +layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(g_frame_layer)); +``` + +Once again, remember to add calls to `_destroy()` for each element to free +memory in `window_unload()`: + +```c +void window_unload(Window *window) +{ + //We will safely destroy the Window's elements here! + text_layer_destroy(g_text_layer); + + gbitmap_destroy(g_frame_bitmap); + bitmap_layer_destroy(g_frame_layer); +} +``` + +With these steps completed, a re-compile of your project should yield something +similar to that shown below: + +![frameadded](/images/blog/tut_2_frame_added.png "frame added") + +The ``TextLayer`` is now surrounded by a crisp frame border. It's not amazing, +but it's the start on a journey to a great watchface! + +###Conclusion +There you have it: turning your simple watchapp into a basic watchface. In +summary: + +1. Modify the App Kind to be a watchface instead of a watchapp. +2. Add a subscription to the ``TickTimerService`` to get the current time. +3. Modify the ``TextLayer`` layout to better suit the task of telling the time. +4. Add an image resource to the project +5. Display that image using a combination of ``GBitmap`` and ``BitmapLayer``. + +From there you can add more images, change the text font and size and more to +further improve the look of the watchface. In future posts you will learn how to +use ``Timer``s and ``Animation``s to make it even better! + +###Source Code +Just like last time, the full source code file can be found +[here](https://gist.github.com/C-D-Lewis/17eb11ab355950595ca2) and directly +imported into CloudPebble +[as a new project](https://cloudpebble.net/ide/gist/17eb11ab355950595ca2). If +your code doesn't compile, have a look at this and see where you may have gone +wrong. + +Send any feedback or questions to [@PebbleDev](http://twitter.com/PebbleDev) or +[@Chris_DL](http://twitter.com/Chris_DL). + + diff --git a/devsite/source/_posts/2014-08-26-Getting-Ready-for-Automatic-App-Updates.md b/devsite/source/_posts/2014-08-26-Getting-Ready-for-Automatic-App-Updates.md new file mode 100644 index 00000000..d20fb15a --- /dev/null +++ b/devsite/source/_posts/2014-08-26-Getting-Ready-for-Automatic-App-Updates.md @@ -0,0 +1,122 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Getting Ready for Automatic App Updates +author: katharine +tags: +- Freshly Baked +--- + +We are pleased to announce that we will soon be automatically updating apps installed from the Pebble +appstore. We believe this will be a significant improvement to your ability to get your work into +the hands of your users, as well as to the user experience. + +However, as developers, you need to make sure that your app is ready for these updates. In +particular, you will need to make sure that your app conforms to our new version numbering, and +that it can tolerate persistent storage left over from old app versions. + + + + +Version Numbers +--------------- + +To prepare for automatic app updates, we have imposed new requirements on the format of app +version numbers. The new format is: + +
    +

    major.minor

    +
    + +Where minor is optional, and each component is considered independently (so 2.10 is newer than 2.5). +For instance, all of the following are valid version codes: + +* 1.0 +* 2.3 +* 0.1 +* 2 + +However, neither of the following are: + +* ~~1.0.0~~ +* ~~2.5-beta6~~ + +We will automatically upgrade the app if the version number from the watch is less than the latest +released version on the appstore. If old versions of your app contained a "bugfix" number, we will +use a number truncated to the major.minor format — so "1.0.7" will be taken as "1.0". + +Persistent Storage +------------------ + +Automatic updates will retain persistent storage between app versions. This ensures that your users +will not be frustrated by updates silently deleting their data and settings. However, this puts the +onus on you as developers to ensure your app behaves well when presented with an old version of +its persistent storage. If your app does not already use persistent storage, you can ignore this +section. + +We recommend that you do this by versioning your persistent storage, and incrementing the version +every time its structure changes. + +The easiest way to do this versioning will be to create a new key in your persistent storage +containing the app version. If you have not already versioned your storage, you should simply check +for the version key and assume its absence to mean "version zero". + +Once you know the "storage version" of your app, you can perform some migration to make it match +your current format. If the version is too old, or you have multiple "version zero" formats that you +cannot otherwise distinguish, you may instead just delete the old keys - but keep in mind that this +is a poor user experience, and we recommend avoiding it wherever possible. + +Ultimately, you might have code that looks something like this: + +```c +#define STORAGE_VERSION_KEY 124 // any previously unused value +#define CURRENT_STORAGE_VERSION 3 +// ... +static void clear_persist(void) { + // persist_delete all your keys. +} + +static void read_v2_persist(void) { + // Read the old v2 format into some appropriate structure +} + +static void read_v1_persist(void) { + // Read the old v1 format into some appropriate structure. +} + +static void migrate_persist(void) { + uint32_t version = persist_read_int(STORAGE_VERSION_KEY); // defaults to 0 if key is missing. + + if(version > CURRENT_STORAGE_VERSION) { + // This is more recent than what we expect; we can't know what happened, so delete it + clear_persist(); + } else if(version == 2) { + read_v2_persist(); + store_persist(); + } else if(version == 1) { + read_v1_persist(); + store_persist(); + } else if(version == 0) { + // Again, just delete this - perhaps we have multiple unversioned types, or 0 is just too + // long ago to be worth handling. + clear_persist(); + } +} +``` + +Obviously, that is just an example; you will have to deal with your own situation as appropriate. + +If you have further questions about getting ready for automatic app updates, let us know! [Contact us](/contact) or +[post on our forums](https://forums.getpebble.com/categories/developer-discussion). diff --git a/devsite/source/_posts/2014-10-10-FreeRTOS-Modifications-From-Pebble-v2.md b/devsite/source/_posts/2014-10-10-FreeRTOS-Modifications-From-Pebble-v2.md new file mode 100644 index 00000000..bbfa67ca --- /dev/null +++ b/devsite/source/_posts/2014-10-10-FreeRTOS-Modifications-From-Pebble-v2.md @@ -0,0 +1,65 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: FreeRTOS™ Code Revisions - Background Worker Edition +author: brad +tags: +- Down the Rabbit Hole +--- + +As part of our commitment to the open source community, Pebble is releasing its +recent modifications to the FreeRTOS project. Pebble has made a few minor +changes to FreeRTOS to enable Background Workers for PebbleOS 2.6 as well as to +make Pebble easier to monitor and debug. + +The changes are available [as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.2.tar.gz). + +Below is a changelog of the modifications since the last time we released our +fork of the FreeRTOS code back in May. + + +* Added `UBaseType_t uxQueueGetRecursiveCallCount( QueueHandle_t xMutex ) + PRIVILEGED_FUNCTION;` to queue.h + * Retrieves the number of times a mutex has been recursively taken + * FreeRTOS always tracked this internally but never made it available + externally + * Used to debug locking relating issues in PebbleOS + +* Added `configASSERT_SAFE_TO_CALL_FREERTOS_API();` + * This macro can be used to assert that it is safe to call a FreeRTOS API. + It checks that the caller is not processing an interrupt or in a critical + section. + * See http://www.freertos.org/RTOS-Cortex-M3-M4.html for more details on + how interrupts interact with the FreeRTOS API +* Added `configASSERT_SAFE_TO_CALL_FREERTOS_FROMISR_API();` + * This macro can be used to assert that it is safe to call a FreeRTOS + "FromISR" API. It checks that the caller is at an appropriate interrupt + level. + * See http://www.freertos.org/RTOS-Cortex-M3-M4.html for more details on + how interrupts interact with the FreeRTOS API +* Added `uintptr_t ulTaskGetStackStart( xTaskHandle xTask );` to task.h + * Retrieves the address of the start of the stack space + * Useful for implementing routines which check for available stack space +* Added `bool vPortInCritical( void );` to port.c + * Indicates if we're currently in a FreeRTOS critical section + * Used to implement `configASSERT_SAFE_TO_CALL_FREERTOS_API();` +* Fixed an issue with vPortStoreTaskMPUSettings that occurred when more than + one task wanted to configure MPU regions + * This bug was encountered when adding support for Background Workers in + FW 2.6 + +Pebble would like to again extend its thanks to FreeRTOS and its community. +We'll be releasing additional updates as we continue to modify FreeRTOS in the +future. If you have any questions, don’t hesitate to [contact us](/contact). diff --git a/devsite/source/_posts/2014-10-29-Displaying-remote-images.md b/devsite/source/_posts/2014-10-29-Displaying-remote-images.md new file mode 100644 index 00000000..2ad55c99 --- /dev/null +++ b/devsite/source/_posts/2014-10-29-Displaying-remote-images.md @@ -0,0 +1,149 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Displaying remote images in a Pebble app +author: thomas +tags: + - Beautiful Code +--- + +> A picture is worth a thousand words. + +The old adage applies just as well to your Pebble apps! One of the most common +requests when [we attend hackathons](/community/events/) is "How do I transfer an image +from the Internet to my Pebble app?". + +Today we introduce a new example that demonstrates downloading a PNG file from +the Internet and loading it on Pebble. We will also cover how to prepare your +images so that they take as little memory as possible and load quickly on Pebble. + +The code is available in our +[pebble-examples github account][pebble-faces]. Continue reading for +more details! + + +## Downloading images to Pebble + +The first step to display a remote image is to download it onto the phone. To do +this we built a simple library that can be easily reused: NetDownload. It is +based on [PebbleKit JavaScript](/guides/communication/using-pebblekit-js) and +``AppMessage``. The implementation is in +[`pebble-js-app.js`][pebble-js-app.js] for the JavaScript part and in +[`netdownload.c`][netdownload.c] for the C part. + +Let's walk through the download process: + + - The C application initializes the library with a call to + `netdownload_initialize()`. The library in turns initializes the AppMessage + framework. + - The [`show_next_image()` function][netdownload-call] calls `netdownload_request(char *url)` to + initiate a download. This function sends an AppMessage to the JavaScript with + two elements. One is the URL to be downloaded and the second one is the + maximum transmission size supported by the watch (this is provided by + ``app_message_inbox_size_maximum()``). + - The JavaScript receives this message, tries to download the resource from + the Internet (via `downloadBinaryResource()`) and saves it as byte array. + - The image is then split into chunks (based on the maximum transmission size) + and sent to the watch. The first message contains the total size of the image + so the watchapp can allocate a buffer large enough to receive the entire + image. We use the JavaScript success and error callbacks to resend chunks as necessary. + - After the last chunk of data, we send a special message to tell the app that + we are done sending. The app can then check that the size matches what was + announced and if everything is ok, it calls the download successful callback + that was defined when initializing the library. + +## Loading PNG images on Pebble + +Instead of converting images to the native PBI format of Pebble, this example +transmits the image in PNG format to the watch and uses a PNG library to decompress +the image and to display it on the screen. The PNG library used is +[uPNG by Sean Middleditch and Lode Vandevenne][upng]. + +This approach is very convenient for multiple reasons: + + - It is often easier to generate PNG images on your server instead of the + native PBI format of Pebble. + - PNG images will be smaller and therefore faster to transfer on the network + and over Bluetooth. + +It does have one drawback though and that is that the PNG library uses +approximately 5.5kB of code space. + +In the NetDownload callback we receive a pointer to a byte-array and its length. +We simply pass them to `gbitmap_create_with_png_data()` (provided by `png.h`) +and in return we get a ``GBitmap`` that we can display like any other +``GBitmap`` structure. + +## Preparing your images + +Because your PNG file will be transferred to Pebble and loaded into the limited +memory available on the watch, it is very important to make sure that the PNG is +as small as possible and does not contain useless information. + +Specifically: + + - The image should be 144x168 pixels (fullscreen) or smaller + - It should be in black and white + - It should not contain any metadata (like the author name, gps location of the + pictures, etc) + +To prepare your image, we recommend using Image Magick (careful! Graphics Magick +is a different library and does not support some of the options recommended +below, it is important to use Image Magick!) + + convert myimage.png \ + -adaptive-resize '144x168>' \ + -fill '#FFFFFF00' -opaque none \ + -type Grayscale -colorspace Gray \ + -colors 2 -depth 1 \ + -define png:compression-level=9 -define png:compression-strategy=0 \ + -define png:exclude-chunk=all \ + myimage.pbl.png + +Notes: + + - `-fill #FFFFFF00 -opaque none` makes the transparent pixels white + - `-adaptive-resize` with `>` at end means resize only if larger, and maintains aspect ratio + - we exclude PNG chunks to reduce size (like when image was made, author) + +If you want to use [dithering](http://en.wikipedia.org/wiki/Dither) to simulate Gray, you can use this command: + + convert myimage.png \ + -adaptive-resize '144x168>' \ + -fill '#FFFFFF00' -opaque none \ + -type Grayscale -colorspace Gray \ + -black-threshold 30% -white-threshold 70% \ + -ordered-dither 2x1 \ + -colors 2 -depth 1 \ + -define png:compression-level=9 -define png:compression-strategy=0 \ + -define png:exclude-chunk=all \ + myimage.pbl.png + +For more information about PNG on Pebbles, how to optimize memory usage and tips +on image processing, please refer to the +[Advanced techniques videos](/community/events/developer-retreat-2014/) +from the Pebble Developer Retreat 2014. + +## Connecting the pieces + +The main C file (`pebble-faces.c`) contains a list of images to load and +everytime you shake your wrist it will load the next one. Take a few minutes to +test this out and maybe this technique will find its way into your next watchface! + +[pebble-faces]: {{site.links.examples_org}}/pebble-faces +[netdownload.c]: {{site.links.examples_org}}/pebble-faces/blob/master/src/netdownload.c +[pebble-js-app.js]: {{site.links.examples_org}}/pebble-faces/blob/master/src/js/pebble-js-app.js +[netdownload-call]: {{site.links.examples_org}}/pebble-faces/blob/master/src/pebble-faces.c#L25 +[upng]: https://github.com/elanthis/upng diff --git a/devsite/source/_posts/2014-12-19-Leverage-Android-Actionable-Notifications.md b/devsite/source/_posts/2014-12-19-Leverage-Android-Actionable-Notifications.md new file mode 100644 index 00000000..fd2c2e98 --- /dev/null +++ b/devsite/source/_posts/2014-12-19-Leverage-Android-Actionable-Notifications.md @@ -0,0 +1,222 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Send a Smile with Android Actionable Notifications +author: thomas +date: 2014-12-19 +tags: +- Beautiful Code +banner: /images/blog/1219-header.jpg +--- + +Just a few days ago, we released [beta version 2.3 of our Android +Application][android-beta] with support for actionable notifications. If you +have not tested it already, [enroll in our beta channel][beta-channel] and try +it out for yourself! + +Notifications have always been a key use case for Pebble, and we are excited by +this new feature which is going to change the way you look at +notifications. With actionable notifications Pebble not only informs you +about relevant events, users can now interact with them and choose from actions +you as an Android developer attach to them. + +When connected to an Android device, Pebble will show all wearable actions, just +like any Android Wear device. While supporting wearable notifications is easy +we have found that there are still a number of mobile apps who miss the opportunity +to extend their reach to the wrist. Don't let your app be one of those! + +In this post, we will describe what you can do with actions on wearable devices +and how to add them to your Android notifications. + + + +## Actionable Notifications + +Android actionable notifications on the phone were introduced with Android 4.1. +Developers specify different actions per notification and users can react to them +by pushing a button on the phone's screen. +By default those actions are shown in the notification area. + +You can make those actions visible to wearable devices and you even have the +option to collect data from the user before triggering the action. For example, +a "Reply" action can offer a list of pre-defined messages that the user can +choose from. On Pebble, this includes a long list of nice emojis! + +To take advantage of actionable notifications on Pebble and Android Wear +devices, you need to do two things: + + - Add support for notification actions in your application + - Declare which ones you want to make available on wearable device + +Let's take a look at how to do this. + +## Pushing a Notification to the Watch + +All notifications start with the Android Notification Builder. A best practice +is to use the `NotificationCompat` class from the [Android Support +libraries][android-support-lib] which will automatically handle backwards +compatibility issues. For example, action buttons are only available in Android +4.1 and up; the library will automatically make sure your code stays compatible +with older Android phones and hide the buttons if necessary. + +If you have not installed the Android Support Library, you can [follow these +instructions][android-support-lib-setup] or simply remove the `Compat` +suffix to the classes we use in this post. + +To prepare a notification, you create an instance of a builder and define some +properties on it. At the very least, you need an icon, a title and some text: + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setSmallIcon(R.drawable.ic_launcher); + builder.setContentTitle("Wearable Pomodoro"); + builder.setContentText("Starting the timer ..."); + +To push the notification, get a hold of the system notification manager: + + NotificationManager notifManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + +And finally send the notification: + + notifManager.notify(42, builder.build()); + +> The first parameter is a notification id that you can use to edit or cancel the +notification later. + +Once you give your app the permission to send notifications to your Pebble +via the notifications menu in the Pebble Android app, +this notification appears with the default _Dismiss_ action. + +![A simple notification on an Android device and a Pebble](/images/blog/1219-an-notif.png) + +If you press _Dismiss_ on Pebble, the notification will be dismissed on the +watch **and** on the phone. This is a nice start, but there's so much more +control you can give to your users with custom actions. + +> While custom actions work for Android 4.1 and above, +> dismiss actions on Pebble are only visible from Android 4.3 onwards. + +## Adding Actions to Notifications + +Let's add a custom action to our notification. +The first step is to define what it will do when it is triggered. +Actions can execute any of the standard Android intents (Activity, Service or +Broadcast). You prepare the intent, wrap it in a `PendingIntent` instance and it +will be triggered if the user selects this action. + +This is what this looks like with a broadcast intent: + + // Create a new broadcast intent + Intent pauseIntent = new Intent(); + pauseIntent.setAction(ACTION_PAUSE); + + // Wrap the intent into a PendingIntent + PendingIntent pausePendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, pauseIntent, 0); + +> Note here that `ACTION_PAUSE` is simply a string that we defined as a constant +> in the class: +> +> static final String ACTION_PAUSE = "com.getpebble.hacks.wearablepomodoro.PAUSE"; + +Now that we have the pending intent, we can create the actual +`Notification.Action` instance using an action builder: + + NotificationCompat.Action pauseAction = + new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Pause", pausePendingIntent).build(); + +To make this action available on the Android notification view, you can add it +directly to the notification: + + builder.addAction(pauseAction); + +Finally, **and this is the only _wearable_ specific step**, to make the action +available on Pebble and Android Wear, you **must** declare it as available to +wearable devices: + + builder.extend(new NotificationCompat.WearableExtender().addAction(pauseAction)); + +We now have an action available on Android and on Pebble: + +![An action shown on the phone and the watch](/images/blog/1219-an-pause.png) + +You can add multiple actions as you want. If you have more than one, Pebble will +show a "More..." menu and display all the actions in a list. + +> The option to "Open on Phone" is not available in this example because we did +> not define a default action for the notification. It will appear automatically +> if you do. + +## User Input on Action + +In some cases a simple choice of actions is not enough. You may want to collect +some information from the user, for example a reply to a text message. + +On Pebble, the user will be presented with a list of possible replies that you +can customize. Such actions always present a list of emojis, too. +In Android lingo, this is an +action with support for remote input and it is trivial to set up and configure: + + String[] excuses = { "Coworker", "Facebook", "Exercise", "Nap", "Phone", "N/A" }; + RemoteInput remoteInput = new RemoteInput.Builder(KEY_INTERRUPT_REASON) + .setLabel("Reason?") + .setChoices(excuses) + .build(); + NotificationCompat.Action interruptAction = + new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Interrupt", replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + +> Pebble will hide input actions if you set `.setAllowFreeFormInput(false)`. + +Don't forget to add this action to the WearableExtender to **make it visible on +wearable devices**: + + builder.extend(new Notification.WearableExtender().addAction(pauseAction).addAction(replyAction)); + +And this is what it looks like on Pebble: + +![Action with remote input on Pebble](/images/blog/1219-an-pebble-interrupt.png) + +## Caveat + +One useful caveat to know about when you are sending notifications to Pebble is +that the watch will automatically de-duplicate identical notifications. So if +the title and content of your notification is identical to another notification +recently displayed, Pebble will not show it. + +This is very useful in the field to avoid spamming the user when notifications +are updated but as a developer you may run into this when you test your app. +If you do, just make sure something in your message changes between each notification. + +## That's All Folks! + +As you can see there is nothing magical about actionable notifications – and they are +very easy to add to your app. We look forward to more Android apps supporting +useful wearable actions and taking advantage of remote input. + +> **Further Reading on Android Notifications** +> +> For more information, we suggest you take a look at the [Android Notifications +Design Guide][android-patterns-notifications] and the associated [developer +guide][android-development-notifications]. Both are great resources to make the +most of notifications! + + +[android-beta]: https://blog.getpebble.com/2014/12/16/ad-23/ +[beta-channel]: /blog/2014/06/12/Android-Beta-Channel/ +[android-patterns-notifications]: http://developer.android.com/design/patterns/notifications.html +[android-development-notifications]: http://developer.android.com/guide/topics/ui/notifiers/notifications.html +[android-support-lib]: http://developer.android.com/tools/support-library/ +[android-support-lib-setup]: http://developer.android.com/tools/support-library/setup.html diff --git a/devsite/source/_posts/2015-01-30-Development-Of-The-Pebble-Emulator.md b/devsite/source/_posts/2015-01-30-Development-Of-The-Pebble-Emulator.md new file mode 100644 index 00000000..f90dcc98 --- /dev/null +++ b/devsite/source/_posts/2015-01-30-Development-Of-The-Pebble-Emulator.md @@ -0,0 +1,576 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Emulator 1/2 - QEMU for Pebble +author: Ron Marianetti +tags: +- Down the Rabbit Hole +date: 2015-01-30 +--- + +This is another in a series of technical articles provided by the members of the +Pebble software engineering team. This article describes some recent work done +at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short +for Quick EMUlator, is a generic, open source machine emulator and virtualizer). + + + + +> This post is part 1 of 2, and covers the development of the Pebble QEMU +> emulator itself. Read +> [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1] +> for more information on PebbleKit JS emulation and +> embedding the emulator in CloudPebble. + +An early implementation of this emulator was used internally at Pebble over a +year ago and was mentioned in a previous article published in April, 2014, +[*How Pebble Converted 135,070 Customized Watchfaces for Pebble OS v2.0.*][4] +However, it had languished due to lack of attention, and somewhere along the +line changes made to the firmware had broke it altogether. + +An emulator has many benefits, not only for outside developers but also for the +internal team: + +* New software can be loaded, executed, and tested much faster on the emulator + than on actual hardware. +* It greatly facilitates automated testing, due to the faster speed of + execution, convenience, and ease of scripting. +* For internal use, the emulator can be built to run firmware images that + wouldn’t fit into the limited memory of the real hardware, enabling engineers + to develop and test new features with extra debugging features included. +* Some tasks, like testing localization changes, can be easily performed in + parallel by launching a different emulator instance for each language. + +[The QEMU open source project][2] was written by Fabrice Bellard. QEMU is a very +flexible and powerful code base capable of emulating a wide variety CPUs, one of +which is the ARM Cortex M3 that is inside the STM32F2xx microcontroller used in +the Pebble. Building upon the core QEMU project as a base, Andre Beckus created +a [fork][3] that adds in support for the hardware peripherals of the STM32F1xx +series of microcontrollers. Pebble further built upon Andre Beckus’ fork for its +emulator and added in support for the STM32F2xx series of microcontrollers as +well as the specific hardware components in the Pebble that are outside the +microcontroller, like the display and buttons. The result is a full-featured +Pebble emulator capable of executing Pebble firmware images and native 3rd party +applications. + +## Interacting With the Emulator + +When you launch the Pebble emulator on a host machine you are presented with a +window displaying the contents of the Pebble display. You can interact with it +using the arrow keys from the keyboard, which act as the 4 buttons on the Pebble +(“back”, “up”, “down”, and “select”). You can also load in and run, unmodified, +any third-party watchface or watchapp written for the Pebble. + +A huge part of the value of the Emulator is in the additional features it +provides for development and testing purposes, like being able to interact with +the Pebble through a terminal and debug it using gdb. These capabilities are not +even possible with a standard shipping Pebble. Before the emulator, the only way +engineers at Pebble could accomplish this was to use custom “big boards”, which +are specially built boards that include the standard Pebble components with the +addition of a USB port and associated circuitry. + +The emulator exposes three socket connections to the outside world. The first, +the gdb socket, is built into the base QEMU framework itself and allows one to +connect and debug the emulated CPU using gdb. The second, the console socket, is +specific to Pebble emulation and is a simple terminal window into which you can +see print output and issue commands to the Pebble OS. The third, the qemu +channel, is also specific to Pebble emulation and is used for sending Bluetooth +traffic and various other hardware sensor information such as the battery level, +compass direction, and accelerometer readings. + +## The GDB Socket + +Built into QEMU is a gdb remote server that listens by default on port 1234 for +connections from gdb. This remote server implements most of the basic debugging +primitives required by gdb including inspecting memory, setting breakpoints, +single-stepping, etc. + +A critical aspect to debugging firmware running on the Pebble is the ability to +see what each of the threads in the operating system is doing at any time. For +this, gdb provides the “info threads” and related commands like “thread apply +all backtrace”, etc. In order to gather the information about the threads +though, the gdb remote server needs to understand where the thread information +is stored in memory by the remote target, which of course is operating system +specific. + +The built-in gdb remote server implemented by QEMU does not have this knowledge +because it is specific to the Pebble. Internally, Pebble uses FreeRTOS for +managing threads, mutexes, semaphores, etc. Since the location of this +information in memory could easily change in different versions of the Pebble +firmware, it did not seem appropriate to incorporate it into QEMU. Instead, we +took the approach of creating a proxy process that sits between gdb and the gdb +remote server implemented by QEMU. This proxy forwards most generic requests +from gdb unmodified to QEMU and returns the responses from QEMU back to gdb. If +however it sees a request from gdb that is thread related, it does the handling +in the proxy itself. Interpreting the thread command generally involves issuing +a series of primitive memory read requests to QEMU to gather the information +stored by FreeRTOS. This design ensures that the QEMU code base is isolated from +the operating system dependencies of the Pebble firmware that could change from +version to version. + +## The Console Socket + +Console input and output has always been built into the Pebble OS but was +traditionally only accessible when using one of the engineering “big boards”. +There are function calls in Pebble OS for sending output to the console and a +simple interpreter and executor that parses input commands. This console output +and the available commands are invaluable tools when developing and debugging +the Pebble firmware. + +Routing this console output to a TCP socket leverages a very powerful and +flexible feature of QEMU, which is the ability to create up to four virtual +serial devices. Each serial device can be associated with a file, pipe, +communication port, TCP socket (identified by port number), etc. This capability +is exposed through the `-serial` command line option of QEMU. When we launch +QEMU to emulate a Pebble, we create one of these serial devices for console use +and assign it to a socket. On the emulated Pebble side, the firmware is simply +accessing a UART peripheral built into the STM32F2xx and has no knowledge of the +external socket connection. In QEMU, the code that emulates that UART device is +given a reference to that virtual serial device so that it can ferry data +between the two. + +## The QEMU Channel Socket + +The third socket is the Pebble QEMU channel (PQ channel). This channel is used +for a number of purposes that are specific to the Pebble running in the +emulator. For example, we send data through this channel to give the Pebble +custom sensor readings like accelerometer, compass, battery level, etc. We also +use this channel to send and receive Bluetooth traffic. + +We have decided to take the approach of creating custom builds of the firmware +for use in the emulator, which frees us from having to make the emulator run the +exact same firmware image that runs on an actual hardware. Of course it is +advantageous to minimize the differences as much as possible. When it comes to +each of the hardware features that needs to be emulated, a judgment call thus +has to be made on where to draw the line – try to emulate the hardware exactly +or replace some of the low level code in the firmware. Some of the areas in the +firmware that we have decided to modify for the emulator are the areas that +handle Bluetooth traffic, accelerometer readings, and compass readings, for +example. + +There is some custom code built into the firmware when it is built for QEMU to +support the PQ channel. It consists of two components: a fairly generic +STM32F2xx UART device driver (called “qemu_serial”) coupled with some logic to +parse out “Pebble QEMU Protocol” (PQP) packets that are sent over this channel +and pass them onto the appropriate handler. Every PQP packet is framed with a +PQP header containing a packet length and protocol identifier field, which is +used to identify the type of data in the packet and the handler for it. + +## Bluetooth Traffic + +In the Pebble OS, there is a communication protocol used which is called, not +surprisingly, “Pebble Protocol”. When running on a real Pebble, this protocol +sits on top of Bluetooth serial and is used for communication between the phone +and the Pebble. For example, this protocol is used to install apps onto the +Pebble, get the list of installed apps, set the time and date on the watch, etc. + +The pebble SDK comes with a command line tool called simply “pebble” which also +allows you to leverage this protocol and install watch apps directly from a host +computer. When using the pebble tool, the tool is actually sending Pebble +protocol packets to your phone over a WebSocket and the Pebble app on the phone +then forwards the packets to the Pebble over Bluetooth. On the Pebble side, the +Bluetooth stack collects the packet data from the radio and passes it up to a +layer of code in Pebble OS that interprets the Pebble Protocol formatted packet +and processes it. + +Rather than try and emulate the Bluetooth radio in the emulator, we have instead +decided to replace the entire Bluetooth stack with custom code when building a +firmware image for QEMU. As long as this code can pass Pebble protocol packets +up, the rest of the firmware is none the wiser whether the data is coming from +the Bluetooth radio or not. We chose this approach after noticing that most +other emulators (for Android, iOS, etc.) have chosen not to try and emulate +Bluetooth using the radio on the host machine. Apparently, this is very +difficult to get right and fraught with problems. + +To support the emulator, we have also modified the pebble tool in the SDK to +have a command line option for talking to the emulator. When this mode is used, +the pebble tool sends the Pebble Protocol packets to a TCP socket connected to +the PQ channel socket of the emulator. Before sending the Pebble Protocol packet +to this socket, it is framed with a PQP header with a protocol ID that +identifies it as a Pebble protocol packet. + +## Accelerometer Data + +Data for the accelerometer on the Pebble is also sent through the PQ channel. A +unique PQP protocol ID is used to identify it as accelerometer data. The pebble +tool in the SDK includes a command for sending either a fixed reading or a +series of accelerometer readings from a file to the emulator. + +An accelerometer PQP packet includes a series of one or more accelerometer +reading. Each reading consists of three 16-bit integers specifying the amount of +force in each of the X, Y and Z-axes. + +On actual hardware, the accelerometer is accessed over the i2c bus in the +STM32F2XX. A series of i2c transactions are issued to set the accelerometer mode +and settings and when enough samples are ready in its FIFO, it interrupts the +CPU so that the CPU can issue more i2c transactions to read the samples out. + +The current state of the emulator does not yet have the STM32F2XX i2c peripheral +fully implemented and does not have any i2c devices, like the accelerometer, +emulated either. This is one area that could definitely be revisited in future +versions. For now, we have taken the approach of stubbing out device drivers +that use i2c when building the firmware for QEMU and plugging in custom device +drivers. + +Control gets transferred to the custom accelerometer device driver for QEMU +whenever a PQP packet arrives with accelerometer data in it. From there, the +driver extracts the samples from the packet and saves them to its internal +memory. Periodically, the driver sends an event to the system informing it that +accelerometer data is available. When the system gets around to processing that +event, it calls back into the driver to read the samples. + +When an application subscribes to the accelerometer, it expects to be woken up +periodically with the next set of accelerometer samples. For example, with the +default sampling rate of 25Hz and a sample size of 25, it should be woken up +once per second and sent 25 samples each time. The QEMU accelerometer driver +still maintains this regular wakeup and feed of samples, but if no new data has +been received from the PQ channel, it simply repeats the last sample - +simulating that the accelerometer reading has not changed. + +## Compass Data + +Data for the compass on the Pebble is also sent through the PQ channel with a +unique PQP protocol ID. The pebble tool in the SDK includes a command for +sending compass orientation (in degrees) and calibration status. + +On the Pebble side, we have a custom handler for PQP compass packets that simply +extracts the heading and calibration status from the packet and sends an event +to the system with this information – which is exactly how the real compass +driver hooks into the system. + +## Battery Level + +Data for the battery level on the Pebble is also sent through the PQ channel +with a unique PQP protocol ID. The pebble tool in the SDK includes a command for +setting the battery percent and the plugged-in status. + +On the Pebble side, we have a custom handler for PQP battery packets inside a +dedicated QEMU battery driver. When a PQP battery packet arrives, the driver +looks up the battery voltage corresponding to the requested percent and saves +it. This driver provides calls for fetching the voltage, percent and plugged-in +status. + +On real hardware, the battery voltage is read using the analog to digital +converter (ADC) peripheral in the STM32F2xx. For now, the emulator takes the +approach of stubbing out the entire battery driver but in the future, it might +be worth considering emulating the ADC peripheral better and then just feed the +PQP packet data into the emulated ADC peripheral. + +## Taps + +On the real hardware, the accelerometer has built-in tap detection logic that +runs even when the FIFO based sample-collecting mode is turned off. This tap +detection logic is used to support features like the tap to turn on the +backlight, etc. + +Data for the tap detection on the Pebble is also sent through the PQ channel +with a unique PQP protocol ID. The pebble tool in the SDK includes a command for +sending taps and giving the direction (+/-) and axis (X, Y, or Z). + +On the Pebble side, we have a custom handler for PQP tap packets that simply +sends an event to the system with the tap direction and axis – which is exactly +what the hardware tap detection driver normally does. + +## Button Presses + +Button presses can be sent to the emulated Pebble through two different +mechanisms. QEMU itself monitors key presses on the host system and we hook into +a QEMU keyboard callback method and assert the pins on the emulated STM32F2xx +that would be asserted in real hardware depending on what key is pressed. + +An alternate button pressing mechanism is exposed through the PQ channel and is +provided primarily for test automation purposes. But here, in contrast to how we +process things like accelerometer data, no special logic needs to be executed in +the firmware to handle these button PQP packets. Instead, they are processed +entirely on the QEMU side and it directly asserts the appropriate pin(s) on the +emulated STM32F2xx. + +A module in QEMU called “pebble_control” handles this logic. This module +essentially sits between the PQ channel serial device created by QEMU and the +UART that we use to send PQP packets to the emulated Pebble. It is always +monitoring the traffic on the PQ channel. For some incoming packets, like button +packets, the pebble_control module intercepts and handles them directly rather +than passing them onto the UART of the emulated Pebble. To register a button +state, it just needs to assert the correct pins on the STM32F2xx depending on +which buttons are pressed. The button PQP packet includes the state of each +button (pressed or not) so that the pebble_control module can accurately reflect +that state among all the button pins. + +## Device Emulation + +So far, we have mainly just talked about how one interacts with the emulator and +sends sensor data to it from the outside world. What exactly is QEMU doing to +emulate the hardware? + +## QEMU Devices + +QEMU is structured such that each emulated hardware device (CPU, UART, flash +ROM, timer, ADC, RTC, etc.) is mirrored by a separate QEMU device. Typically, +each QEMU device is compiled into its own module and the vast majority of these +modules have no external entry points other than one that registers the device +with a name and a pointer to an initialization method. + +There are far too many intricacies of QEMU to describe here, but suffice it to +say that the interface to each device in QEMU is standardized and generically +defined. You can map an address range to a device and also assign a device one +or more interrupt callbacks. Where a device would normally assert an interrupt +in the real hardware, it simply calls an interrupt callback in QEMU. When the +emulated CPU performs a memory access, QEMU looks up to see if that address maps +to any of the registered devices and dispatches control to a handler method for +that device with the intended address (and data if it is a write). + +The basic mode of communication between devices in QEMU is through a method +called `qemu_set_irq()`. Contrary to its name, this method is not only used for +generating interrupts. Rather, it is a general-purpose method that calls a +registered callback. One example of how this is used in the Pebble emulation is +that we provide a callback to the pin on the emulated STM32F2xx that gets +asserted when the vibration is turned on. The callback we provide is a pointer +to a method in the display device. This enables the display device to change how +it renders the Pebble window in QEMU based on the vibration control. The IRQ +callback mechanism is a powerful abstraction because an emulated device does not +need to know who or what it is connected to, it simply needs to call the +registered callback using `qemu_set_irq()`. + +## Pebble Structure + +We create multiple devices to emulate a Pebble. First of course there is the +CPU, which is an ARM v7m variant (already implemented in the base code of QEMU). + +The STM32F2XX is a microcontroller containing an ARM v7m CPU and a rather +extensive set of peripherals including an interrupt controller, memory +protection unit (MPU), power control, clock control, real time clock, DMA +controller, multiple UARTS and timers, I2C bus, SPI bus, etc. Each of these +peripherals is emulated in a separate QEMU device. Many of these peripherals in +the STM32F2xx behave similarly with those in the STM32F1xx series of +microcontroller implemented in Andre Beckus’ fork of QEMU and so could be +extended and enhanced where necessary from his implementations. + +Outside of the STM32F2xx, we have the Pebble specific devices including the +display, storage flash, and buttons and each of these is emulated as a separate +QEMU device as well. For the storage flash used in the Pebble, which sits on the +SPI bus, we could use a standard flash device already implemented in QEMU. The +display and button handling logic however are custom to the Pebble. + +## Device Specifics + +Getting the Pebble emulation up and running required adding some new QEMU +devices for peripherals in the STM32F2XX that are not in the STM32F1XX and +extending and enhancing some of the existing devices to emulate features that +were not yet fully implemented. + +To give a flavor for the changes that were required, here is a sampling: the DMA +device was more fully implemented to support 8 streams of DMA with their +associated IRQs; a bug in the NOR flash device was addressed where it was +allowing one to change 0’s to 1’s (an actual device can only change 1’s to 0’s +when programming); many of the devices were raising an assert for registers that +were not implemented yet and support for these registers needed to be added in; +the PWR device for the STM32F2xx was added in; wake up timer support was not +implemented in the RTC; many devices were not resetting all of their registers +correctly in response to a hardware reset, etc. + +## Real Time Clock + +One of the more challenging devices to emulate correctly was the real time clock +(RTC) on the STM32F2xx. Our first implementation, for example, suffered from the +problem that the emulated Pebble did not keep accurate time, quite a glaring +issue for a watch! + +The first attempt at emulating the RTC device relied on registering a QEMU timer +callback that would fire at a regular interval. The QEMU framework provides this +timer support and will attempt call your register callback after the requested +time elapses. In response to this callback, we would increment the seconds +register in the RTC by one, see if any alarm interrupts should fire, etc. + +The problem with this approach is that there is no guarantee that the QEMU timer +callback method will be called at the exact requested time and, depending on the +load on the host machine, we were falling behind sometimes multiple minutes in +an hour. + +To address this shortcoming, we modified the timer callback to instead fetch the +current host time, convert it to target time, and then modify the registers in +the RTC to match that newly computed target time. In case we are advancing the +target time by more than one in any specific timer callback, we also need to +check if any alarm interrupts were set to fire in that interval. We also update +the RTC time based on the host time (and process alarms) whenever a read request +comes in from the target for any of the time or calendar registers. + +Although conceptually simple, there were a number of details that needed to be +worked out for this approach. For one, we have to maintain an accurate mapping +from host time to target time. Anytime the target modifies the RTC registers, we +also have to update the mapping from host to target time. The other complication +is that the Pebble does not use the RTC in the “classic” sense. Rather than +incrementing the seconds register once per second, the Pebble OS actually runs +that RTC at 1024 increments per second. It does this so that it can use the RTC +to measure time to millisecond resolution. The emulation of the RTC thus needs +to also honor the prescaler register settings of the RTC to get the correct +ratio of host time to target time. + +A further complication arose in the Pebble firmware itself. When the Pebble OS +has nothing to do, it will put the CPU into stop mode to save power. Going into +stop mode turns off the clock and an RTC alarm is set to wake up the CPU after +the desired amount of time has elapsed. On real hardware, if we set the alarm to +fire in *N* milliseconds, we are guaranteed that when we wake up, the RTC will +read that it is now *N* milliseconds later. When running in emulation however, +quite often the emulation will fall slightly behind and by the time the emulated +target processes the alarm interrupt, the RTC registers will show that more than +*N* milliseconds have elapsed (especially since the RTC time on the target is +tied to the host time). Addressing this required modifying some of the glue +logic we have around FreeRTOS in order to fix up the tick count and wait time +related variables for this possibility. + +## UART Performance + +We rely heavily on the PQ channel to communicate with the emulated pebble. We +use it to send 3rd party apps to the emulated pebble from the host machine for +example and even for sending firmware updates to the emulated Pebble. + +The first implementation of the UART device though was giving us only about +1Kbyte/second throughput from the host machine to the emulated Pebble. It turns +out that the emulated UART device was telling QEMU to send it only 1 character +at a time from the TCP socket. Once it got that character, it would make it +available to the emulated target, and once the target read it out, it would tell +QEMU it had space for one more character from the socket, etc. + +To address this, the QEMU UART device implementation was modified to tell QEMU +to send it a batch of bytes from the socket at a time, then those bytes were +dispatched to the target one at a time as it requested them. This simple change +gave us about a 100x improvement in throughput. + +## CPU Emulation Issues + +Running the Pebble OS in QEMU ended up stressing some aspects of the ARM that +were not exercised as completely before, and exposed a few holes in the CPU +emulation logic that are interesting to note. + +The ARM processor has two operating modes (handler mode and thread mode) and two +different stack pointers it can use (MSP and PSP). In handler mode, it always +uses the MSP and in thread mode, it can use either one, depending on the setting +of a bit in one of the control registers. It turns out there was a bug in the +ARM emulation such that the control bit was being used to determine which stack +pointer to use even when in handler mode. The interesting thing about this is +that because of the way the Pebble OS runs, this bug would only show up when we +tried to launch a 3rd party app for the first time. + +Another, more subtle bug had to do with interrupt masking. The ARM has a BASEPRI +register, which can be used to mask all interrupts below a certain priority, +where the priority can be between 1 and 255. This feature was not implemented in +QEMU, so even when the Pebble OS was setting BASEPRI to mask off some of the +interrupts, that setting was being ignored and any interrupt could still fire. +This led to some intermittent and fairly hard to reproduce crashes in the +emulated Pebble whose source remained a mystery for quite a while. + +We discovered an issue that if the emulated CPU was executing a tight infinite +loop, that we could not connect using gdb. It turns out that although QEMU +creates multiple threads on the host machine, only one is allowed to effectively +run at a time. This makes coding in QEMU easier because you don’t have to worry +about thread concurrency issues, but it also resulted in the gdb thread not +getting a chance to run at all if the thread emulating the CPU had no reason to +exit (to read a register from a peripheral device for example). To address this, +we modified the CPU emulation logic to break out at least once every few hundred +instructions to see if any other thread had work to do. + +As briefly mentioned earlier, the ARM has a stop mode that is used for power +savings and the Pebble OS uses this mode extensively. Going into stop mode turns +off the clock on the CPU and most peripherals until an alarm interrupt fires. +There was a flaw in the initial implementation of stop mode in the emulator that +resulted in any and all interrupts being able to wake up the CPU. The emulated +Pebble ran just fine and the user would not notice any problems. However, QEMU +ended up using 100% of a CPU on the host machine at all times. When this bug was +addressed, the typical CPU load of QEMU went down to about 10%. Addressing this +bug was critical for us in order to efficiently host enough QEMU instances on a +server farm for use by CloudPebble. + +Another challenge in the CPU emulation was figuring out how to correctly +implement standby mode for the STM32F2xx. In standby mode, all peripherals are +powered off and the Pebble sets up the CPU to only wake up when the WKUP pin on +the STM32F2xx is asserted. This mode is entered when the user chooses “Shut +Down” from the Pebble settings menu or automatically when the battery gets +critically low. In most cases, wake up sources for the CPU (like the RTC +peripheral, UARTs, timers, etc.) generate an interrupt which first goes to the +interrupt controller (NVIC) which is outside the core CPU. The NVIC then figures +out the priority of the interrupt and only wakes the CPU if that priority of +interrupt is not masked off. The WKUP pin functionality is unique in that it is +not a normal interrupt, cannot be masked, and instead of resulting in the +execution of an interrupt service routine, it results in a reset of the CPU. +Figuring out how to hook this up correctly into QEMU required quite a bit of +learning and investigation into the core interrupt handling machinery in QEMU +and understanding difference between that and the WKUP functionality. + +## Window Dressing + +A few of the many fun things about working on the emulator had to do with +finding creative ways to represent things such as the brightness of the +backlight and the status of the vibration output. + +In the Pebble hardware, a timer peripheral is used to generate a PWM (Pulse +Width Modulated) output signal whose duty cycle controls the brightness of the +backlight. This allows the Pebble to gradually ramp up and down the brightness +of the backlight. In QEMU, the STM32F2xx timer device was enhanced to support +registering a QEMU IRQ handler that it would call whenever the PWM output value +was changed. In QEMU, when you call an interrupt handler callback using +`qemu_set_irq()`, you pass in an integer argument, allowing one to pass a scalar +value if need be. A “brightness” interrupt handler callback was added to the +Pebble display device and this callback was registered with the timer +peripheral. Once hooked up, this enabled the display to be informed whenever the +PWM output value changed. The display device implementation was then modified to +adjust the brightness (via the RGB color) of the pixels rendered to the display +based on the scalar value last sent to its brightness callback. + +Whenever the Pebble firmware decides to turn on the vibration, it asserts one of +the GPIO (General Purpose IO) pins on the STM32F2xx. To implement this feature, +a “vibration” callback was added to the display device implementation and this +callback was registered with the GPIO device pin that gets asserted by the +firmware in order to vibrate. To implement the vibrate, the display device +repeatedly re-renders the contents of the Pebble screen to the window offset +plus or minus 2 pixels, giving the illusion of a vibrating window. + +## Future Work + +The Pebble emulator has already proven to be a great productivity enhancer in +its current state, and has a lot of potential for future enhancements and +improvements. + +It would be interesting for example to investigate how well we can emulate I2C +and some of the I2C devices used in the Pebble. Doing this could allow us to use +more of the native device drivers in the firmware for things like the +accelerometer rather than having to plug in special QEMU based ones. + +Another area for investigation revolves around Bluetooth. Currently, we replace +the entire Bluetooth stack, but another option worth investigating is to run the +native Bluetooth stack as-is and just emulate the Bluetooth chip used on the +Pebble. This chip communicates over a serial bus to the STM32F2xx and accepts +low-level HCI (Host Controller Interface) commands. + +There is a lot of potential to improve the UI on the QEMU side. Currently, we +show only a bare window with the contents of the Pebble display. It would be a +great improvement for example to show a status area around this window with +clickable buttons. The status area could display helpful debugging information. + +There is a lot of potential for enhanced debugging and profiling tools. Imagine +a way to get a trace of the last *N* instructions that were executed before a +crash, or improved ways to profile execution and see where performance can be +improved or power savings realized. + +## More Information + +Continue reading [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1] +for more information on PebbleKit JS emulation and embedding the emulator in +CloudPebble. + + +[1]: /blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation/ +[2]: http://wiki.qemu.org/Main_Page +[3]: http://beckus.github.io/qemu_stm32/ +[4]: http://appdevelopermagazine.com/1313/2014/4/14/How-Pebble-Converted-135,070-Customized-Watchfaces-For-Pebble-OS-v2.0/ \ No newline at end of file diff --git a/devsite/source/_posts/2015-01-30-Pebble-Emulator-JavaScript-Simulation.md b/devsite/source/_posts/2015-01-30-Pebble-Emulator-JavaScript-Simulation.md new file mode 100644 index 00000000..e36ab120 --- /dev/null +++ b/devsite/source/_posts/2015-01-30-Pebble-Emulator-JavaScript-Simulation.md @@ -0,0 +1,433 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble Emulator 2/2 - JavaScript and CloudPebble +author: katharine +tags: +- Down the Rabbit Hole +date: 2015-01-30 +--- + +This is another in a series of technical articles provided by the members of the +Pebble software engineering team. This article describes some recent work done +at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short +for Quick EMUlator, is a generic, open source machine emulator and virtualizer). + + + + +> This post is part 2 of 2, and details the work undertaken to provide emulation +> of the PebbleKit JS and CloudPebble aspects of emulating a Pebble in its +> entirety. Read +> [*Pebble Emulator 1/2 - QEMU For Pebble*][9] for information on +> the creation of the Pebble Emulator. + +For developers of third-party apps it is insufficient to emulate just the Pebble +itself, as most non-trivial apps also run JavaScript on the phone to provide the +watchapp with additional information. Furthermore, some apps are written +entirely in JavaScript using Pebble.js; it is important to support those as +well. + +We therefore decided to implement support for running JavaScript in tandem with +the emulated Pebble. The JavaScript simulator is called [*pypkjs*][11]. + +## JavaScript Runtimes + +Since our JavaScript environment primarily consists of standard HTML5 APIs, we +initially tried building on top of [PhantomJS][1]. However, we quickly ran into +issues with the very old version of WebKit it uses and a lack of flexibility in +implementing the functionality we needed, so we abandoned this plan. + +Our second attempt was to use [node.js][2], but this proved impractical because +it was difficult to inject additional APIs into modules before loading them, by +which time they may have already tried to use them. Furthermore, some libraries +detected that they were running in node and behaved differently than they would +in the mobile apps; this discrepancy proved tricky to eliminate. + +We ultimately chose to build directly on top of +[Google’s V8 JavaScript Engine][3], making use of the [PyV8][4] Python bindings +to reduce the effort involved in writing our APIs. This gave us the flexibility +to provide exactly the set of APIs we wanted, without worrying about the +namespace already being polluted. PyV8 made it easy to define these without +worrying about the arcana of the C++ V8 interface. + +## Threading and Event Handling + +JavaScript is intrinsically single-threaded, and makes heavy use of events. In +order to provide event handling, we used [gevent][5] to provide support the key +support for our event loop using greenlets. The “main thread” of the interpreter +is then a single greenlet, which first evaluates the JavaScript file and then +enters a blocking event loop. The PebbleKit JS program is terminated when this +event loop ends. Any calls to asynchronous APIs will then spawn a new greenlet, +which will ultimately add a callback to the event queue to take effect on the +main thread. + +PebbleKit JS provides for single and repeating timers, which can take either a +function to call or a string to be evaluated when the timer expires. We +implemented these timers by spawning a new greenlet that sleeps for the given +period of time, then places either the function call or a call to eval on the +event queue. + +## HTML5 APIs + +Since we are using V8 without any additions (e.g. from Chromium), none of the +standard HTML5 APIs are present. This works in our favour: we only support a +restricted subset for PebbleKit JS. Our Android and iOS apps differ in their +support, so we chose to make the emulator support only the common subset of +officially supported APIs: XMLHttpRequest, localStorage, geolocation, timers, +logging and performance measurement. We implemented each of these in Python, +exposing them to the app via PyV8. We additionally support standard JavaScript +language features; these are provided for us by V8. + +## LocalStorage + +LocalStorage provides persistent storage to PebbleKit JS apps. It is a tricky +API to implement, as it has properties that are unlike most objects in +JavaScript. In particular, it can be accessed using either a series of method +calls (`getItem`, `setItem`, `removeItem`, etc.), as well as via direct property +access. In either case, the values set should be immediately converted to +strings and persisted. Furthermore, iterating over the object should iterate +over exactly those keys set on it, without any additional properties. Correctly +capturing all of these properties took three attempts. + +Our first approach was to directly provide a Python object to PyV8, which +implemented the python magic methods `__setattr__`, `__delattr__` and +`__getattr__` to catch attribute access and handle them appropriately. However, +this resulted in ugly code and made it impossible to correctly implement the +LocalStorage iteration behaviour as PyV8 does not translate non-standard python +iterators. + +The second approach was to create a native JavaScript object using +Object.create, set the functions on it such that they would not appear in +iteration, and use an ECMAScript 6 (“ES6”) Observer to watch for changes to the +object that should be handled. This approach failed on two fronts. The primary +issue was that we could not catch use of the delete operator on keys set using +property accessors, which would result in values not being removed. Secondly, +Observers are asynchronous. This made it impossible to implement the immediate +cast-to-string that LocalStorage performs. + +The final approach was to use an ES6 Proxy to intercept all calls to the object. +This enabled us to synchronously catc property accesses to cast and store them. +It also provided the ability to provide custom iteration behaviour. This +approach lead to a clean, workable and fully-compliant implementation. + +## Timers + +PebbleKit JS provides for single and repeating timers, which can take either a +function to call or a string to be evaluated when the timer expires. We +implemented these timers by spawning a new greenlet that sleeps for the given +period of time, then places either the function call or a call to eval on the +event queue. + +## Geolocation + +PebbleKit JS provides access to the phone’s geolocation facilities. According to +the documentation, applications must specify that they will use geolocation by +giving the ‘location’ capability in their manifest file. In practice, the mobile +apps have never enforced this restriction, and implementing this check turned +out to break many apps. As such, geolocation is always permitted in the +emulator, too. + +Since there is no geolocation capability readily available to the emulator, it +instead uses the MaxMind GeoIP database to look up the user’s approximate +location. In practice, this works reasonably well as long as the emulator is +actually running on the user’s computer. However, when the emulator is *not* +running on the user’s computer (e.g. when using CloudPebble), the result isn’t +very useful. + +## XMLHttpRequest + +Support for XMLHttpRequest is primarily implemented using the Python +[*requests* library][6]. Since requests only supports synchronous requests, and +XMLHttpRequest is primarily asynchronous, we spawn a new greenlet to process the +send request and fire the required callbacks. In synchronous mode we join that +greenlet before returning. In synchronous mode we must also place the *creation* +of the events on the event queue, as creating events requires interacting with +V8, which may cause errors while the main greenlet is blocked. + +## Pebble APIs + +PebbleKit JS provides an additional Pebble object for communicating with the +watch and handling Pebble accounts. Unlike the HTML5 APIs, these calls have no +specification, instead having two conflicting implementations and often vague or +inaccurate documentation that ignores the behaviour of edge cases. Testing real +apps with these, especially those that do not strictly conform to the +documentation, has required repeated revisions to the emulated implementation to +match what the real mobile apps do. For instance, it is not clear what should be +done when an app tries to send the float *NaN* as an integer. + +## Watch Communication + +The PebbleKit JS runtime creates a connection to a TCP socket exposed by QEMU +and connected to the qemu_serial device. Messages from PebbleKit JS are +exclusively Pebble Protocol messages sent to the bluetooth channel exposed over +the Pebble QEMU Protocol. A greenlet is spawned to read from this channel. + +The primary means of communication available to apps over this channel is +AppMessage, a mechanism for communicating dictionaries of key-value pairs to the +watch. These are constructed from the provided JavaScript object. It is possible +for applications to use string keys; these are replaced with integer keys from +the app’s manifest file before sending. If no such key can be found an exception +is thrown. This is the documented behaviour, but diverges from the implemented +behaviour; both mobile apps will silently discard the erroneous key. + +When messages are received, a new JavaScript object is created and the messages +parsed into JavaScript objects. Here we perform the reverse mapping, converting +received integers to string keys, if any matching keys are specified in the +app’s manifest. An event is then dispatched to the event loop on the main +greenlet. + +A method is also provided for showing a “simple notification”; again, it is not +clear what sort of notification this should be. This implementation sends an SMS +notification, which appears to be consistent with what the iOS app does. + +## Configuration Pages + +PebbleKit JS provides the option for developers to show a “configuration page” +in response to the user pressing a Settings button in the Pebble mobile app. +These configuration pages open a webview containing a user-specified webpage. +The mobile apps capture navigation to the special URL ‘pebblejs://close’, at +which point they dismiss the webview and pass the URL fragment to the PebbleKit +JS app. + +However, our emulator does not have the ability to present a webview and handle +the custom URL scheme, so another approach is required. We therefore pass a new +query parameter, ‘return_to’, to which we pass a URL that should be used in +place of the custom URL scheme. Configuration pages therefore must be modified +slightly: instead of using the fixed URL, they should use the value of the +‘return_to’ parameter, defaulting to the old pebblejs://close URL if it is +absent. + +When a URL is opened, the PebbleKit JS simulator starts a temporary webserver +and gives a URL for it in the ‘return_to’ parameter. When that page is loaded, +it terminates the webserver and passes the result to the PebbleKit JS app. + +## Exception Handling + +There are three potential sources of exceptions: errors in the user’s +JavaScript, error conditions for which we generate exceptions (e.g. invalid +AppMessage keys), and unintentional exceptions thrown inside our JavaScript +code. In all cases, we want to provide the user with useful messages and +JavaScript stack traces. + +PyV8 supports exceptions, and will translate exceptions between Python and +JavaScript: most exceptions from JavaScript will become JSErrors in Python, and +exceptions from Python will generally become Exceptions in JavaScript. JSErrors +have a stack trace attached, which can be used to report to the user. PyV8 also +has some explicit support for IndexErrors (RangeErrors in JavaScript), +ReferenceErrors, SyntaxErrors and TypeErrors. When such an exception passes the +Python/JavaScript boundary, it is converted to its matching type. + +This exception conversion support causes a complication: when a support +exception crosses from JavaScript to Python, it is turned into a standard Python +exception rather than a JSError, and so has no stack trace or other JavaScript +information attached. Since many exceptions become one of those (even when +thrown from inside JavaScript), and all exception handling occurs in Python, +many exceptions came through without any useful stack trace. + +To resolve this issue, we [forked PyV8][7] and changed its exception handling. +We now define new exceptions for each of the four supported classes that +multiple- inherit from their respective Python exceptions and JSError, and still +have JavaScript stack information attached. We can then catch these exceptions +and display exception information as appropriate. + +Due to the asynchronous nature of JavaScript, and the heavily greenlet-based +implementation of pypkjs, we must ensure that every point at which we call into +developer-provided JavaScript does something useful with any exceptions that may +be thrown so that JavaScript traces can be passed back to the developer. +Fortunately, the number of entry points is relatively small: the event system +and the initial evaluation are the key points to handle exceptions. + +## Sandboxing + +While PyV8 makes it very easy to just pass Python objects into JavaScript +programs and have them treated like standard JavaScript objects, this support is +an approximation. The resulting objects still feature all the standard Python +magic methods and properties, which the JavaScript program can access and call. +Furthermore, Python has no concept of private properties; any object state that +the Python code has access to can also be accessed by the JavaScript program. + +While this behaviour is good enough when working with JavaScript programs that +expect to be running in this environment, the programs that will be run here are +not expecting to run in this environment. Furthermore, they are untrusted; with +access to the runtime internals, they could potentially wreak havoc. + +In order to present a cleaner interface to the JavaScript programs, we instead +define JavaScript extensions that, inside a closure, feature a ‘native function’ +call. These objects then define proxy functions that call the equivalent +functions in the Python implementation. By doing this, we both present genuine +JavaScript objects that act like real objects in all ways, and prevent access to +the implementation details of the runtime. + +## Emulation in CloudPebble + +The majority of our developers use our web-based development environment, +CloudPebble. The QEMU Pebble emulator and PebbleKit JS simulator described above +are designed for desktop use, and so would not in themselves be useful to the +majority of our developers. Some arrangement therefore had to be made for those +developers. + +We decided to run a cluster of backend servers which would run QEMU on behalf of +CloudPebble’s users; CloudPebble would then interact with these servers to +handle input and display. + +## Displaying the Screen + +Displaying a remote framebuffer is a common problem; the most common solution to +this problem is VNC’s Remote Framebuffer Protocol. QEMU has a VNC server built- +in, making this the obvious choice to handle displaying the screen. + +CloudPebble’s VNC client is based on [noVNC][8], an HTML5 JavaScript-based VNC +client. Since JavaScript cannot create raw socket connections, noVNC instead +connects to the QEMU VNC server via VNC-over-websockets. QEMU already had +support for this protocol, but crashed on receiving a connection. We made a +minor change to initialisation to resolve this. + +While it would appear to make sense to use indexed colour instead of 24-bit true +colour for our 1-bit display, QEMU does not support this mode. In practice, +performance of the true colour display is entirely acceptable, so we did not +pursue this optimisation. + +## Communicating With the Emulator + +CloudPebble expects to be able to communicate with the watch to install apps, +retrieve logs, take screenshots, etc. With physical watches, this is done by +connecting to the phone and communicating over the Pebble WebSocket Protocol +(PWP). Due to restrictions on WebSocket connections within local networks, +CloudPebble actually connects to an authenticated WebSocket proxy which the +phone also connects to. In addition, this communication occurs over bluetooth — +but the PebbleKit JS runtime is already connected to the qemu_serial socket, +which can only support one client at a time. + +We therefore chose to implement PWP in the PebbleKit JS simulator. This neatly +the issue with multiple connections to the same socket, closely mimics how the +real phone apps behave, and minimises the scope of the changes required to +CloudPebble. CloudPebble’s use of a WebSocket proxy provides further opportunity +to imitate that layer as well, enabling us to take advantage of the existing +authentication mechanisms in CloudPebble. + +The PebbleKit JS simulator was thus split out to have two ‘runners’; one that +provides the standard terminal output (sendings logs to stdout and interacting +with the user’s local machine) and one that implements PWP. The WebSocket runner +additionally hooks into the low-level send and receive methods in order to +provide the message echoing functionality specified by PWP. Since the emulator +requires some communication that is not necessary with real phones, there are +were some extensions added that are used only by the emulator. However, for the +most part, existing code works exactly as before once pointed to the new +WebSocket URL. + +## Configuration Pages + +The mechanism previously designed for configuration pages is only usable when +running locally. To trigger a configuration page, CloudPebble sends a request +using an extension to the PWP. If the PebbleKit JS app implements a +configuration page, it receives a response giving it the developer’s intended +URL. CloudPebble then inserts a return_to parameter and opens a new window with +the developer’s page. Once the page navigates to the return URL, the page is +closed and the configuration data sent back over the WebSocket. + +Due to restrictions on how windows may communicate, CloudPebble must poll the +new window to discover if it has navigated to the return URL. Once the +navigation is detected, CloudPebble sends it a message and receives the +configuration data in reply, after which the window closes. + +A further complication is pop-up blockers. These usually only permit window +opens in response to direct user action. Since we had to request a URL from the +PebbleKit JS app before we could open the window, an active popup blocker will +tend to block the window. We worked around this by detecting the failure of the +window to open and providing a button to click, which will usually bypass the +popup blocker. + +## Input + +Originally, button input from CloudPebble was performed by sending keypresses +over VNC directly to QEMU. However, this turned out to cause issues with key- +repeat and long button presses. Resolving these issues proved to be difficult, +so we instead avoided sending VNC keypresses at all. Instead, another extension +was added to the PWP that permitted sending arbitrary PQP messages to the +emulator. We then sent packets indicating the state of each button to the +emulator via PWP. However, mouse clicks tend to be quicker than Pebble watch +button presses, and the time that the buttons appeared to be ‘pressed’ was too +short for the firmware to react. To avoid this problem, we rate limited +CloudPebble to send state changes no more rapidly than once every 100ms; more +rapid changes are queued to be sent later. + +The channel for sending PQP messages over PWP is also used to set the battery +state and toggle bluetooth; in the future, we will also use it to set the +accelerometer and compass readings. + +## Compass and Accelerometer Sensors + +Most computers do not have a compass or an accelerometer — and even if they did, +it would be impractical to pick up the computer and shake it, tilt it, or rotate +it to test apps. To deal with this, we took advantage of a piece of hardware +owned by all Pebble owners, and likely most Pebble developers: their phones. + +When developers want to use their phones to provide sensor data, a six-digit +code is generated and stored with the information required to connect to the +emulator. The user is prompted to open a short URL (cpbl.io) on their phone and +enter the code on that webpage. The code is looked up and, if found, a +connection to the emulator is established from the webpage on their phone. The +webpage then collects accelerometer and compass data using the +[HTML5 DeviceOrientation APIs][10] and streams it to the emulator. + +The generated codes expire a few minutes after being generated. + +## Emulator Management + +CloudPebble must manage multiple emulators on potentially multiple hosts. +Management is split between CloudPebble itself and a [controller program][12] +responsible for managing the lifecycle of individual emulator instances. + +CloudPebble is aware of a pool of emulator hosts. When a user requests an +emulator, it picks one at random and requests that its manager spin up an +emulator. If this is possible, it returns connection details for the emulator to +the client; if not (e.g. because that host has reached capacity), it picks +another host and tries again. If no hosts are available a failure message is +reported to the client and logged in our analytics system. + +The manager program selects some unused ports and spawns instances of QEMU and +pypkjs configured to work together, and reports back a UUID and public port +numbers for the VNC and Pebble WebSocket Protocol servers. The manager then +expects to be pinged for that emulator periodically; if too long passes without +being pinged or either QEMU or pypkjs fail, the QEMU and pypkjs instances will +be terminated. + +A complication arose when attempting to run this system over the Internet. Some +users, especially behind corporate firewalls, cannot make connections to the +non-standard ports that the manager was selecting. To avoid this issue, the +manager (which runs on the standard HTTPS port 443) can proxy connections to the +VNC and PWP websockets. + +Finally, in order to restrict abuse and provide continuity across client +reloads, CloudPebble tracks emulators assigned to users in a Redis database. If +a user who already has an emulator requests one and CloudPebble can ping their +emulator, they are given the same instance again. A new instance can be +requested by explicitly killing the emulator in the CloudPebble UI, or closing +the client and waiting for the manager to time out and kill the emulator. + + +[1]: http://phantomjs.org +[2]: http://nodejs.org +[3]: https://code.google.com/p/v8/ +[4]: https://code.google.com/p/pyv8/ +[5]: http://www.gevent.org +[6]: http://docs.python-requests.org/en/latest/ +[7]: https://github.com/pebble/pyv8 +[8]: https://github.com/kanaka/noVNC +[9]: /blog/2015/01/30/Development-Of-The-Pebble-Emulator/ +[10]: http://w3c.github.io/deviceorientation/spec-source-orientation.html +[11]: https://github.com/pebble/pypkjs +[12]: https://github.com/pebble/cloudpebble-qemu-controller \ No newline at end of file diff --git a/devsite/source/_posts/2015-02-13-Bezier-Curves-And-GPaths.md b/devsite/source/_posts/2015-02-13-Bezier-Curves-And-GPaths.md new file mode 100644 index 00000000..2f3e0c4a --- /dev/null +++ b/devsite/source/_posts/2015-02-13-Bezier-Curves-And-GPaths.md @@ -0,0 +1,239 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Bezier Curves and GPaths on Pebble +author: lukasz +tags: +- Beautiful Code +date: 2015-02-13 +summary: | + A look at how to use Bezier curves and GPaths to efficiently draw complex + paths in your Pebble apps and watchfaces. +banner: /images/blog/bezier-banner.png +--- + +Drawing complex paths requires a lot of manual work on Pebble. Here I'll show +you how to do this efficiently and quickly using a Pebble-optimized ``GPath`` +algorithm and Bezier curves. + + + + +## Why Bezier Curves? + +Typically, when a developer wants to create curve-based graphics on Pebble he +has to use prepared bitmaps or complex GPaths (or generate paths with +[svg2gpath](http://gpathsvg.org/)). Both of those approaches have serious +downsides: bitmaps require resource space to store, memory to process and +rotating bitmaps is a complex operation. Detailed GPaths, while fast in +processing, can take a long time to design without special tools. + +Bezier curves are a very interesting solution, which may seem computationally +excessive but solves the obstacles of both the previous methods. Because they're +so simple to use they are common elements in many graphic systems, primarly +vector based graphics. + +## Drawing Bezier Curves + +The mathematical formula for a Bezier curve can be found on +[Wikipedia](http://en.wikipedia.org/wiki/Composite_B%C3%A9zier_curve). We can +fairly easily turn it into C code, but we have to make a couple of important +decisions. The formula itself is linear and requires us to decide how many steps +we want to take in order to draw our Bezier curve. It's safe to assume that +calculating 1000 points on our Bezier curve should be enough to display a +continuous line on the small Pebbles screen. + +```c +void naive_bezier(GContext *ctx, GPoint points[]) { + for (double t = 0.0; t<1.0; t += 0.001) { + double tx = pow_d(1-t, 3) * points[0].x + 3 * t * pow_d(1-t, 2) * points[1].x + + 3 * pow_d(t, 2) * (1-t) * points[3].x + pow_d (t, 3) * points[2].x; + double ty = pow_d(1-t, 3) * points[0].y + 3 * t * pow_d(1-t, 2) * points[1].y + + 3 * pow_d(t, 2) * (1-t) * points[3].y + pow_d(t, 3) * points[2].y; + + graphics_draw_pixel(ctx, GPoint(tx, ty)); + } +} +``` + +**Notes:** + +* `pow_d(double a, int b)` is simply a function which returns `b` to the power + of `a`. + +* `points` contains array of 4 points used as parameters for drawing the Bezier + curve. + +While this approach gives us precise results its also very computationally +expensive. Most of the work done by the CPU is redundant as most of those points +overlap each other due to the low screen resolution. + +## Optimizing for Pebble + +The simplest optimization would be reducing the number of steps and drawing +lines between computed points. While this will significantly reduce calculation +time, it will also make the curve less precise and pleasing to the eye. But what +if we could dynamically calculate the number of segments needed to create +perfectly smooth curve? Here's a method published by Maxim Shermanev which you +can find out more about on his website: +[www.antigrain.com](http://www.antigrain.com/research/adaptive_bezier/index.html). + +Below you can see code from his research adapted to work efficiently on the +Pebble architecture: + +```c +void recursive_bezier_fixed(int x1, int y1, + int x2, int y2, + int x3, int y3, + int x4, int y4){ + // Calculate all the mid-points of the line segments + int x12 = (x1 + x2) / 2; + int y12 = (y1 + y2) / 2; + int x23 = (x2 + x3) / 2; + int y23 = (y2 + y3) / 2; + int x34 = (x3 + x4) / 2; + int y34 = (y3 + y4) / 2; + int x123 = (x12 + x23) / 2; + int y123 = (y12 + y23) / 2; + int x234 = (x23 + x34) / 2; + int y234 = (y23 + y34) / 2; + int x1234 = (x123 + x234) / 2; + int y1234 = (y123 + y234) / 2; + + // Angle Condition + int32_t a23 = atan2_lookup((y3 - y2) / fixedpoint_base, (x3 - x2) / fixedpoint_base); + int32_t da1 = abs(a23 - atan2_lookup((y2 - y1) / fixedpoint_base, (x2 - x1) / fixedpoint_base)); + int32_t da2 = abs(atan2_lookup((y4 - y3) / fixedpoint_base, (x4 - x3) / fixedpoint_base) - a23); + if(da1 >= TRIG_MAX_ANGLE) da1 = TRIG_MAX_ANGLE - da1; + if(da2 >= TRIG_MAX_ANGLE) da2 = TRIG_MAX_ANGLE - da2; + + if(da1 + da2 < m_angle_tolerance) + { + // Finally we can stop the recursion + add_point(x1234 / fixedpoint_base, y1234 / fixedpoint_base); + return; + } + + // Continue subdivision + recursive_bezier_fixed(x1, y1, x12, y12, x123, y123, x1234, y1234); + recursive_bezier_fixed(x1234, y1234, x234, y234, x34, y34, x4, y4); +} + +bool bezier_fixed(GPathBuilder *builder, GPoint p1, GPoint p2, GPoint p3, GPoint p4) { + // Translate points to fixedpoint realms + int32_t x1 = p1.x * fixedpoint_base; + int32_t x2 = p2.x * fixedpoint_base; + int32_t x3 = p3.x * fixedpoint_base; + int32_t x4 = p4.x * fixedpoint_base; + int32_t y1 = p1.y * fixedpoint_base; + int32_t y2 = p2.y * fixedpoint_base; + int32_t y3 = p3.y * fixedpoint_base; + int32_t y4 = p4.y * fixedpoint_base; + + if (recursive_bezier_fixed(builder, x1, y1, x2, y2, x3, y3, x4, y4)) { + return gpath_builder_line_to_point(builder, p4); + } + return false; +} +``` + +**Notes:** + +* This code uses fixedpoint integers since the Pebble CPU doesn't support + floating point operations. You can find more about that in a talk given by + Matthew Hungerford during 2014 Pebble Developer Retreat (video available + [here](https://www.youtube.com/watch?v=8tOhdUXcSkw)). + +* To determine the angle, the algorithm calculates parameters of the curve at + given points. This implementation is very effective since Pebble's + `atan2_lookup` is just looking up that value in a precomputed table. + +* `m_angle_tolerance` is the angle of the curve we're looking for, expressed in + degrees. In our case it's 10 degrees: `int32_t m_angle_tolerance = + (TRIG_MAX_ANGLE / 360) * 10;` + +## Applying Code to GPath + +In order to make it easy for developers, we have prepared the GPathBuilder +library which will ease the process of creating GPaths out of a few Bezier +curves and/or lines. The resulting path can already be manipulated with the +[existing APIs](/docs/c/group___path_drawing.html#ga1ba79344b9a34432a44af09bed8b00fd). You can find it on the +[pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier) along +with a simple demo app. + +Usage is extremely simple. Here are the main functions in the API: + + - `GPathBuilder* gpath_builder_create(uint32_t max_points)` will create the + GPathBuilder object you will need to use in order to create a GPath with + Bezier curves. `max_points` sets the limit on number of points created in the + process. + - `void gpath_builder_destroy(GPathBuilder *builder)` will destroy the + GPathBuilder object and free the memory it used. + - `bool gpath_builder_line_to_point(GPathBuilder *builder, GPoint to_point)` + will create a straight line from last point to the given point. + - `bool gpath_builder_curve_to_point(GPathBuilder *builder, GPoint to_point, + GPoint control_point_1, GPoint control_point_2)` will create a Bezier curve + from the last point to a given point (`to_point`), `control_point_1` and + `control_point_2` are used as parameters for the Bezier curve for last point + and given point respectively. + - `GPath* gpath_builder_create_path(GPathBuilder *builder)` will return a GPath + object ready to be used in your graphic routine. Remember that the + GPathBuilder is not being destroyed in the process and you have to do that + manually. + +Below is shown a simple shape involving two curves and the code required to +create the path: + +![result >{pebble-screenshot,pebble-screenshot--steel-black}](/images/blog/bezier-result.png) + +```c +// Create GPathBuilder object +GPathBuilder *builder = gpath_builder_create(MAX_POINTS); + +// Move to the starting point of the GPath +gpath_builder_move_to_point(builder, GPoint(0, -60)); +// Create curve +gpath_builder_curve_to_point(builder, GPoint(60, 0), GPoint(35, -60), GPoint(60, -35)); +// Create straight line +gpath_builder_line_to_point(builder, GPoint(-60, 0)); +// Create another curve +gpath_builder_curve_to_point(builder, GPoint(0, 60), GPoint(-60, 35), GPoint(-35, 60)); +// Create another straight line +gpath_builder_line_to_point(builder, GPoint(0, -60)); + +// Create GPath object out of our GPathBuilder object +s_path = gpath_builder_create_path(builder); +// Destroy GPathBuilder object +gpath_builder_destroy(builder); + +// Get window bounds +GRect bounds = layer_get_bounds(window_get_root_layer(window)); +// Move newly created GPath to the center of the screen +gpath_move_to(s_path, GPoint((int16_t)(bounds.size.w/2), (int16_t)(bounds.size.h/2))); +``` + +And there you have it, complex GPaths built with a few lines of code. + +## Conclusion + +This library should make it easier to create amazing graphics on Pebble and can +be used in animations as it's lightweight and fast. Bear in mind that the +GPathBuilder temporarily uses RAM proportinal to `max_points` until +`gpath_builder_destroy` is called. You can find the example app and library code +on the [pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier). + +We are also looking forward to this technology being used in online tools such +as [SVG to Pebble GPath Converter](http://gpathsvg.org/) by awesome Pebble +developer [Rajendra Serber](https://github.com/ardnejar). diff --git a/devsite/source/_posts/2015-03-20-Getting-Started-With-Timeline.md b/devsite/source/_posts/2015-03-20-Getting-Started-With-Timeline.md new file mode 100644 index 00000000..7c88be2d --- /dev/null +++ b/devsite/source/_posts/2015-03-20-Getting-Started-With-Timeline.md @@ -0,0 +1,290 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Getting Started With Timeline +author: kirby +tags: +- Timeline +date: 2015-03-20 +banner: /images/blog/getting-started-timeline.png +--- + +The new timeline interface is a completely new way to have users interact with +your app. The Pebble SDK along with timeline web APIs allows you to push pins to +your users' Pebbles. Adding pins to timeline is a straightforward process. This +guide will walk you through the steps from start to finish. + + + +## The Project + +We're going to build an app that lets users track their packages. We'll place pins +on the timeline for each package. For simplicity, we'll use the [Slice API](https://developer.slice.com/) +to get information about our packages. We'll go over the specifics of how to: + +- Use the [PebbleKit JS timeline APIs](/guides/pebble-timeline/timeline-js/) +- Setup a server that utilizes the [pebble-api](https://www.npmjs.com/package/pebble-api) +npm module +- Enable timeline for the app through the [Developer Portal](https://dev-portal.getpebble.com) + +## Setup + +Before we dive into the timeline specifics of this app we'll first need to build +a typical Pebble app. I won't spend much time on how to build a basic Pebble app; +there are plenty of [guides](/guides/) for that. + +In order to get setup with Slice follow their [Hello World example](https://developer.slice.com/docs/hello). +After you have your OAuth token it is easy to make a request to their +[shipments endpoint](https://developer.slice.com/docs/resources#res_shipments) +and get a JSON array of all your shipments. For example: + +```bash +$ curl -X GET --header 'Authorization: XXXXXXXXXXXXXXXXXXXXXXXXXX' \ + https://api.slice.com/api/v1/shipments + +# response +{ + "result": [ + { + "status": { + "code": 3, + "description": "DELIVERED" + }, + "updateTime": 1426785379000, + "shipper": {...}, + "description": "Dockers Pants, Tapered Fit Alpha Khaki Flat Front Dark Pebble 34x32", + "receivedDate": "2015-03-20", + "receivedShare": null, + "trackingUrl": "https://tools.usps.com/go/TrackConfirmAction.action?tLabels=9261299998829554536102", + "shares": [...], + "merchantTrackingUrl": null, + "emails": [...], + "href": "https://api.slice.com/api/v1/shipments/2689846985907082329", + "shippingDate": "2015-03-17", + "items": [...], + "shippingEstimate": {...}, + "trackingNumber": "9261299998829554536102", + "deliveryEstimate": { + "maxDate": "2015-03-20", + "minDate": "2015-03-20" + }, + "destinationAddress": {...}, + "history": [...] + }, ... + ] +} +``` + +Our app will be pretty simple. We'll use ``AppSync`` to load a ``MenuLayer`` with +a list of packages that we get from Slice. + +![package tracker app >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/package-tracker.png) + +Here is the source code to [timeline-package-tracker]({{site.links.examples_org}}/timeline-package-tracker/tree/7edde5caa0f6439d2a0ae9d30be183ace630a147) +without any timeline support. + +> Note that this is just an example. In a real app, you'd never hard code an +OAuth token into javascript, and you probably wouldn't arbitrarily limit your +app to have a 3 package limit. + +## PebbleKit JS Timeline APIs + +For our app we simply want to push each package as a pin to the timeline. Before +we can do this we need to get our user's timeline token. After we get the token +we'll need to send it to our own server. We'll also send our user's Slice oauth +token so that the server can keep up to date on all of their packages. From +there we can format a timeline pin with our package information and send it to +the user via their timeline token. + +```js +var API_ROOT = 'YOUR_TIMELINE_SERVER'; +var oauth = 'XXXXXXXXXXXXXXXXXXXX'; // in a real app we'd use a configuration window + +Pebble.addEventListener('ready', function() { + doTimeline(); +}); + +var doTimeline = function(packages) { + Pebble.getTimelineToken(function (token) { + sendToken(token, oauth); + }, function (error) { + console.log('Error getting timeline token: ' + error); + }); +}; + +var sendToken = function(token, oauth) { + var request = new XMLHttpRequest(); + request.open('GET', API_ROOT + '/senduserpin/' + token + '/' + oauth, true); // send the user's timeline token and Slice oauth token to our server + request.onload = function() { + console.log('senduserpin server response: ' + request.responseText); + }; + request.send(); +} +``` + +> Note that we're currently talking to a server that doesn't exist yet! We'll +cover how to set that up next. + +## Pebble Timeline Web APIs + +We'll need a server of our own to talk to the Pebble timeline web APIs. +[Express](https://github.com/strongloop/express/) is a convenient and easy way +to get a server up and running quickly. It also allows us to utilize the +[pebble-api](https://www.npmjs.com/package/pebble-api) npm module. + +A basic Express server look like this: + +```js +var express = require('express'); +var request = require('request'); + +var Timeline = require('pebble-api'); + +var app = express(); +app.set('port', (process.env.PORT || 5000)); + +var timeline = new Timeline(); + +app.get('/', function(req, res) { + res.send('Hello, world!'); +}); + +// start the webserver +var server = app.listen(app.get('port'), function () { + console.log('Package Tracker server listening on port %s', app.get('port')); +}); +``` + +We'll go ahead and include `request`, so that we can easily make requests to +Slice to get each user's package information. We'll also include the `pebble-api` +module too. + +Now that we have a basic server up all we need to do is handle each request to +our server, fetch packages, and send pins. The `pebble-api` module will +make it super easy to create our pins according to +[spec](/guides/pebble-timeline/pin-structure/). + +```js +var users = {}; // This is a cheap "in-memory" database ;) Use mongo db in real life! + +app.get('/senduserpin/:userToken/:oauth', function(req, res) { + var userToken = req.params.userToken; + var oauth = req.params.oauth; + + users[userToken] = {}; + users[userToken]['oauth'] = oauth; // store user + users[userToken]['packages'] = []; + + res.send('Success'); +}); + +// every 5 minutes check for new packages for all users +setInterval(function() { + Object.keys(users).forEach(function(user) { + var userToken = user; + var oauth = users[user]['oauth']; + getPackages(oauth, userToken); + }); +}, 300000); + +var getPackages = function(oauth, userToken) { + var options = { + url: 'https://api.slice.com/api/v1/shipments', + headers: { 'Authorization': oauth } + }; + + request(options, function(error, response, body) { + var response = JSON.parse(body); + var pkgs = getCurrentPackages(response.result); + pkgs.forEach(function(pkg) { + var found = false; + users[userToken]['packages'].forEach(function(oldPkg) { // check if pkg is new or not + if(oldPkg.id === pkg.id) { + found = true; + } + }); + if(!found) { + users[userToken]['packages'].push(pkg) // we have a new package, save it + sendPin(pkg, userToken); // and send it as a pin + } + }); + }); +}; + +// slice returns every package we've ever ordered. Let's just get the ones from the past week. +var getCurrentPackages = function(pkgs) { + current = []; + var oneWeekAgo = (new Date).getTime() - 604800000; + pkgs.forEach(function(pkg) { + if(pkg.updateTime > oneWeekAgo) { + current.push({'name': pkg.description, 'date': pkg.shippingEstimate.minDate, 'id': pkg.trackingNumber}); + } + }); + return current.slice(0, 3); +}; + +var sendPin = function(pkg, userToken) { + var pin = new Timeline.Pin({ + id: pkg.id, + time: new Date(Date.parse(pkg.date) + 43200000), // will always be noon the day of delivery + layout: new Timeline.Pin.Layout({ + type: Timeline.Pin.LayoutType.GENERIC_PIN, + tinyIcon: Timeline.Pin.Icon.MAIL, + title: pkg.name + }) + }); + + timeline.sendUserPin(userToken, pin, function (err, body, resp) { + if(err) { + return console.error(err); + } + }); +}; +``` + +This will accept GET requests from our Pebble app, and will then store each user +in memory. From there it checks every 5 minutes for new packages, and if they exist +creates a pin for them. Since the carrier only tells us the date the package will +be delivered, we arbitrarily set the time of the pin to noon. + +> Note in this current implementation users are stored in memory. A better +implementation would store packages in a database. + +## Enable Timeline in the Developer Portal + +We have almost everything setup, but our app won't work correctly with the +timeline web APIs until we upload the app's pbw to the Pebble Developer Portal +and enable timeline. + +In order to enable timeline, perform the following steps: + +0. Make sure you have a new and unique uuid. If you used the example from GitHub you will have to change it in your `appinfo.json`. To generate a new UUID, [you can use this tool](https://www.uuidgenerator.net/version4) +1. Login to the [Developer Portal](https://dev-portal.getpebble.com) +2. Click the Add Watchapp button on the portal (you don't need to upload any image assets) +3. Upload your pbw by adding a release (located in the build folder of your project) +4. Click the Enable Timeline button + +And then you're done! + +![package tracker pin >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/package-tracker-pin.png) + +Checkout the full source code on [Github]({{site.links.examples_org}}/timeline-package-tracker/). + +> **Further Reading on Pebble Timeline** +> +> For more information, we suggest you take a look at the [timeline guides](/guides/pebble-timeline/) +as well as the [hello-timeline]({{site.links.examples_org}}/hello-timeline) +and the [timeline-tv-tracker]({{site.links.examples_org}}/timeline-tv-tracker) +examples. They are all great resources to make the most of timeline! diff --git a/devsite/source/_posts/2015-04-03-The-Road-To-Pebble-SDK-3.0-In-Ten-Questions.md b/devsite/source/_posts/2015-04-03-The-Road-To-Pebble-SDK-3.0-In-Ten-Questions.md new file mode 100644 index 00000000..f25214c7 --- /dev/null +++ b/devsite/source/_posts/2015-04-03-The-Road-To-Pebble-SDK-3.0-In-Ten-Questions.md @@ -0,0 +1,265 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: The Road to Pebble SDK 3.0 in Ten Questions +author: thomas +tags: +- Freshly Baked +date: 2015-04-03 +banner: /images/blog/road30-banner.png +--- + +We launched the first version of our new SDK with support for Pebble Time two +days into our Kickstarter campaign. Since then, we have updated it every week, +releasing six iterations of our developer preview. Some of these versions +included major new features like support for color in the emulator and support +for the timeline. Some other features were more subtle, like the new +antialiased drawing mode and the updates to the animation system. All these +changes together, and a bunch more that you have not seen yet, will form the +SDK that you will use to build watchfaces, watchapps and timeline apps for all +models of Pebble watches in the coming months. + +These updates include lots of changes, and no matter how hard we try to +document everything there are a lot of unanswered questions. Some subjects have +just not been addressed by the developer previews yet, some need more +explaining and as always there are things we just missed in our communication +efforts. + +In this update, I want to answer publicly the most frequent questions received +from the community in the last few weeks. I will cover components that have +been released but also those that are still to come so you have the information +needed to plan ahead and prepare your apps for the Pebble Time launch. + + + +## What is the “timeline” shown in the Kickstarter video and how can I plug into it? + +The timeline is a system-provided app that allows the user to navigate through +important events in their near future and their past. + +![A glipse at the future (on the timeline) >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-timeline.gif) + +Apps have a very important role to play in the timeline. All Pebble apps (but +not companion apps that use our native PebbleSDK on Android and iOS) can push +“pins” to the timeline. From a developer perspective, pins are just a block of +JSON which describes the information to show to the user and what layout to use +to display the information (generic, calendar, sports or weather). Pins can +also include notifications that will be shown to the user when the pin is +created or updated on the watch, and reminders that can trigger at a specific +time. Finally pins can have actions attached to them. Actions can open a +Pebble app with a `uint32` parameter. + +You can experiment with pins in CloudPebble’s new “Timeline” tab or with the +command line SDK and the `pebble insert-pin` command. For documentation, refer +to our [“Understanding Timeline Pins”](/guides/pebble-timeline/pin-structure/) +guide. Once you have nailed down what you want your pins to look and feel like +you can start pushing them via a public HTTP API. This API requires that your +app is submitted on the Pebble appstore; that is also how you get your API key +to use the timeline API. We have built a [Node.js +library](https://github.com/pebble/pebble-api-node) to make this easier and +David Moreau has already shared a +[PHP library](https://github.com/dav-m85/pebble-api-php/). + +We also have a number of examples to help you get started: + + * A very simple [timeline hello + world](https://github.com/pebble-examples/hello-timeline) that shows how + to push a pin every time you press a button in your app + * A [TV show tracker](https://github.com/pebble-examples/timeline-tv-tracker/) + that announces your favorite tv show and lets users click on the pins to + increase the number of viewers in realtime. + +Get started now with our [Getting started with Timeline +post](/blog/2015/03/20/Getting-Started-With-Timeline/) or join us at the next +[Pebble SF developer meetup](http://www.meetup.com/PebbleSF/) on April 24th +where we will do a walkthrough of how to build a timeline app. If you are unable +to make it, you can also find this session on our [YouTube +channel](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ). + +As a side note, many people have asked us what will happen if their app does +not integrate with the timeline. The new Pebble system still includes a main +menu in which all the apps will appear and they can be still be launched from +there. + +## How can I build apps with transitions and animations like the ones shown in the Kickstarter videos? + +The user interface in the new OS is full of extremely rich animations, and of +course we are not limiting them to the system applications. We want all apps to +take advantage of them and to achieve that, we are investing a lot of effort. + +An essential part of all the animations is the core graphics framework used to +draw shapes, text and images. We have improved it a lot in recent weeks with +[much better support for animations](/guides/graphics-and-animations/animations), +[anti-aliasing and stroke width](/guides/graphics-and-animations/drawing-primitives-images-and-text/), +color bitmap handling and a lot of bugfixes and performance improvements. + +Upcoming releases of the SDK will add the new UI components that you saw in the +Kickstarter video: + + * A new MenuLayer with support for color, infinite scrolling (i.e. looping back + to the top), and new animations ![The new MenuLayer + >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-menulayer.gif) + * A new ActionBar (available this week in developer preview 6!) that is wider, + takes the full height of the screen, and includes some new animations. ![The + updated ActionBar + >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-actionbar.png) + * An ActionMenuWindow which is a full screen component to select an action in a + list. This is the same UI used to select an action on a pin or a + notification, and it is available to all apps. ![The new ActionMenuWindow + >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-actionmenu.gif) + * A StatusBarLayer that gives you much more control over the status bar, + including the ability to animate it and add goodies like a progress indicator + and a card counter for the card pattern. ![The new StatusBar + >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-statusbar.png) + * Cards are not really a UI Component but more of a standard pattern, seen in + the Weather app in the Kickstarter video and we expect that a lot more apps + will want to re-use it because it is an extremely efficient way to represent + information and looks very good! We will have a full example available to you + very soon showing how to build this type of app. ![A preview of the Weather + app and its animations + >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-card.gif) + +For more information on the design of apps in SDK 3.0, we are putting the +finishing touches on a completely new design guide. Stay tuned! + +## What is up with the icons on Pebble Time? Can I use these effects in my applications? + +A lot of the new design aesthetic is provided by icons, and those icons are not +your usual bitmap file. They animate during transitions to give a level of life +and personality never seen in any User Interface framework before. + +They are vector based animations and we came up with a complete new set of APIs +called Pebble Drawing Commands, as well as a new file format (.pdc) to +implement them. Compared to bitmap icons they can be automatically animated by +the system depending on the context: the same icon can explode on the screen, +regroup into a dot or fly through one of the screen edges; all of this in a +fraction of the space it would normally take to do such effects with animated +PNGs. + +We will be providing a large set of icons that you can use in your timeline +pins and in your apps. You will also be able to make your own animated icons to +play in your app. + + +## What is really happening when I compile an app today to be compatible with all Pebbles? + +With the current version of the Pebble SDK, we compile your apps once with SDK +2.9 for Aplite (Pebble and Pebble Steel) and once with SDK 3.0 for Basalt +(Pebble Time and Pebble Time Steel). This means that your apps can have a +different look on each platform. This is going to be even more true as we +introduce the new UI components in the next few releases. + +![Aplite differences](/images/blog/road30-aplite-differences.png) + +However, as soon as we provide official support for Aplite in SDK 3.0, we will +start compiling against the same set of APIs and the UI components will have +similar sizes, look and behavior. + +All the new software features of Pebble Time will be available on the original +Pebble, including the timeline, as well as the new UI components, PNG, Pebble +Drawing Commands, unlimited apps, AppFaces, etc. Of course features that are +hardware-dependent (such as the microphone) will not be supported on Aplite. + +We encourage everyone to maintain support for both Aplite and Basalt in their +source code. You can easily do it with `#ifdef` calls. If you just want to +freeze your Aplite app as it is today and focus on your Basalt app this will be +possible too with a new feature in the build process coming in the near future. + +## What are AppFaces and when can we use them? + +AppFaces are previews of your app that are displayed in the launcher before the +user starts your app. To use them, your app will need to support being launched +without a UI to update the AppFace. + +![AppFaces](/images/blog/road30-appfaces.png) + +AppFaces will not be part of Pebble SDK when we launch Pebble Time. Your apps +will just appear with their name until we add this API at a later stage. This +also means that your app icons defined in the appinfo.json are not used with +the new launcher (but they will still appear on Aplite until 3.0 ships for +Aplite). + +## When can we start interacting with smartstraps? + +We have released a lot of [mechanical and electrical information about +smartstraps](/smartstraps/) but no APIs yet. You will quickly find out that +without software support, the smartstrap does not get any power. + +We are working hard with our first smartstrap partners to finalize the +specifications for this API. If you want to be a part of this discussion, +[please make yourself known!](/contact) + +## How can I get my hands on Pebble Time? When will users get them? + +If you did not order one on Kickstarter you can register on [our +website]({{ site.links.pebble }}/pebble_time/) to be notified as soon as they are +available for normal orders. We will ship every Kickstarter pledge before we +start selling them on the Pebble website. If you ordered one through the +Kickstarter campaign, we are working hard to get it to you as soon as possible. + +As you know if you are a Kickstarter backer, Pebble Time starts shipping in May. + +As a developer, you already have access to the emulator. It includes everything +you need to work on your app. If you are missing anything, [contact us](/contact). +With only about a month left, there is no time to lose! + +## What about PebbleKit for iOS and Android? Any improvements? + +We decided to focus all our efforts on the new UI framework and the timeline. +There are no functional changes to PebbleKit in SDK 3.0. However, all Android +apps must be recompiled with the new PebbleKit Android library to be compatible +with Pebble Time. If you do not recompile, your app will not work with Pebble +Time. + +We have lots of plans for PebbleKit in the future and hope to share them +with you soon. + +If you have suggestions for APIs you’d like to see, please +[contact us](/contact) + +## Will developers have access to the microphone? What can we do with it? + +Absolutely. We will give developers access to our speech-to-text APIs. You will +be able to show a UI to start a recording and your app will receive the text +spoken by the user. This API is coming soon™ - not in 3.0. + +We are considering other APIs such as direct microphone access. Stay tuned for +more information on those. + +## Can we submit apps for Pebble Time to the Pebble appstore now? + +No, you should not. As long as you are using a developer preview SDK, the APIs +are non-final and apps built with the developer preview SDK may not work with +the final firmware and SDK shipped with Pebble Time. + +Once the APIs are stable, we will announce a final SDK and encourage everyone +to push applications built with the final SDK to the Pebble appstore. + +## Want more answers? + +For more questions and answers, such as the future of the `InverterLayer`, +information on floating point support or whether Pebble is really powered by +unicorns and rainbows, please take a look at this [/r/pebbledevelopers +thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/). + +Much thanks to all the developers who contributed questions to prepare this +blog post! + +If there is anything else you would like to know, [this +thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/) +is just [one]({{site.links.forums_developer}}) [of +the](https://twitter.com/pebbledev) [many +ways](/contact) [to get in touch](http://www.meetup.com/pro/pebble/) +with us and get answers! diff --git a/devsite/source/_posts/2015-05-13-tips-and-tricks-transparent-images.md b/devsite/source/_posts/2015-05-13-tips-and-tricks-transparent-images.md new file mode 100644 index 00000000..3387c5a7 --- /dev/null +++ b/devsite/source/_posts/2015-05-13-tips-and-tricks-transparent-images.md @@ -0,0 +1,107 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Tips and Tricks - Transparent Images +author: chrislewis +tags: + - Beautiful Code +--- + +Ever wondered how to draw transparent images in a Pebble app? This post will +walk you through the process. + +In this post, we'll be using a sample image with a transparency component, shown +below: + + + + + + + +When adding your project resource, ensure you set its ‘type’ correctly. On +CloudPebble, this is done when uploading the resource and choosing the 'PNG' +type. In the SDK, this is done with the `png` `type` in the project's +`appinfo.json`. + +```js +"media": [ + { + "type": "png", + "name": "GLOBE", + "file": "globe.png" + } +] +``` + +This will create a resource ID to use in code: + +```text +RESOURCE_ID_GLOBE +``` + +Simply create a ``GBitmap`` with this resource and draw the image with +``GCompOpSet`` as the compositing mode: + +```c +static GBitmap *s_bitmap; +static Layer *s_canvas_layer; +``` + +```c +static void window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + + // Create GBitmap + s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_GLOBE); + + // Create canvas Layer + s_canvas_layer = layer_create(layer_get_bounds(window_layer)); + layer_set_update_proc(s_canvas_layer, layer_update_proc); + layer_add_child(window_layer, s_canvas_layer); +} +``` + +```c +static void layer_update_proc(Layer *layer, GContext *ctx) { + // Draw the image with the correct compositing mode + graphics_context_set_compositing_mode(ctx, GCompOpSet); + graphics_draw_bitmap_in_rect(ctx, s_bitmap, gbitmap_get_bounds(s_bitmap)); +} +``` + +When drawing on a ``TextLayer`` underneath `s_canvas_layer`, the result looks +like this: + +![result-aplite >{pebble-screenshot,pebble-screenshot--steel-black}](/images/blog/tips-result-aplite.png) + + +See a full demo of this technique on the +[pebble-examples GitHub repo]({{site.links.examples_org}}/feature-image-transparent). + +Job done! Any transparent pixels in the original image will be drawn as clear, +leaving the color beneath unaffected. + +Read the [Image Resources](/guides/app-resources/) guide to learn more +about transparent PNGs. + + +## Conclusion + +So there you have it. Using these examples you can easily implement transparency +on all Pebble platforms. To learn more, read the ``GCompOp`` documentation or +the +[`pebble-examples/feature-image-transparent`]({{site.links.examples_org}}/feature-image-transparent) +SDK example. diff --git a/devsite/source/_posts/2015-05-19-tips-and-tricks-platform-specific-c-file-sets.md b/devsite/source/_posts/2015-05-19-tips-and-tricks-platform-specific-c-file-sets.md new file mode 100644 index 00000000..d3f3ae2f --- /dev/null +++ b/devsite/source/_posts/2015-05-19-tips-and-tricks-platform-specific-c-file-sets.md @@ -0,0 +1,226 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Tips and Tricks - Platform-specific C File Set +author: chrislewis +tags: + - Beautiful Code +--- + +In +[the last Tips and Tricks blog post](/blog/2015/05/13/tips-and-tricks-transparent-images/) +we looked at drawing transparent images on both the Aplite and Basalt platforms. + +This time around, we will look at a `wscript` modification that can allow you to +build a Pebble project when you have two completely separate sets of C source +files; one set for Aplite, and another for Basalt. + +Note: This technique can be applied only to local SDK projects, where access to +`wscript` is available. This means that it cannot be used in CloudPebble +projects. + + + +> Update 05/20/15: There was an error in the JS code sample given at the end of +> this post, which has now been corrected. + +## The Preprocessor Approach + +In the [3.0 Migration Guide](/sdk/migration-guide/#backwards-compatibility) we +recommend using preprocessor directives such as `PBL_PLATFORM_APLITE` and +`PBL_PLATFORM_BASALT` to mark code to be compiled only on that particular +platform. This helps avoid the need to maintain two separate projects for one +app, which is especially convenient when migrating a 2.x app to the Basalt +platform. + +```c +#ifdef PBL_PLATFORM_APLITE + // Aligns under the status bar + layer_set_frame(s_layer, GRect(0, 0, 144, 68)); +#elif PBL_PLATFORM_BASALT + // Preserve alignment to status bar on Aplite + layer_set_frame(s_layer, GRect(0, STATUS_BAR_LAYER_HEIGHT, 144, 68)); +#endif +``` + +This is a good solution for small blocks of conditional code, but some +developers may find that complicated conditional code can soon become more +`#ifdef...#elif...#endif` than actual code itself! + + +## The Modified Wscript Approach + +In these situations you may find it preferable to use a different approach. +Instead of modifying your app code to use preprocessor statements whenever a +platform-specific value is needed, you can modify your project's `wscript` file +to limit each compilation pass to a certain folder of source files. + +By default, you will probably have a project with this file structure: + +```text +my_project + resources + images + banner~bw.png + banner~color.png + src + main.c + util.h + util.c + appinfo.json + wscript +``` + +In this scenario, the `wscript` dictates that *any* `.c` files found in `src` +will be compiled for both platforms. + +To use a different set of source files for each platform during compilation, +modify the lines with the `**` wildcard (within the `for` loop) to point to a +folder within `src` where the platform- specific files are then located: + +```python +for p in ctx.env.TARGET_PLATFORMS: + ctx.set_env(ctx.all_envs[p]) + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + + # MODIFY THIS LINE! + # E.g.: When 'p' == 'aplite', look in 'src/aplite/' + ctx.pbl_program(source=ctx.path.ant_glob('src/{}/**/*.c'.format(p)), target=app_elf) + + if build_worker: + worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) + + # MODIFY THIS LINE! + # Also modify this line to look for platform-specific C files in `worker_src` + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/{}/**/*.c'.format(p)), target=worker_elf) + + else: + binaries.append({'platform': p, 'app_elf': app_elf}) +``` + +With this newly modified `wscript`, we must re-organise our `src` folder to +match the new search pattern. This allows us to maintain two separate sets of +source files, each free of any excessive `#ifdef` pollution. + +```text +my_project + resources + images + banner~bw.png + banner~color.png + src + aplite + main.c + util.h + util.c + basalt + main.c + util.h + util.c + appinfo.json + wscript +``` + + +## Sharing Files Between Platforms + +Using the modified wscript approach as shown above still requires any files that +are used on both platforms to be included twice: in the respective folder. You +may wish to reduce this clutter by moving any platform-agnostic files that both +platforms use to a `common` folder inside `src`. + +You project might now look like this: + +```text +my_project + resources + images + banner~bw.png + banner~color.png + src + aplite + main.c + basalt + main.c + common + util.h + util.c + appinfo.json + wscript +``` + +To tell the SDK to look in this extra folder during compilation of each +platform, further modify the two lines calling `ctx.pbl_program()` to include +the `common` folder in the array of paths passed to +[`ant_glob()`](https://waf.io/book/#_general_usage). This is shown in the code +snipped below, with unchanged lines ommitted for brevity: + +```python +# Additionally modified to include the 'common' folder +ctx.pbl_program(source=ctx.path.ant_glob(['src/{}/**/*.c'.format(p), 'src/common/**/*.c']), target=app_elf) + +/* Other code */ + +if build_worker: + + # Also modify this line to look for common files in '/worker_src/common/' + ctx.pbl_worker(source=ctx.path.ant_glob(['worker_src/{}/**/*.c'.format(p), 'worker_src/common/**/*.c']), target=worker_elf) + +else: + + /* Other code */ + +``` + + +## Important Notes + +While this new `wscript` allows us to keep our source files for each platform +separated entirely, there are a couple of important limitations to take into +account when using this method (without any further modification): + +* There can still be only *one* JavaScript file, in `src/js/pebble-js-app.js`. + You can simulate two JS files using a platform check: + +```js +Pebble.addEventListener('ready', function() { + if(Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo().platform === 'basalt') { + // This is the Basalt platform + console.log('PebbleKit JS ready on Basalt!'); + } else { + // This is the Aplite platform + console.log('PebbleKit JS ready on Aplite!'); + } +}); +``` + +* Each binary can be bundled with only the app resources required for that + specific platform. To learn how to package app resources with only a certain + platform, read the + [*Platform-specific Resources*](/guides/app-resources/platform-specific/) + guide. + + +## Conclusion + +With this modification to a Pebble project's `wscript`, developers now have two +options when it comes to diverging their app code for Aplite- and Basalt- +specific features **without** the need to maintain two completely separate +projects. + +You can see a simple example project that ses all these techniques over at +[`pebble-examples/multi-platform-wscript`]({{site.links.examples_org}}/multi-platform-wscript). diff --git a/devsite/source/_posts/2015-10-29-ios-migration.md b/devsite/source/_posts/2015-10-29-ios-migration.md new file mode 100644 index 00000000..2efb5cc6 --- /dev/null +++ b/devsite/source/_posts/2015-10-29-ios-migration.md @@ -0,0 +1,99 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Migrating to Pebblekit iOS 3.0 +author: alex +tags: +- Freshly Baked +--- + +Starting with Pebble Time Round, we are moving towards communicating with Bluetooth +Low-Energy only. This means there are some updates to PebbleKit iOS to support this. Here are +the major changes and steps to take to support the new BLE connection. + +**What's New** + +* Companion apps have dedicated, persistent communication channels +* Start mobile apps from Pebble +* 8K AppMessage buffers +* Swift support + + +## What do you need to do? +Import the new [PebbleKit iOS 3.0](/guides/migration/pebblekit-ios-3/#how-to-upgrade) library into your project. + +### API Changes + +#### NSUUIDs + +You can now use `NSUUID` objects directly rather than passing ``appUUID`` as a `NSData` object. + +**PebbleKit 2.x** + +```c +uuid_t myAppUUIDbytes; +NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"]; +[myAppUUID getUUIDBytes:myAppUUIDbytes]; +[PBPebbleCentral defaultCentral].appUUID = [NSData dataWithBytes:myAppUUIDbytes length:16]; +``` + +**PebbleKit 3.0** + +```c +NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"]; +[PBPebbleCentral defaultCentral].appUUID = myAppUUID; +``` + +#### Cold start PBPebbleCentral + +You'll want to start ``PBPebbleCentral`` in a cold state now so users don't get +a pop-up asking for Bluetooth permissions as soon as the app initializes. Call +`[central run]` when it makes sense for the pop-up to show up. Add some custom +text to the dialog with `NSBluetoothPeripheralUsageDescription` to your +`Info.plist` file. + +**PebbleKit 2.x** + + ```c +// MyAppDelegate.m - Set up PBPebbleCentral and run if the user has already +// performed onboarding +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [PBPebbleCentral defaultCentral].delegate = self; + [PBPebbleCentral defaultCentral].appUUID = myAppUUID; + if ([MySettings sharedSettings].userDidPerformOnboarding) { + [[PBPebbleCentral defaultCentral] run]; + } +} +``` + +**PebbleKit 3.0** + +```c +// MyOnboarding.m - Once the pop-up has been accepted, begin PBPebbleCentral +- (IBAction)didTapGrantBluetoothPermissionButton:(id)sender { + [MySettings sharedSettings].userDidPerformOnboarding = YES; + [[PBPebbleCentral defaultCentral] run]; // will trigger pop-up +} +``` + +### Specify that your app is built with PebbleKit 3.0 in the Developer Portal +Go to edit the companion app listing in your [developer portal](https://dev-portal.getpebble.com/developer) page and check the box for "Was this iOS app compiled with PebbleKit iOS 3.0 or newer?" This way, users on Pebble Time Round will be able to see your app in the appstore. + +![](/images/blog/checkbox.png) + +### Final thoughts + +With a few quick steps, you can bring compatability for the BLE connection to your app. In the coming months, we'll be rolling out updates for users of Pebble and Pebble Time to take advantage of BLE-only connection as well. In the short term, if you intend to support Pebble Time Round, these steps are mandatory. For the complete details on migrating to PebbleKit 3.0, take a look at our [migration guide](/guides/migration/pebblekit-ios-3/). If you have any issues in migrating or have any questions concerning PebbleKit 3.0, feel free to [contact](/contact/) us anytime! + diff --git a/devsite/source/_posts/2015-11-10-Nuance-Brings-Pebble-The-Freedom-Of-Speech.md b/devsite/source/_posts/2015-11-10-Nuance-Brings-Pebble-The-Freedom-Of-Speech.md new file mode 100644 index 00000000..ce59f640 --- /dev/null +++ b/devsite/source/_posts/2015-11-10-Nuance-Brings-Pebble-The-Freedom-Of-Speech.md @@ -0,0 +1,192 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Nuance Brings Pebble The Freedom Of Speech +author: jonb +tags: +- Freshly Baked +--- + +In October, we gave developers access to the microphone in the Pebble Time via +our new +[Dictation API](``Dictation``), +and almost instantly we began seeing awesome projects utilising speech input. +Voice recognition is an exciting new method of interaction for Pebble and it has +created an opportunity for developers to enhance their existing applications, or +create highly engaging new applications designed around the spoken word. + +Speech-to-text has been integrated with Pebble by using the +[Recognizer](http://www.nuance.com/for-business/automatic-speech-recognition/automated-ivr/index.htm) +cloud service from Nuance, a leading provider of voice and language solutions. + + +## The Pebble Dictation Process + +The Dictation API has been made incredibly easy for developers to integrate into +their watchapps. It’s also intuitive and simple for users to interact with. +Here’s an overview of the the dictation process: + +1. The user begins by pressing a designated button or by triggering an event + within the watchapp to indicate they want to start dictating. The watchapp’s + UI should make this obvious. + +2. The watchapp + [initiates a dictation session](/docs/c/Foundation/Dictation/#dictation_session_start), + assigning a + [callback function](/docs/c/Foundation/Dictation/#DictationSessionStatusCallback) + to handle the response from the system. This response will be either + successful and return the dictated string, or fail with an error code. + +3. The system Dictation UI appears and guides the user through recording their + voice. The stages in the image below illustrate: + + a. The system prepares its buffers and checks connectivity with the cloud + service. + + b. The system begins listening for speech and automatically stops listening + when the user finishes talking. + + c. The audio is compressed and sent to the cloud service via the mobile + application. + + d. The audio is transcribed by the cloud service and the transcribed text is + returned and displayed for the user to accept or reject (this behaviour + can be + [programmatically overridden](/docs/c/Foundation/Dictation/#dictation_session_enable_confirmation)). + +4. Once the process has completed, the registered callback method is fired and + the watchapp can deal with the response. + +![dictation-flow](/images/blog/dictation-flow.png) + + +## But How Does It Actually Work? + +Let’s take a closer look at what’s happening behind the scenes to see what’s +really going on. + +![dictation-recognizer](/images/blog/dictation-recognizer.png) + +1. To capture audio, Pebble Time (including Time Steel and Time Round) has a + single [MEMS](https://en.wikipedia.org/wiki/Microelectromechanical_systems) + microphone. This device produces output at 1 MHz in a + [PDM](https://en.wikipedia.org/wiki/Pulse-density_modulation) format. + +2. This 1 bit PDM signal needs to be converted into 16-bit + [PCM](https://en.wikipedia.org/wiki/Pulse-code_modulation) data at 16 kHz + before it can be compressed. + +3. Compression is performed using the [Speex](http://www.speex.org/) encoder, + which was specifically designed for speech compression. Compression needs to + occur in order to reduce the overall size of the data before it’s transferred + via bluetooth to the mobile application. Speex also has some additional + advantages like tuneable quality/compression and recovery from dropped + frames. + +4. The mobile application sends the compressed data to Nuance Recognizer, along + with some additional information like the user’s selected language. + +5. Nuance performs its magic and returns the textual representation of the + spoken phrase to the mobile application, which is then automatically passed + back to the watchapp. + +6. The Dictation UI presents the transcribed text back to the user where they + can choose to accept or reject it. + + +## About the Dictation API + +Behind the scenes there’s a lot going on, but let’s take a look at how minimal +the code needs to be in order to use the API. + +1. Create a static variable as a reference to the dictation session: + + ```c + static DictationSession *s_dictation_session; + ``` + +2. Create a callback function to receive the dictation response: + + ```c + static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { + if(status == DictationSessionStatusSuccess) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Transcription:\n\n%s", transcription); + } else { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Transcription failed.\n\nError ID:\n%d", (int)status); + } + } + ``` + +3. Within a button click or other event, create a ``DictationSession`` to begin + the process: + + ```c + s_dictation_session = dictation_session_create(512, dictation_session_callback, NULL); + ``` + +4. Before your app exits, don’t forget to destroy the session: + + ```c + dictation_session_destroy(s_dictation_session); + ``` + + +## Voice-enabled Watchapps + +Here’s a small sample of some of the watchapps which are already available in +the Pebble appstore which utilise the Dictation API. + +* [Voice2Timeline](http://apps.getpebble.com/en_US/application/561f9188bcb7ac903a00005b) + is a handy tool for quickly creating pins on your timeline by using your + voice. It already works in 6 different languages. You can leave notes in the + past, or even create reminders for the future (e.g. “Don’t forget the milk in + 1 hour”). + +* [Translate (Vox Populi)](http://apps.getpebble.com/en_US/application/561ff3cbbcb7aca6250000a3) + allows a user to translate short phrases and words into a different language. + It uses the [Yandex](https://translate.yandex.com/) machine translator API + which supports more than 60 different languages. + +* [Checklist](http://apps.getpebble.com/en_US/application/5620e876768e7ada4e00007a) + is a really simple tool which generates a list of items using your voice. It + even allows you to enter multiple items at once, by specifying a comma or + period. You can easily mark them as completed by pressing the ‘select’ button + on each item. + +* [Smartwatch Pro](https://itunes.apple.com/gb/app/smartwatch-pro-for-pebble/id673907094?mt=8) + (iOS) has been updated to give users voice controlled music playback, create + reminders and even create tweets by using their voice. + + +## Final Thoughts + +Why not also checkout this video of Andrew Stapleton (Embedded Developer) as he +deep dives into the internals of the Pebble Dictation API during his presentation +at the [Pebble Developer Retreat 2015](/community/events/developer-retreat-2015/). + +[EMBED](www.youtube.com/embed/D-8Ng24RXwo) + +We hope you’ve seen how flexible and easy it is to use the new Dictation API, +and perhaps it will inspire you to integrate voice into your own application or +watchface - if you create a voice enabled watchapp, let us know by tweeting +[@pebbledev](https://twitter.com/pebbledev). + +If you’re looking to find out more about voice integration, checkout our +[developer guide](/guides/events-and-services/dictation/), +[API documentation](``Dictation``) +and our +[simple example app](https://github.com/pebble-examples/simple-voice-demo). We +also have a very friendly and helpful Pebble community on Discord; why not +[join us]({{ site.links.discord_invite }})? diff --git a/devsite/source/_posts/2015-12-01-A-New-Pebble-Tool.md b/devsite/source/_posts/2015-12-01-A-New-Pebble-Tool.md new file mode 100644 index 00000000..cfc71d13 --- /dev/null +++ b/devsite/source/_posts/2015-12-01-A-New-Pebble-Tool.md @@ -0,0 +1,117 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Introducing Pebble Tool 4.0 +author: katharine +tags: +- Freshly Baked +--- + +I am pleased to today announce that version 4.0-rc4 of the `pebble` tool is now +available. The key new feature is a new paradigm for dealing with firmware and +SDK versions. This makes it much easier to deal with differing SDK versions, or +to test code on multiple (emulated) firmware versions. + +_A note: while the tool is now at version 4.0, the SDK, firmware and mobile apps +will not be following. Pebble tool versioning is now completely independent of +the rest of the Pebble ecosystem._ + + + + +Managing SDKs +------------ + +The pebble tool now manages SDKs for you, without you needing to download and +install the entire SDK manually each time. The first time you need an SDK, the +latest one will be automatically installed for you. After that, you can use the +SDK operations that live under the `pebble sdk` subcommand. + +To see a list of available SDKs, use `pebble sdk list`: + +```nc|text +katharine@kbrmbp ~> pebble sdk list +Installed SDKs: +3.7 (active) + +Available SDKs: +3.6.2 +3.4 +3.3 +3.2.1 +3.1 +3.0 +2.9 +``` + +You can install any SDK using `pebble sdk install`, like so: + +```nc|text +katharine@kbrmbp ~> pebble sdk install 3.6.2 +Installing SDK... +Do you accept the Pebble Terms of Use and the Pebble Developer License? (y/n) y +Downloading... +100%[======================================================] 1.40 MB/s 0:00:01 +Extracting... +Preparing virtualenv... (this may take a while) +Installing dependencies... +Done. +Installed. +``` + +You can switch between active SDKs using `pebble sdk activate `, like +`pebble sdk activate 3.7`. Once you activate an SDK, it will be used for all +`build` and `install` commands. + +Switching on the fly +-------------------- + +A number of commands now take an optional `--sdk` flag, which will override the +current active SDK. This enables you to easily run one command with a different +SDK version — for instance, compiling with 3.6.2 and then running on 3.7: + +```nc|text +katharine@kbrmbp ~> pebble build --sdk 3.6.2 +# ... +katharine@kbrmbp ~> pebble install --emulator basalt --sdk 3.7 +# ... +``` + +This is supported by `pebble build` as well as any command that supports +`--emulator`. Additionally, you can now run emulators for multiple SDKs +simultaneously by passing different values for `--sdk`. + +Benefits +-------- + +Beyond the obvious benefit of easier SDK management, the new system also +produces much smaller SDKs. Each SDK used to be a 38 MB download, which +decompressed to 143 MB, plus another hundred megabytes for the toolchain. +Most of this is now downloaded only once, as part of the initial pebble tool +setup. After that, each SDK is only a 2 MB download, which expands to 4 MB on +disk. + +The new pebble tool can also alert you to new SDKs as they become available, +enabling you to install them with a single command. + +Try it out! +----------- + +To try out our new pebble tool, read the instructions on the [SDK +Beta](/sdk/beta) page. + +Please [contact us](/contact/) if you run into any issues installing or using +`pebble` v4.0, or if you have any feedback. You can also frequently find me +on ~~Slack~~ Discord — [join us]({{ site.links.discord_invite }})! diff --git a/devsite/source/_posts/2015-12-02-Bitmap-Resources.md b/devsite/source/_posts/2015-12-02-Bitmap-Resources.md new file mode 100644 index 00000000..3c0a53e2 --- /dev/null +++ b/devsite/source/_posts/2015-12-02-Bitmap-Resources.md @@ -0,0 +1,163 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Unifying bitmap resources +author: katharine +tags: +- Freshly Baked +--- + +With the upcoming release of firmware 3.8 on Pebble and Pebble Steel, and the +associated SDK 3.8, we have decided to redesign how image resources work in +Pebble apps. + + + + +Why change? +----------- + +First, some history: when SDK 1 was originally released, and continuing up to +SDK 2.9, there was a single image type, `png`. A `png` resource would take a PNG +as input and spit out a custom, uncompressed, 1-bit-per-pixel image format we +called "pbi". This was the only bitmap format Pebble supported, and life was +simple. + +With the release of SDK 3.0, we added firmware support for a new image format: +PNG (with some restrictions). This enabled Pebble to directly read compressed +images, and those images could be 1-bit, 2-bit, 4-bit or 8-bit palettised. The +existing `png` resource type was changed to produce these images instead of the +old PBI format, and everyone had smaller image resources. + +Unfortunately, "png" isn't the best option for all cases. The old 1-bit format +supported some [compositing operations](``GCompOp``) that other image formats +do not support. We added the `pbi` format to achieve this legacy behavior. +Additionally, PNG decompression isn't free: loading a PNG resource requires +enough memory to hold the compressed image, the uncompressed image, and some +scratch space. This was often offset by the benefits of palettised images with +fewer bits per pixel, but sometimes it didn't fit. We added `pbi8`, which +produced 8-bit-per-pixel PBI images. This still left it impossible to generate +a palettized PBI, even though the format does exist. + +As a further complication, since SDK 1 and continuing through the present day, +`pbi` (and `pbi8`) images have cropped transparent borders around the outside. +However, `png` images (as of SDK 3) do _not_ crop like this. The cropping +behavior was originally a bug, and is generally undesirable, but must be +maintained for backwards compatibility. + +There is one additional exception to all of this: until SDK 3.8, the Aplite platform still +interprets `png` to mean `pbi`. It also interprets `pbi8` to mean `pbi`. When +we built the 3.8 SDK, we changed `png` to really mean `png` on Aplite. +Unfortunately, the more limited memory of the Aplite platform meant that these +PNGs sometimes did not have enough space to decompress. The only workaround was +to duplicate resources and use `targetPlatforms` to specify a `pbi` resource for +Aplite and a `png` resource for Basalt and Chalk. + +The easiest answer was to keep `png` as an alias for `pbi` on Aplite—but then +there's no way of generating a real PNG for Aplite. Furthermore, the `png`, +`pbi` and `pbi8` trio was getting confusing, so we decided to do something else. + +"bitmap" to the rescue +---------------------- + +As of SDK 3.8, `png`, `pbi` and `pbi8` **are all deprecated**. We are instead +introducing a new resource type, `bitmap`. This new resource type unifies all +the existing types, allows the SDK to use its best judgement, increases the +flexibility available to developers, and takes the guesswork out of advanced +image manipulation. It also removes the generally undesirable forced cropping +behavior. + +The simplest option is to only specify that you want a `bitmap` resource, and +by default the SDK will do the most reasonable thing: + +```js +{ + "type": "bitmap", + "name": "IMAGE_BERRY_PUNCH", + "file": "images/berry-punch.png" +} +``` + +This will create an image with the smallest possible in-memory representation, +which will depend on the number of colors. On Aplite, where memory is tight, +it will optimize for low memory consumption by creating a pbi. On all other +platforms, where there is more memory to spare, it will create a png. + +This behavior can be overridden using the following attributes: + +* `memoryFormat`: This determines the `GBitmapFormat` that the resulting image + will have. The default value is `Smallest`, which picks the value that will + use the least memory. If you have specific requirements, you can specify: + `SmallestPalette`, `1BitPalette`, `2BitPalette`, `4BitPalette`, `1Bit` or + `8Bit`. If an image cannot be represented in the requested format, a build + error will result. This, for instance, enables palette manipulation with + static checking for confidence that you will have an appropriate palette to + manipulate. +* `spaceOptimization`: This determines whether we should optimize the image for + resource space (`storage`) or memory (`memory`). The default depends on the + memory available on the platform: Aplite defaults to `memory`, while other + platforms default to `storage`. +* `storageFormat`: This explicitly states whether an image should be stored on + flash as a PNG or pbi image. In most cases you should not specify this, and + instead depend on `spaceOptimization`. However, if you read resources + directly, this option exists to force a value. + +So if you want an image that will always have a 2-bit palette and use as little +memory as possible, you can do this: + +```js +{ + "type": "bitmap", + "name": "IMAGE_TIME_TURNER", + "file": "images/time-turner.png", + "memoryFormat": "2BitPalette", + "spaceOptimization": "memory" +} +``` + +The resulting image will always have `GBitmapFormat2BitPalette`, even if it +could have a 1-bit palette. If it has more than four colors, the build will +fail. + +In SDK 3.8-beta8, this would result in a 2-bit palettized pbi. However, this +is not guaranteed: we can change the format in the future, as long as the result +has ``GBitmapFormat2BitPalette`` and we prefer to optimise for memory +consumption where possible. + +Finally, note that some combinations are impossible: for instance, a `1Bit` +image can never be stored as a PNG, so `"storageFormat": "png"` combined with +`"memoryFormat": "1Bit"` will be a compile error. + +Migration +--------- + +If you have an existing app, how do you migrate it to use the new `bitmap` type? + +If you were depending on the `pbi` cropping behavior, you will have to manually +crop your image. Beyond that, this table gives the equivalences: + +| SDK 3.7 type | `bitmap` specification | +|--------------|----------------------------------------------| +| `png` | `{"type": "bitmap"}` | +| `pbi` | `{"type": "bitmap", "memoryFormat": "1Bit"}` | +| `pbi8` | `{"type": "bitmap", "storageFormat": "pbi"}` | + +`png-trans` has been left out of this entire exercise. Its behavior is unchanged: +it will produce two pbis with `GBitmapFormat1Bit`. However, `png-trans` is also +deprecated and discouraged. As of SDK 3.8, all platforms support transparency +in images, and so should use `bitmap` instead. + +If you have any questions, you can [find us on Discord]({{ site.links.discord_invite }}) +or [contact us](/contact/). diff --git a/devsite/source/_posts/2015-12-02-Bringing-the-Family-Back-Together.md b/devsite/source/_posts/2015-12-02-Bringing-the-Family-Back-Together.md new file mode 100644 index 00000000..9c98c44a --- /dev/null +++ b/devsite/source/_posts/2015-12-02-Bringing-the-Family-Back-Together.md @@ -0,0 +1,147 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Bringing the Family Back Together - 3.x on Aplite is Almost Here! +author: chrislewis +tags: +- Freshly Baked +banner: /images/blog/3.x-on-tintin.png +--- + +The time has almost come for Pebble Classic and Pebble Steel to get firmware +3.x! + +This is great news for users who get access to all the improvements and fixes +since 2.x, as well as new features such as timeline, Quiet Time, no more eight +app limit, Standby Mode, and last but not least one new color (gray)! + +But let us not forget about Basalt and Chalk app developers, who also have a lot +to get excited about - a huge new user base of Aplite watch wearers! + +This blog post aims to help developers using SDK 2.x features and APIs to +migrate their apps to a single 3.x codebase. As well as bringing compatibility +to all older watches when they upgrade to firmware 3.x, these changes will also +serve to remove a lot of complex and ugly conditional code that has needed to +exist in the meantime. Let's fix some apps! + + +## Get the Beta SDK + +To try out the beta SDK, read the instructions on the [SDK Beta](/sdk/beta) +page. + + +## Mandatory Changes + +To be compatible with users who update their Pebble Classic or Pebble Steel to +firmware 3.x the following important changes **MUST** be made: + +* If you are adding support for Aplite, add `aplite` to your `targetPlatforms` + array in `appinfo.json`, or tick the 'Build Aplite' box in 'Settings' on + CloudPebble. + +* Recompile your app with at least Pebble SDK 3.8 (coming soon!). The 3.x on + Aplite files will reside in `/aplite/` instead of the `.pbw` root folder. + Frankenpbws are **not** encouraged - a 2.x compatible release can be uploaded + separately (see [*Appstore Changes*](#appstore-changes)). + +* Update any old practices such as direct struct member access. An example is + shown below: + + ```c + // 2.x - don't do this! + GRect bitmap_bounds = s_bitmap->bounds; + + // 3.x - please do this! + GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap); + ``` + +* If your app uses either the ``Dictation`` or ``Smartstrap`` APIs, you must + check that any code dependent on these hardware features fails gracefully when + they are not available. This should be done by checking for `NULL` or + appropriate `enum` values returned from affected API calls. An example is + shown below: + + ```c + if(smartstrap_subscribe(handlers) != SmartstrapResultNotPresent) { + // OK to use Smartstrap API! + } else { + // Not available, handle gracefully + text_layer_set_text(s_text_layer, "Smartstrap not available!"); + } + + DictationSession *session = dictation_session_create(size, callback, context); + if(session) { + // OK to use Dictation API! + } else { + // Not available, handle gracefully + text_layer_set_text(s_text_layer, "Dictation not available!"); + } + ``` + + +## Appstore Changes + +To handle the transition as users update their Aplite to firmware 3.x (or choose +not to), the appstore will include the following changes: + +* You can now have multiple published releases. When you publish a new release, + it doesn’t unpublish the previous one. You can still manually unpublish + releases whenever they want. + +* The appstore will provide the most recently compatible release of an app to + users. This means that if you publish a new release that has 3.x Aplite + support, the newest published release that supports 2.x Aplite will be + provided to users on 2.x Aplite. + +* There will be a fourth Asset Collection type that you can create: Legacy + Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite + should use the Legacy Aplite asset collection for their 2.x assets. + + +## Suggested Changes + +To fully migrate to SDK 3.x, we also suggest you make these non-essential +changes: + +* Remove any code conditionally compiled with `PBL_SDK_2` defines. It will no + longer be compiled at all. + +* Ensure that any use of ``app_message_inbox_size_maximum()`` and + ``app_message_outbox_size_maximum()`` does not cause your app to run out of + memory. These calls now create ``AppMessage`` buffers of 8k size by default. + Aplite apps limited to 24k of RAM will quickly run out if they use much more + memory. + +* Colors not available on the black and white Aplite display will be silently + displayed as the closet match (black, gray, or white). We recommend checking + every instance of a `GColor`to ensure each is the correct one. + +* In addition to the point above, investigate how the contrast and readability + of your app can be improved by making use of gray (either `GColorLightGray` or + `GColorDarkGray`). Examples of this can be seen in the system UI in the banner + at the top of this blog post. + +* Apps using image resources should take advantage of the new `bitmap` resource + type, which optimizes image files for you. Read the + [*Unifying Bitmap Resources*](/blog/2015/12/02/Bitmap-Resources/) + blog post to learn more. + + +## Questions? + +That's it! It will be quite straightforward to update most of your apps, but if +you do have any problems or queries, feel free to +[contact us](/contact/) or find us on [Discord]({{ site.links.discord_invite }}). diff --git a/devsite/source/_posts/2016-01-29-Multiple-JavaScript-Files.md b/devsite/source/_posts/2016-01-29-Multiple-JavaScript-Files.md new file mode 100644 index 00000000..52e0a24c --- /dev/null +++ b/devsite/source/_posts/2016-01-29-Multiple-JavaScript-Files.md @@ -0,0 +1,127 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Multiple JavaScript Files +author: katharine +tags: +- Freshly Baked +--- + +In SDK 3.9 we will introduce a new feature to the Pebble SDK: the ability to +cleanly use multiple JavaScript files in the PebbleKit JS app portion of +your project. + + +SDK 3.9 is available now in beta! Check out our +[beta instructions](/sdk/download/#testing-beta-sdks) to try it out. + +**EDIT:** SDK 3.9 is now [publicly available](/sdk/) - to update your SDK, run: ```pebble sdk install latest``` + +# How do I use this? + +First, if you are using the native SDK, you must make sure you have +`"enableMultiJS": true` in your appinfo.json file. This defaults to false if +omitted, but will be set to `true` in all projects created using SDK 3.9+ and +Pebble Tool 4.1+. If you are using CloudPebble, ensure that "JS Handling" is +set to "CommonJS-style" in your project's settings page. + +Having enabled it, PebbleKit JS now expects to have an "entry point" at +`src/pkjs/index.js`. This is the code we will run when your app starts, and is +equivalent to the native SDK's old `src/js/pebble-js-app.js`. Aside from a +slightly less redundant name, the effective behavior is exactly the same as +before. + +However, you can now add additional javascript files! These files will not be +executed automatically. Instead, you can pull them in to your code using the +`require` function, which returns a reference to the imported file. + +Since `require` returns something, there needs to be some way to provide +something that it can usefully return. To do this, add properties to +`module.exports` in the file to be required. For instance: + +**adder.js** + +```js +function addThings(a, b) { + return a + b; +} + +module.exports.addThings = addThings; +``` + +**index.js** + +```js +var adder = require('./adder'); +console.log("2 plus 2 is " + adder.addThings(2, 2)); +``` + +Running the app with this JavaScript would look like this: + +```nc|text +katharine@scootaloo ~> pebble install --emulator basalt --logs +Installing app... +App install succeeded. +[17:39:40] javascript> 2 plus 2 is 4 +``` + +That's about all there is to it: we handle this all transparently. + +# But…!? + +### …how do I migrate my existing project? + +If you use the native SDK and your existing project has only one JavaScript +file, just move it from `src/js/pebble-js-app.js` to `src/pkjs/index.js` and add +`"enableMultiJS": true` to your appinfo.json file. Easy! + +If you use CloudPebble and have multiple JavaScript files, you first change +"JS Handling" to "CommonJS-style" in your project's Settings page. If you don't +have a JavaScript file called "index.js", but you do have some others, it will +prompt you to rename a file. If you previously used multiple JavaScript files +on CloudPebble, you will now need to make code changes. In particular, +you will have to modify your code so that there is a single, clearly-defined +entry point (`index.js`) and the other files use the module export system +described above. + +That said, there is no need to do this now: the existing system will continue to +work for the forseeable future. + +### …will this work for users who haven't updated their mobile apps? + +Yes! This all happens entirely at build time; no updates to the user's phone +software are required. Since it is part of SDK 3.9, they will need to be running +firmware 3.9, as usual. + +### …I use Pebble.js. What does this mean for me? + +Nothing! Pebble.js already does something very similar to this, and it will +continue to do so. No changes are needed. + +### …I built something like this myself. Do I have to use this? + +No! If you want to keep using your own custom system, you can set +`enableMultiJS` to false or omit it entirely, and nothing will change. The +system is strictly opt-in, and won't break anything you were doing if you +leave it as-is. + +### …I don't want my main JavaScript file to be called "index.js" + +We recommend that you use the default name for portability reasons. In +particular, CloudPebble will _require_ that you use this name, and will fail +to build a non-compliant project. However, if you would really like to change +it, you can pass an alternate path as `js_entry_file` to `pbl_bundle` in your +wscript file. + diff --git a/devsite/source/_posts/2016-02-19-JavaScript-Libraries-for-C-Developers-pt-1.md b/devsite/source/_posts/2016-02-19-JavaScript-Libraries-for-C-Developers-pt-1.md new file mode 100644 index 00000000..ef5bc1b0 --- /dev/null +++ b/devsite/source/_posts/2016-02-19-JavaScript-Libraries-for-C-Developers-pt-1.md @@ -0,0 +1,248 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: JavaScript Libraries for C Developers (pt. 1) +author: cat +tags: +- Freshly Baked +- Beautiful Code + +--- + +One of the exciting changes introduced in [SDK 3.9] [sdk 3.9] was support for +including [Multiple JavaScript Files] [js blog] to your projects. While this +feature doesn’t allow you to run any JavaScript you previously couldn’t, it +makes organizing your PebbleKit JS code a heck of a lot easier. + +In this blog post, we'll look at how to refactor some exisiting JavaScript +'library code' into an actual module for PebbleKit JS, making it simpler to use +and share! + + + +## OWM-Weather + +A few months ago we published a very simple 'library' for working with the +[Open Weather Map] [owm] API. The JavaScript side of the code takes care of +grabbing the user’s location with the built in [geolocation.getCurrentPosition] +[getcurrentposition] API, then fetches weather information for the returned +position. + +The initial version of the code we’re working with can be found [here] [owm lib 0]. + +## Creating JavaScript Classes + +> **Note:** While JavaScript (with ECMAS5) does not allow for true "Classes", +> JavaScript does a support a mechanism that allows you write Class-like code. +> For convience, we'll be refering to these objects as 'classes' throughout +> this post. + +The first thing we want to do is wrap our library code in an Object constructor +function, which is what allows us to treat `OWMWeather` as a class, and rewrite +our functions so they're properties belonging to `this` (an instantiated +version of the object). If you're interested in diving a bit deeper into how +this works, take a look at Pivotal's great blog post about [JavaScript +constructors, prototypes, and the 'new' keyword] [pivotal blog]. + +```js +var OWMWeather = function() { + this.owmWeatherAPIKey = ''; + + this.owmWeatherXHR = function(url, type, callback) { + //... + }; + //... +}; +``` + +We’ll also need to change our JS application code to construct a new +`OWMWeather` object, and change our reference of `appMessageHandler()` in the +in the `Pebble.addEventListener('appmessage', ...)` callback to +`owmWeather.appMessageHandler()`. + +```js +var owmWeather = new OWMWeather(); + +Pebble.addEventListener('ready', function(e) { + console.log('PebbleKit JS ready!'); +}); + +Pebble.addEventListener('appmessage', function(e) { + console.log('appmessage: ' + JSON.stringify(e.payload)); + owmWeather.appMessageHandler(e); +}); +``` + +## Using the .bind() function + +If we try to run our code at this point in time, we're going to run into a slew +of errors because we've changed the scope of the functions, but we haven't +updated the places where we call them. We need to change +`owmWeatherFunctionName()` to `this.owmWeatherFunctionName()` (same goes for +the API key). Here's what the new `owmWeatherLocationSuccess` method looks like +(changes are indicated with in-line comments): + +```js +this.owmWeatherLocationSuccess = function(pos) { + // Change owmWeatherAPIKey to this.owmWeatherAPIKey + var url = 'http://api.openweathermap.org/data/2.5/weather?' + + 'lat=' + pos.coords.latitude + '&lon=' + pos.coords.longitude + + '&appid=' + this.owmWeatherAPIKey; + console.log('owm-weather: Location success. Contacting OpenWeatherMap.org..'); + + this.owmWeatherXHR(url, 'GET', function(responseText) { + console.log('owm-weather: Got API response!'); + if(responseText.length > 100) { + // Change owmWeatherSendToPebble(..) to this.owmWeatherSendToPebble(..) + this.owmWeatherSendToPebble(JSON.parse(responseText)); + } else { + console.log('owm-weather: API response was bad. Wrong API key?'); + Pebble.sendAppMessage({ 'OWMWeatherAppMessageKeyBadKey': 1 }); + } + // Add .bind(this) to the closing brace of the callback + }.bind(this)); +}; +``` + +An observant developer might notice that along with changing `owmWeatherXHR` to +`this.owmWeatherXHR`, we've also added `.bind(this)` to the end of the callback +function. + +We're not going to dive too deeply into how `this` and `bind` works (that's a +whole blog post on its own), but what I will say is that the `bind` method can +be thought of as modifying a function so that it will, when invoked, have its +`this` keyword set to the provided parameter. + +> If you want to learn more about JavaScript Objects, scope, and `bind`, I will +> encourage you to read [You Don't Know JS: *this* & Object Prototypes] [js book]. + +We'll want use `bind(this)` (where `this` will be the instance of the +OWMWeather class) whenever we're using an OWMWeather method as a callback from +within the OWMWeather code. + +```js +navigator.geolocation.getCurrentPosition( + this.owmWeatherLocationSuccess.bind(this), + this.owmWeatherLocationError.bind(this), { + timeout: 15000, + maximumAge: 60000 +}); +``` + +At this point, our code should look like [this] [owm lib 1]. + +## Using module.exports + +The last thing we want to do to make this into a handy module is extract the +code into its own file (`/src/js/lib/owm_weather.js`), and use the +[module.exports] [module exports] API to export our OWMWeather class. + +```js +var OWMWeather = new function() { + //... +}; + +module.exports = OWMWeather; +``` + +In order to use this in our PebbleKit JS application, we need to do a couple things.. + +If you're using CloudPebble: + +- Change **JS Handling** to _CommonJS-style_ in the project settings + + +If you're using the SDK: + +- Update our `appinfo.json` to include `'enableMultiJS': true` if it isn't + already there + +- Rename `src/js/pebble-js-app.js` to `src/js/app.js` + +Once we've made these changes to our files, we're ready to include OWMWeather +in our `app.js` file with the [require API] [require api]. + +```js +var OWMWeather = require('./lib/owm_weather.js'); +var owmWeather = new OWMWeather(); + +Pebble.addEventListener('ready', function(e) { + console.log('PebbleKit JS ready!'); +}); + +Pebble.addEventListener('appmessage', function(e) { + console.log('appmessage: ' + JSON.stringify(e.payload)); + owmWeather.appMessageHandler(e); +}); +``` + +At this point, our code should something look like [this][owm lib 2]. + +## Refactoring Variable and Function Names + +Since we’ve moved all of the OWMWeather code into a class, we can safely remove +the `owmWeather` prefixes on all of our methods and properties. While we’re at +it, we're also going to rename functions and properties that are intended to be +private to begin with an `_`, which is fairly common practice: + +```js +var OWMWeather = function() { + this._apiKey = ''; + + this._xhrWrapper = function(url, type, callback) { + //... + }; + //... +}; +``` + +## What's next.. + +And that's it - we've successfuly refactored our code into a module that should +be easier for developers to use and share (which is exactly what we want). You +can view the full source code for this blog post [here][owm lib final]. + +In the next blog post, we'll take this library a step further and look at how +you can abstract away the need for `appKeys` in your `appinfo.json` file to +make working with the library *even easier*... + +Until then, happy hacking! + + +[sdk 3.9]: /sdk/changelogs/3.9/ +[js blog]: /blog/2016/01/29/Multiple-JavaScript-Files/ + + +[owm]: http://openweathermap.org/ + + +[getcurrentposition]: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition + +[pivotal blog]: https://blog.pivotal.io/labs/labs/javascript-constructors-prototypes-and-the-new-keyword + +[js book]: https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes + +[module exports]: http://www.sitepoint.com/understanding-module-exports-exports-node-js/ + +[require api]: http://www.sitepoint.com/understanding-module-exports-exports-node-js/#importing-a-module + + +[owm lib 0]: https://github.com/pebble-hacks/owm-weather/tree/8c4f770e591fe5eff65209ebb6fe6ef23152d81a + +[owm lib 1]: https://github.com/pebble-hacks/owm-weather/blob/8c9ab77a66d5c38719acbdfce939fbfac6d12235/owm_weather/owm_weather.js + +[owm lib 2]: https://github.com/pebble-hacks/owm-weather/tree/597c717627c281b56ff303ca94f35789002a969e + +[owm lib final]: https://github.com/pebble-hacks/owm-weather/tree/657da669c3d9309a956f655c65263b8dc06cec1f diff --git a/devsite/source/_posts/2016-02-29-introducing-app-debugging.md b/devsite/source/_posts/2016-02-29-introducing-app-debugging.md new file mode 100644 index 00000000..ceacc201 --- /dev/null +++ b/devsite/source/_posts/2016-02-29-introducing-app-debugging.md @@ -0,0 +1,271 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Introducing App Debugging +author: katharine +tags: +- Freshly Baked +--- + +Happy leap day! Today is a once-every-four-years day of bizarre date-related +bugs, and thus an opportune moment for us to introduce our latest developer +feature: app debugging in the emulator! This gives you a powerful new way to +hunt down errors in your apps. + + + + +A new command is available in our preview release of pebble tool 4.2 and +SDK 3.10: `pebble gdb`. +Running this command from a project directory on an emulator with your app +installed will pause the emulator and attach a gdb debugger instance +to it: + +```nc|text +katharine@scootaloo ~/p/pebble-caltrain (master)> pebble gdb --emulator basalt +Reading symbols from /Users/katharine/Library/Application Support/Pebble SDK/SDKs/3.10-beta4/sdk-core/pebble/basalt/qemu/basalt_sdk_debug.elf...(no debugging symbols found)...done. +Remote debugging using :49229 +0x0801cd8c in ?? () +add symbol table from file "/Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf" at + .text_addr = 0x200200a8 + .data_addr = 0x20023968 + .bss_addr = 0x200239b8 +Reading symbols from /Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf...done. +Breakpoint 1 at 0x804aebc + +Press ctrl-D or type 'quit' to exit. +Try `pebble gdb --help` for a short cheat sheet. +Note that the emulator does not yet crash on memory access violations. +(gdb) +``` + +Do note that once we pause execution by launching gdb, emulator buttons and +any pebble tool command that interacts with the emulator won't work until we +continue execution (using `continue` or `c`) or quit gdb. + +Once we're here, we can add some breakpoints to the app in interesting places. +Here we are debugging my +[Caltrain app](https://github.com/Katharine/pebble-caltrain). Let's say I think +there's a bug in the search for the next train: I probably want to poke around +in [`find_next_train`](https://github.com/Katharine/pebble-caltrain/blob/f4983c748429127a8af85911cb123bd8c3bacb73/src/planning.c#L4). +We can run `b find_next_train` to add a breakpoint there: + +```nc|text +(gdb) b find_next_train +Breakpoint 2 at 0x20020f1e: file ../src/planning.c, line 5. +(gdb) +``` + +Now we can use the `c` or `continue` command to set my app running again, until +it stops at `find_next_train`: + +```nc|text +(gdb) c +Continuing. +``` + +The app runs as usual until we open a station, which causes it to look up a +train, where it hits the breakpoint and pauses the app so we can inspect it: + +```nc|text +Breakpoint 2, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, + sb=0x20018738) at ../src/planning.c:5 +5 TrainTime *best[2] = {NULL, NULL}; +(gdb) +``` + +Now we can see how we got here using the `backtrace` or `bt` command: + +```nc|text +(gdb) bt +#0 find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) + at ../src/planning.c:7 +#1 0x200211b2 in next_train_at_station (station=13 '\r', + northbound=0x20025a0c , southbound=0x20025a14 ) + at ../src/planning.c:76 +#2 0x200215c8 in prv_update_times () at ../src/stop_info.c:106 +#3 0x200216f8 in show_stop_info (stop_id=13 '\r') at ../src/stop_info.c:174 +#4 0x200219f0 in prv_handle_menu_click (menu_layer=0x2002fe3c, + cell_index=0x2002ff0c, context=0x2002fe3c) at ../src/stop_list.c:57 +#5 0x0805cb1c in ?? () +#6 0x0805a962 in ?? () +#7 0x0801ebca in ?? () +#8 0x0801e1fa in ?? () +#9 0x200202d6 in main () at ../src/main.c:23 +#10 0x080079de in ?? () +#11 0x00000000 in ?? () +``` + +The `??` entries are inside the pebble firmware; the rest are in the Caltrain +app. +We can step forward a few times to get to an interesting point using the `step` +or `s` command: + +```nc|text +(gdb) s +7 const time_t timestamp = time(NULL); +(gdb) s +8 const uint16_t minute = current_minute(); +(gdb) s +current_minute () at ../src/model.c:183 +183 const time_t timestamp = time(NULL); +(gdb) +``` + +Now we've stepped into another function, `current_minute`. Let's say we're +confident in the implementation of this (maybe we wrote unit tests), so we can +jump back up to `find_next_train` using the `finish` command: + +```nc|text +(gdb) finish +Run till exit from #0 current_minute () at ../src/model.c:183 +0x20020f38 in find_next_train (count=184, times=0x2002f414, nb=0x2001873c, + sb=0x20018738) at ../src/planning.c:8 +8 const uint16_t minute = current_minute(); +Value returned is $2 = 738 +(gdb) +``` + +When we step to the next line, we see it has a similar `current_day` that we +don't need to inspect closely, so we jump over it using the `next` or `n` +command: + +```nc|text +(gdb) s +9 const uint8_t day = current_day(); +(gdb) n +11 for(int i = 0; i < count; ++i) { +(gdb) +``` + +Now we can double check our current state by using `info locals` to look at all +our local variables, and `info args` to look at what was originally passed in: + +```nc|text +(gdb) info locals +i = 184 +best = {0x0 <__pbl_app_info>, 0x0 <__pbl_app_info>} +timestamp = 1456776942 +minute = 738 +day = 1 '\001' +(gdb) info args +count = 184 +times = 0x2002f414 +nb = 0x2001873c +sb = 0x20018738 +(gdb) +``` + +`timestamp`, `minute` and `day` all have the values they gained from our last +few function calls. `best` is still a pair of NULL pointers, and `i` hasn't been +assigned yet, so its value is garbage. Once we step another line it'll be filled +in, which we can check using the `print` or `p` command: + +```nc|text +(gdb) s +12 TrainTime *train_time = &times[i]; +(gdb) p i +$3 = 0 +``` + +Now let's step forward and have it fill in `train_time`, and see what we get: + +```nc|text +(gdb) s +14 trip_get(train_time->trip, &trip); +(gdb) p train_time +$4 = (TrainTime *) 0x2002f414 +(gdb) +``` + +This is unenlightening — it's just the same pointer as `times`, which is what we +expect when referencing `×[0]`. Fortunately, `print`/`p` will evaluate +arbitrary expressions, so we can dereference the pointer to see what it actually +points at: + +```nc|text +(gdb) p *train_time +$5 = {trip = 189, time = 309, stop = 13 '\r', sequence = 10 '\n'} +(gdb) +``` + +Better! It might be more interesting to just print that out for each loop +iteration, so let's set a breakpoint here and have it print `*train_time` +and continue: + +```nc|text +(gdb) b +Breakpoint 3 at 0x20020f62: file ../src/planning.c, line 14. +(gdb) commands +Type commands for breakpoint(s) 3, one per line. +End with a line saying just "end". +>p *train_time +>c +>end +(gdb) c +Continuing. + +Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, + sb=0x20018738) at ../src/planning.c:14 +14 trip_get(train_time->trip, &trip); +$6 = {trip = 209, time = 344, stop = 13 '\r', sequence = 11 '\v'} + +Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, + sb=0x20018738) at ../src/planning.c:14 +14 trip_get(train_time->trip, &trip); +$7 = {trip = 199, time = 345, stop = 13 '\r', sequence = 13 '\r'} +``` +…and so on. A bit noisy, so let's remove that breakpoint now: + +```nc|text +(gdb) delete 3 +(gdb) +``` + +Finally, let's have our program continue on its way by running `c` again: + +```nc|text +(gdb) c +Continuing +``` + +When we want to get out of gdb we'll need our `(gdb)` prompt back, so press +ctrl-C to pause the app again: + +```nc|text +^C +Program received signal SIGINT, Interrupt. +0x08007072 in ?? () +(gdb) +``` + +This will most likely pause execution inside some firmware code, as we did when +we initially launched gdb. We can now do anything we've done before, but we're +just going to quit: + +```nc|text +(gdb) quit +A debugging session is active. + + Inferior 1 [Remote target] will be killed. + +Quit anyway? (y or n) y +katharine@scootaloo ~/p/pebble-caltrain (master)> +``` + +Hopefully this has given you some ideas as to how you might be able to use gdb +to debug your own apps. If you'd like to know more about gdb, +[here is a Q&A-style tutorial](http://www.unknownroad.com/rtfm/gdbtut/) that +will answer many questions you might have. Good luck and happy debugging! diff --git a/devsite/source/_posts/2016-03-07-Take-the-Pebble-Health-API-in-your-Stride.md b/devsite/source/_posts/2016-03-07-Take-the-Pebble-Health-API-in-your-Stride.md new file mode 100644 index 00000000..47c2ca1b --- /dev/null +++ b/devsite/source/_posts/2016-03-07-Take-the-Pebble-Health-API-in-your-Stride.md @@ -0,0 +1,206 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Take the Pebble Health API in your Stride +author: jonb +tags: +- Freshly Baked +--- + +I've been desperate to write a blog post about the Pebble [HealthService API] +(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/) +since it's initial release last month, and now I can finally share its +awesomeness with you. I'm also going to show you how you can use this exciting +new API to build an ultra-cool clone of the [Stride watchface] +(https://apps.getpebble.com/en_US/application/56b15c5c9c4b20ed5300006c) by +Pebble. + + +## Background + +Back in December 2015, Pebble Health was launched and had a massive uptake from +the existing Pebble userbase. For some reason, I foolishly thought health was +purely aimed at fitness fanatics and I completely underestimated its potential +uses and benefits to non-athletes like me. To give you a bit of my background, +I'm an overweight father of two, I don't get much sleep and probably spend an +unhealthy amount time in front of a computer screen. I enabled Pebble Health, +and as time progressed, I began to see how little exercise and how little sleep +I actually get. + +Once you have started visualising health information, you can begin to see +patterns and get an understanding of what 'good' days and 'bad' days look like. +You can then make incremental changes and see how that affects your mood and +quality of life, though to be completely honest, I haven't changed my bad habits +just yet. I haven't suddenly started running every day, nor have I magically +started getting >5 hours sleep each night, but I now have a wealth of data +available to help shape my future lifestyle decisions. + +## What health data is available? + +The HealthService API exposes various [`HealthMetric`] +(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#HealthMetric) +values which relate to the user being physically active, or sleeping. + +- `HealthMetricStepCount` - The number of steps counted. +- `HealthMetricActiveSeconds` - The number of seconds spent +active (i.e. not resting). +- `HealthMetricWalkedDistanceMeters` - The distance +walked, in meters. +- `HealthMetricSleepSeconds` - The number of seconds spent +sleeping. +- `HealthMetricSleepRestfulSeconds` - The number of sleep +seconds in the 'restful' or deep sleep state. +- `HealthMetricActiveKCalories` - The number of kcal +(Calories) burned while active. +- `HealthMetricRestingKCalories` - The number of kcal +(Calories) burned while resting due to resting metabolism. + +## Querying the health data + +There are a few important things you need to do before you actually attempt to +access the data from the HealthService. + +1. Indicate that your watchapp will be accessing Health data by adding `health` +to the `capabilities` section of the `appinfo.json`, or checking the ‘Uses Health’ +option in your project settings in CloudPebble. This is used by the Pebble Time +mobile app to inform users who are installing your watchapp that you're going to +be accessing their health data. If you don’t do this and attempt to access a +health api, your app will be terminated. +2. Check if the user has enabled Pebble Health in the Pebble Health app settings +and that there is any health data available. This is done by checking the +[`HealthServiceAccessibilityMask`] +(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#HealthServiceAccessibilityMask) +using [`health_service_metric_accessible()`] +(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#health_service_metric_accessible). + +Here is a basic example of the steps you should take before attempting to access +health data: + +```c +// Between midnight (the start time) and now (the end time) +time_t start = time_start_of_today(); +time_t end = time(NULL); + +// Check step count data is actually available +bool any_data_available = HealthServiceAccessibilityMaskAvailable & + health_service_metric_accessible(HealthMetricStepCount, start, end); +``` + +## Building a Stride clone + +![stride](/images/blog/2016-03-07-image03.png) +

    Stride by Pebble

    + +The Stride watchface displays the current time and your current step count for +today. Around the edge of the screen it displays a bar which is your progress +towards your average daily step count. The yellow line shows your daily average +step count for the current time of day. So as the day progresses, the yellow +line moves towards the end of your total step goal. If you manage to get ahead +of the yellow line, you’re on track to beat your daily step count and to +highlight this, all of the blue elements turn green. + +We're going to break down this project into several steps, so you can easily +see what's going on. I'm going to cheat slightly to keep the examples short. +Specifically we'll use emojis instead of the shoe icon, we won't be displaying +the am/pm indicator and we’ll only be drawing circular progress bars on all +platforms. + +## Step 1 - Building the basic watchface + +![strider-step1](/images/blog/2016-03-07-image00.png) + +We'll start by creating a fairly typical structure of a watchface. We +initialise a new `Window`, add a `TextLayer` to display the time, then set up a +tick handler to update the time every minute. + +[View the source code] +(https://github.com/pebble-examples/strider-watchface/tree/step1/src/Strider.c) + +## Step 2 - Add the step count & icon + +![strider-step2](/images/blog/2016-03-07-image07.png) + +Now we need to add a new `TextLayer` to display our step count and emoji. We're +going to need to query the HealthService API to retrieve: + +- The current step count for today. +- The target step goal amount. +- The average step count for the current time of day. + +The step goal only needs to be updated once per day and we're going to use the +[`HealthEventMovementUpdate`] +(https://developer.pebble.com/guides/pebble-apps/sensors/health/#subscribing-to-healthservice-events) +event to trigger an update for the other data. This event will probably trigger +multiple times per minute, so if you’re looking to conserve energy, you could +manually update the data. Stride also changes the color of the text and icon if +the user has exceeded their average step count, so we'll do that too. + +[View the source code] +(https://github.com/pebble-examples/strider-watchface/tree/step2/src/Strider.c) + +## Step 3 - Add the progress indicators and progress bar + +![strider-step3](/images/blog/2016-03-07-image04.png) + +We’re going to create 2 new layers, one for the grey progress indicator dots +and the other for the progress bar. To simplify the example, we’re just dealing +with a circular indicator. If you want to see how Stride actually draws +rectangularly, checkout the [health-watchface on Github] +(https://github.com/pebble-examples/health-watchface). + +The dots layer update procedure calculates the coordinates for each point using +[`gpoint_from_polar()`] +(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#gpoint_from_polar) +and then draws a circle at that point, for each dot. The progress layer update +procedure uses [`graphics_fill_radial()`] +(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#graphics_fill_radial) +which can fill a circle from a start and end angle, we’re using a narrow inset +thickness so that the circle is just drawn as a ring. + +We also need to redraw the progress layer when the step count changes, but all +we need to do is mark the layer dirty. + +[View the source code] +(https://github.com/pebble-examples/strider-watchface/tree/step3/src/Strider.c) + +## Step 4 - Add the average step indicator + +![strider-step4](/images/blog/2016-03-07-image01.png) + +The final thing we’re going to add is an indicator to show your average daily +steps for this time of day. We’ve already calculated the average, and we’re +going to use [`graphics_fill_radial()`] +(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#graphics_fill_radial) +again but this time it’s just to draw a yellow line. We’ll need to add another +new layer and update procedure to handle the drawing of the line. + +We also need to redraw the new layer when the step average value changes, but +again, all we need to do is mark the layer dirty. + +[View the complete source code] +(https://github.com/pebble-examples/strider-watchface) + +## The finished product + +![strider-step2b](/images/blog/2016-03-07-image05.jpg) +

    Strider watchface

    + +## Final thoughts + +I’ve really only scratched the surface of the HealthService API, but hopefully +you’re now sufficiently excited to build something awesome! We’d really love to +see how you use it, and If you create a health enabled watchapp or watchface, +don’t forget to let us know on [Twitter](http://twitter.com/pebbledev) or on +[Discord]({{ site.links.discord_invite }})! diff --git a/devsite/source/_posts/2016-05-16-Pebble-JS-Round.md b/devsite/source/_posts/2016-05-16-Pebble-JS-Round.md new file mode 100644 index 00000000..88fbd6bb --- /dev/null +++ b/devsite/source/_posts/2016-05-16-Pebble-JS-Round.md @@ -0,0 +1,149 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble.js Support for Chalk! +author: meiguro +tags: +- Freshly Baked +--- + +After many morning coding sessions on the weekends, +[Pebble.js](/docs/pebblejs/) has been updated to +support the Chalk platform (Pebble Time Round). This update is available in +[CloudPebble]({{site.links.cloudpebble}}), and ready for you to work with right in +your browser with no setup! + +Supporting Chalk required a ton of bug crushing to become compatible with the +new 3.0 style status bar, changes to accommodate reduced memory on the 3.x +Aplite platform, adding new features such as text flow and paging, and making it +easier to target multiple platforms by adding a feature detection API. + + + +![Pebble.js Screenshots](/images/blog/2016-05-13-pebble-js-round/pebblejs.png) +

    Pebble.js running on all available platforms.

    + +Whether or not you’re targeting the Chalk platform, there are many new features +for you to explore in this update - and simply recompiling your project with the +latest version of Pebble.js will update the look and feel through refined +spacing between text fields. + +The background color of window's status bar can now be changed, allowing it to +blend in with the app's background in a similar way to Pebble's Music app. + +```js +var defaultBackgroundColor = 'white'; +var defaultColor = 'black'; + +var card = new UI.Card({ + backgroundColor: defaultBackgroundColor, + status: { + color: defaultColor, + backgroundColor: defaultBackgroundColor + } +}); + +card.show(); +``` + +Two new elements were added, +[Line](/docs/pebblejs/#line) and +[Radial](/docs/pebblejs/#radial), and all elements +now include [borderWidth](/docs/pebblejs/#element- +borderwidth- width), [borderColor](/docs/pebblejs +/#element- bordercolor-color) and +[backgroundColor](/docs/pebblejs/#element- +backgroundcolor-color) properties (except `Line`, which uses +[strokeWidth](/docs/pebblejs/#line-strokewidth- +width) and [strokeColor](/docs/pebblejs/#line- +strokecolor-color) instead). + +There are many bugfixes as well. Scrolling is no longer jittery for cards and +menus, the status bar will no longer disappear upon changing windows that both +requested to have a status bar, and large bodies of text are truncated in cards +instead of just not showing up. + +There is one new known issue - which causes some applications with heavy memory +usage to crash. If you're experiencing this issue, we would appreciate you +adding more details to [#161](https://github.com/pebble/pebblejs/issues/161). + +This update also comes with two new guides to help familiarize yourself with the +exciting new world of round and colorful apps. [Using +Color](/docs/pebblejs/#using-color) will help you +understand all the different ways you can specify which color you want your text +(and other elements) to be, and how you would go about styling your app with +color. [Using Feature](/docs/pebblejs/#using- +feature) will help you understand the new Feature API, how it can be used to +specify different behaviours and UIs depending on what platform you're running +on, and what features it includes. + +## CloudPebble + +Enabling Chalk support in CloudPebble is simple. Open your project's "Settings" +screen, check the "Build Chalk" box, then recompile your project. + +![CloudPebble Settings Screen](/images/blog/2016-05-13-pebble-js-round/settings.png) +

    Cloud Pebble Settings Screen.

    + +To run your project on CloudPebble's Chalk emulator, compile your project, then +open the "Compilation" sceen, and select "Emulator" then "Chalk." + +## Pebble Tool + Local SDK + +If you're using the Pebble Tool and local SDK, you'll need to merge the `master` +branch of the Pebble.js repositoroy into your project, then edit the +`appinfo.json` file to include `chalk` in the `targetPlatforms` array. + +``` +{ + "uuid": "8e02b5f5-c0fb-450c-a568-47fcaadf97eb", + "shortName": "Test", + "longName": "Test Application", + "companyName": "#makeawesomehappen", + "versionLabel": "0.1", + "sdkVersion": "3", + "enableMultiJS": true, + "targetPlatforms": ["aplite", "basalt", "chalk"], + "watchapp": { + "watchface": false + }, + "appKeys": { }, + "resources": { } +} + +``` + +To run your project with command line emulator, compile your project, then +install it to the Chalk emulator: + +``` +> pebble clean +> pebble build && pebble install --emulator=chalk --logs +``` + +Remember, we’re also working on supporting native JavaScript applications on +Pebble smartwatches, and are calling it +[Rocky.js](https://pebble.github.io/rockyjs/). Rest assured, I will continue to +maintain Pebble.js, and you can continue to develop and ship apps with it! Keep +an eye out on future developments by following the [GitHub +repository](https://github.com/pebble/pebblejs) and checking out the latest +commits. + +Let us know abour Pebble.js project or anything else you feel like mentioning, +by shouting on Twitter to [@PebbleDev](https://twitter.com/pebbledev), +[@Pebble](https://twitter.com/pebble) or even myself +[@Meiguro](https://twitter.com/meiguro). + +Here’s to more exciting weekend coding sessions in the future for both of us! diff --git a/devsite/source/_posts/2016-06-07-pebble-packages.md b/devsite/source/_posts/2016-06-07-pebble-packages.md new file mode 100644 index 00000000..2f00ddb4 --- /dev/null +++ b/devsite/source/_posts/2016-06-07-pebble-packages.md @@ -0,0 +1,74 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Introducing Pebble Packages +author: katharine +tags: +- Freshly Baked +--- + +I am thrilled to announce that, as part of SDK 3.13 and Pebble Tool 4.3, we have +launched our own packaging system: Pebble Packages! + + + +There are already a healthy selection of Pebble libraries to be had, such as +Yuriy's excellent [EffectLayer](https://github.com/ygalanter/EffectLayer), +Reboot's Ramblings' +[palette manipulator](https://github.com/rebootsramblings/GBitmap-Colour-Palette-Manipulator), +or even our own [weather library](https://github.com/pebble-hacks/owm-weather). +However, using them today is inelegant: you generally have to copy/paste all of +the library code into your own project and follow some other setup instructions: +assigning appmessage keys, editing resources into your appinfo.json, or similar. + +Starting today, using a Pebble Package can be as simple as running +`pebble package install pebble-owm-weather`. A Pebble Package can contain +both C code for the watch and JavaScript code for the phone. Furthermore, it can +also contain any resources, and define its own appmessage keys. All numeric +identifiers will be resolved at app build time to ensure there are never any +conflicts. This helps to enable zero-setup packages of all kinds. + +To make the usage of appmessages in Pebble Packages possible, we have also +substantially improved the functionality of the old `appKeys` section of +appinfo.json. We will now automatically insert the keys into your C code with +the prefix `MESSAGE_KEY_`. You can also access their numeric values from +JavaScript by requiring the `message_keys` module: +`var keys = require('message_keys')`. + +Packages would be nothing without a package manager, so we have built our +packaging system on top of the excellent [npm](https://npmjs.com) package manager. This gives +us support for dependency management, versioning, package hosting, and more. Furthermore, +some traditional JavaScript modules on npm will work out of the box, as long as +they don't depend on running in node or on a browser. As part of this +move we have **deprecated appinfo.json**: we now use `package.json` instead. +The latest version of the Pebble Tool can convert your project when you run +`pebble convert-project`. Old-style projects continue to be supported, but +cannot use the package manager. + +For your convenience, we have provided some wrappers around npm functionality: + +* `pebble package install` to safely install a package. +* `pebble package uninstall` to uninstall a package. +* `pebble package login` to log in to or create your npm account. +* `pebble package publish` to publish your package to npm. + +We also have UI in CloudPebble to enter your dependencies. + +You can browse the available packages on npm under the +[pebble-package](https://www.npmjs.com/browse/keyword/pebble-package) keyword, +and read our guides on [using](/guides/pebble-packages/using-packages/) and +[creating](/guides/pebble-packages/creating-packages/) Pebble Packages. + +I'm looking forward to seeing what you make! diff --git a/devsite/source/_posts/2016-06-15-sdk4-preview.md b/devsite/source/_posts/2016-06-15-sdk4-preview.md new file mode 100644 index 00000000..e7e5dae8 --- /dev/null +++ b/devsite/source/_posts/2016-06-15-sdk4-preview.md @@ -0,0 +1,186 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: A Wild SDK Appears +author: cat +tags: +- Freshly Baked +--- + +Developers rejoice - we’ve released the first version of our [SDK 4](/sdk4) +developer preview! This SDK enables you to start building applications for the +new Diorite platform (Pebble 2), and includes a set of new APIs for interacting +with the 4.0 user experience. + +In this blog post we’ll dive into the [App Glance](/docs/c/Foundation/App_Glance/), +[`UnobstructedArea`](/docs/c/User_Interface/UnobstructedArea/), and +[`AppExitReason`](/docs/c/Foundation/Exit_Reason/) APIs, and explain +how you can use them to create great experiences for your users! + + + +## App Glance API + +Let’s start with the App Glance API, which allows applications to present +information for the launcher to display. The information an application displays +in the launcher is called an app glance - and the information that is displayed +at any particular point in time is referred to as an [`AppGlanceSlice`](/docs/c/Foundation/App_Glance/#AppGlanceSlice). + +![Virtual Pet App >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-06-15-sdk4-preview/virtual-pet.png) + +`AppGlanceSlice`s have expiration times, which means you can add multiple slices +at once. Slices will be displayed in the order they are added and removed at +their specified expiration times. + +```c +static void prv_update_app_glance(AppGlanceReloadSession *session, size_t limit, void *context) { + // This shouldn't happen, but developers should always ensure they have + // sufficient slices, or adding slices may fail. + if (limit < 1) return; + + // !! When layout.icon_resource_id is not set, the app's default icon is used + const AppGlanceSlice entry = (AppGlanceSlice) { + .layout = { + .template_string = "Hello from the app glance" + }, + .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION + }; + + // Add the slice, and store the result so we can check it later + AppGlanceResult result = app_gpance_add_slice(session, entry); +} +``` + +To dig into this feature, we recommend you start with our new +[AppGlance C API guide](/guides/user-interfaces/appglance-c), and also give the +[API docs](/docs/c/Foundation/App_Glance/) a quick read. + +> We are planning to extend the AppGlance API to include a PebbleKit JS API, as +> well as a HTTP web API, intended to allow developers to push `AppGlanceSlice`s +> from external web services to their users' smartwatches. + +## UnobstructedArea API + +The [`UnobstructedArea`](/docs/c/User_Interface/UnobstructedArea/) API +allows developers to create watchfaces capable of sensing, and responding to +Timeline Quick View events. Timeline Quick View is a new feature in SDK 4.0, +which displays upcoming and ongoing events on top of the user’s watchface. The +functionality added through the `UnobstructedArea` API allows developers to get +the unobstructed bounds of a layer, or subscribe to events related to the +watchface’s unobstructed area changing. + +```c +static int s_offset_top_percent = 33; +static int s_offset_bottom_percent = 20; + +static void prv_unobstructed_change(AnimationProgress progress, void *context) { + // Get the total available screen real-estate + GRect bounds = layer_get_unobstructed_bounds(window_layer); + + // Shift the Y coordinate of the top text layer + GRect top_frame = layer_get_frame(text_layer_get_layer(s_top_text_layer)); + top_frame.origin.y = bounds.size.h * s_offset_top_percent / 100; + layer_set_frame(top_frame, text_layer_get_layer(s_top_text_layer)); + + + // Shift the Y coordinate of our bottom text layer + GRect bottom_frame = layer_get_frame(text_layer_get_layer(s_bottom_text_layer)); + bottom_frame.origin.y = bounds.size.h * s_offset_bottom_percent / 100; + layer_set_frame(bottom_frame, text_layer_get_layer(s_bottom_text_layer)); +} + +static void prv_main_window_load(Window *window) { + unobstructed_area_service_subscribe((UnobstructedAreaHandlers) { + .change = prv_unobstructed_change + }, NULL); +} +``` + +> We encourage developers to begin exploring what their existing watchfaces will +> look like when the system is displaying the Timeline Quick View dialog, and +> adjust their designs to provide the best experience possible for users. + +Take a look at our [UnobstructedArea API Guide](/guides/user-interfaces/unobstructed-area/) +and the [API documentation](/docs/c/User_Interface/UnobstructedArea/) to +get started! + +## AppExitReason API + +One of the APIs we haven’t talked about as much is the +[`AppExitReason`](/docs/c/Foundation/Exit_Reason/) API, which enables +developers to specify why their app exited, and determines whether the system +returns the user to the launcher, or the active watchface. Take a look at our +[AppExitReason API Guide](/guides/user-interfaces/app-exit-reason), +and read [the API docs](/docs/c/Foundation/Exit_Reason) to learn more. + +This feature enables developers to create "One Click Action" watchapps, that +perform an action, then immediately return the user to the watchface to create a +simple, fluid experience. + +![Uber App >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-06-15-sdk4-preview/uber.gif) + +To help get you started thinking about and designing One Click Action apps, +we’ve created a [One Click Action App Guide](/guides/design-and-interaction/one-click-actions) +around the relatively minimal example of locking and unlocking your front door +(with a [Lockitron lock](https://lockitron.com)). + +## 4.0 Emulator + +The SDK 4 preview also includes an update to the emulator, which not only adds +support for Pebble 2, but includes the updated 4.0 user interface, and a few +other goodies that we’re sure developers will love. + +The new emulator includes a launcher capable of displaying watchapps’ glances, +and can be accessed by pressing the `Select` button from the watchface. + +CloudPebble and the Pebble Tool also include new functionality to enable +developers to toggle Timeline Quick View, allowing you to make sure your +watchface looks good in every context! + +Finally, we’ve added the ability to install multiple watchfaces and watchapps +into the emulator. Watchfaces and watchapps installed into the emulator will +remain installed (even if the emulator is closed) until `pebble wipe` is called +from the command line. + +Due to limitations with CloudPebble, this feature is currently only available in +the Pebble Tool. + +## What’s Next + +The SDK 4 developer preview is exactly that, a preview. You’ve probably noticed +a few really important and exciting features we didn’t mention - the Heart Rate +API has been designed, but is not yet fully implemented, and we have not yet +added the ability to build applications for the Emery Platform (Pebble Time 2). + +The official SDK 4 release is currently planned for the end of August - and it +will include not only the Heart Rate API, and support for building Emery +applications, but the first version of Pebble’s fantastic new embedded +JavaScript SDK, [Rocky.js](http://pebble.github.io/rockyjs/). + +## Show Us What You Make + +While we’ve been conceptualizing and designing the new functionality in SDK 4 +for quite a long time, the API itself is relatively new, even for those of us +lucky enough to ‘be on the inside’ - so we’ve only really just begun to see +what’s possible, and we’re more than excited to see what you’ll build with this +new functionality. + +Send us a link to something you’ve built on Twitter ( +[@pebbledev](https://twitter.com/pebbledev)) and we’ll look at including it in +an upcoming newsletter (and send some swag your way). + +Happy Hacking! + +Team Pebble diff --git a/devsite/source/_posts/2016-06-24-introducing-clay.md b/devsite/source/_posts/2016-06-24-introducing-clay.md new file mode 100644 index 00000000..114d2963 --- /dev/null +++ b/devsite/source/_posts/2016-06-24-introducing-clay.md @@ -0,0 +1,117 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Introducing Clay - App configuration made easy +author: keegan +tags: +- Freshly Baked +--- + +It is with great pleasure that I present to you, Clay, a Pebble package that makes it easy to add offline configuration pages to your Pebble apps. All you need to get started is a couple lines of JavaScript and a JSON file; no servers, HTML or CSS required. + + + +![Clay Example](/images/blog/2016-06-24-introducing-clay/clay-example.png =421) + +## How to get started + +This post aims to provide high level explanation of how the framework works and how it came to be. Below is a quick overview of how easy it is to make your app configurable with Clay. If you would like to learn how to integrate Clay into your project, read our guide on [app configuration](/guides/user-interfaces/app-configuration/) or read the full documentation on the project [GitHub repository.](https://github.com/pebble/clay#clay) + +**1) Install the module via [Pebble Packages](/guides/pebble-packages/using-packages/)** + - SDK: `$ pebble package install pebble-clay` + - CloudPebble: Add `pebble-clay`, version `^1.0.0` to your project dependencies. + +**2) Create a configuration file that looks something like:** + +```js +module.exports = [ + { + "type": "heading", + "defaultValue": "Example App" + }, + { + "type": "section", + "items": [ + { + "type": "color", + "messageKey": "BackgroundColor", + "defaultValue": "0x000000", + "label": "Background Color" + }, + { + "type": "toggle", + "messageKey": "Animations", + "label": "Enable Animations", + "defaultValue": false + } + ] + }, + { + "type": "submit", + "defaultValue": "Save Settings" + } +]; +``` + +**3) Update your project settings with the matching `messageKeys` from the config above.** + +**4) Add a few lines to your `index.js`** + +```js +var Clay = require('pebble-clay'); +var clayConfig = require('./config'); +var clay = new Clay(clayConfig); +``` + +**5) Retrieve the values on the C side using standard [app messages](/guides/communication/sending-and-receiving-data/) or alternatively let [enamel](https://github.com/gregoiresage/enamel) do the work for you.** + + +## Why I created Clay + +Clay began as a side project of mine. Whilst developing my [Segment watchface](https://apps.pebble.com/applications/560ae4754d43a36393000001), I wanted to add a configuration page to allow users to customize the colors of the watchface. However, I soon discovered this process to be rather fiddly. The old way of doing things required you to create and host HTML pages even for simple things like changing a background color or toggling an option. You would also need to write a bunch of boilerplate JavaScript to serialize and send the settings to the watch. Best case, for developers, this is super tedious. Worst case, it is terrifying. By day, I work as a web developer (mainly front-end) so if I was finding the process tiresome, I could only imagine how challenging it would be for someone who wasn't familiar with web technologies. And so I decided to create a framework that would alleviate this barrier for developers. I had a number of requirements that needed to be met in order for the framework to achieve my goals: + + - It should not require developers to write any HTML or CSS. + - It should use JSON to define the generated config page. + - It should all work offline + - Developers should be able to add interactivity to the config page without manually manipulating DOM. + - Developers should be able to create and share their own custom components. + - The config page should be able to be versioned with the rest of the code in the project. + - It should have extensive unit tests with 100% test coverage. + + +## How Clay Actually Works + +Clay has two main components. The first is a very long string that is compiled from all the HTML, CSS and JavaScript that forms the skeleton of the generated config page. The second is the module that gets included in your `index.js`. This module is in charge of bundling all the dynamic elements set by the developer into a format that can be opened using `Pebble.openURL()`. It is also in charge of listening for the `webviewclosed` event and persisting the user's settings to local storage. I use Gulp and a series of Browserify transforms to compile all these components into a single module. The advantage of using a system such as Browserify, is that later, Clay can be made into a stand alone module to be used by developers who have very advanced use cases that require Clay to be run in a hosted HTML page. + +The most challenging item on the requirements list was making the whole thing work offline. Neither of the Pebble mobile apps allow file system access from the config page's webview, so I had to get a little creative. It turns out that [Data URIs](https://en.wikipedia.org/wiki/Data_URI_scheme) work with more than images. You can in fact encode an entire HTML page into the URI. This was the solution to making the config pages offline. It sounds crazy but this method actually provides other advantages for developers beyond offline access. By bundling all the HTML, CSS and JavaScript into the Clay package, the version of Clay being used by the developer will not change without the developer rebuilding their app. This means developers do not need to worry about the behavior of their app's configuration page potentially breaking as new versions of Clay get released. + + +## Advanced Use + +If developers want to add additional functionality to their config page, they can. Clay allows developers to inject, what I call a [`custom function`](https://github.com/pebble/clay#custom-function) into the generated config page. This allows developers control of their config page without needing to manipulate the DOM. Clay achieves this by exposing an API that provides a consistent way of interacting with the config page as well as making AJAX requests. Instead of manually updating the values of HTML elements, developers can use the much simpler Clay API. + + +## Where to find more information + + - [App configuration guide.](/guides/user-interfaces/app-configuration/) + - [Clay GitHub repository including full documentation.](https://github.com/pebble/clay) + - Chat to us in the `#clay` channel on [Discord]({{ site.links.discord_invite }}). + - Visit the [Pebble Forums](https://forums.pebble.com/) + - Tweet at [@pebbledev](https://twitter.com/pebbledev) + + +## How to get involved + +Clay is open source and welcomes pull requests from anybody. Have a look at the [contributing guide](https://github.com/pebble/clay/blob/master/CONTRIBUTING.md) for instructions on how you can help make Clay even better. Regardless of your skill level, if you have any ideas on how Clay could be improved or if you find any bugs, we would love you to [submit an issue on GitHub](https://github.com/pebble/clay/issues/new). diff --git a/devsite/source/_posts/2016-08-15-introducing-rockyjs-watchfaces.md b/devsite/source/_posts/2016-08-15-introducing-rockyjs-watchfaces.md new file mode 100644 index 00000000..39b503bc --- /dev/null +++ b/devsite/source/_posts/2016-08-15-introducing-rockyjs-watchfaces.md @@ -0,0 +1,291 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Introducing Rocky.js Watchfaces! +author: jonb +tags: +- Freshly Baked +--- + +We're incredibly excited and proud to announce the first public beta version of +Rocky.js watchfaces for Pebble. Rocky.js is ECMAScript 5.1 JavaScript running +natively on Pebble smartwatches, thanks to +[our collaboration](https://github.com/Samsung/jerryscript/wiki/JerryScriptWorkshopApril2016) +with [JerryScript](https://github.com/pebble/jerryscript). This is an incredible +feat of engineering! + + +## It's JavaScript on the freakin' watch! + +```javascript +var rocky = require('rocky'); + +rocky.on('minutechange', function(event) { + rocky.requestDraw(); +}); + +rocky.on('draw', function(event) { + var ctx = event.context; + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + ctx.fillStyle = 'white'; + ctx.textAlign = 'center'; + + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + ctx.fillText('JavaScript\non the watch!', w / 2, h / 2); +}); +``` + +![Rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-16-jotw.png) + +Right now we're giving you early access to this game-changing environment to +create watchfaces entirely in JavaScript. Feel free to share your code, to +install it on the emulator and on your watch (with the upcoming firmware 4.0), +but please don't upload apps to the appstore as they will stop working in a +future release. + +
    + {% markdown %} + **Appstore Publishing** + + Rocky.js JavaScript watchfaces must not be published into the appstore at + this time. + {% endmarkdown %} +
    + +We can't wait to hear your feedback and see what amazing watchfaces you create! + +Enough hype, let's dive into the eye of the tiger! + + +## Rocky.js Projects + +Things are a little different in the JavaScript world, so let's take a closer +look at the structure of a Rocky.js project: + +```nc|text +package.json +src/rocky/index.js +src/pkjs/index.js +common/*.js +``` + +#### package.json + +The format of the `package.json` file remains roughly the same as existing +Pebble projects, with some minor differences: + +* `"projectType": "rocky"`. +* Aplite `targetPlatform` is not supported. +* `resources` are not currently supported, but will consume space in your PBW if +specified. +* `messageKeys` are not permitted. + +#### src/rocky/index.js + +This is now the main entry point into our application on the watch. This is +where our Rocky.js JavaScript code resides. All code within this file will be +executed on the smartwatch. Additional scripts may be placed in this folder, see +[below](#additional-scripts). + +#### src/pkjs/index.js + +This file contains our [PebbleKit JS](/docs/pebblekit-js/) (pkjs) JavaScript +code. This code will execute on the mobile device connected to the smartwatch. +Additional scripts may be placed in this folder, see +[below](#additional-scripts). + +For Rocky.js projects only, we've added a new simplified communication channel +[`postMessage()`](/docs/rockyjs/rocky/#postMessage) and +[`on('message', ...)`](/docs/rockyjs/rocky/#on) which allows you to send and +receive JavaScript JSON objects between the phone and smartwatch. + +#### Additional Scripts + +Both the `rocky` and `pkjs` folders support the use of multiple .js files, which +helps to keep your code clean and modular. Use the +[CommonJS Module](http://www.commonjs.org/specs/modules/1.0/) format for +your additional scripts, then `require()` them in your `index.js`. + +```javascript +// file: src/rocky/additional.js +function test() { + console.log('Additional File'); +} + +module.exports.test = test; +``` + +```javascript +// file: src/rocky/index.js +var additional = require('./additional'); +additional.test(); +``` + +#### common/*.js + +If you need to share code between `rocky` and `pkjs`, you can create a `common` +folder, then add your JavaScript files. + +```javascript +// file: src/common/shared.js +function test() { + console.log('Hello from shared code'); +} + +module.exports.test = test; +``` + +```javascript +// file: src/rocky/index.js +var shared = require('../common/shared'); +shared.test(); +``` + +```javascript +// file: src/pkjs/index.js +var shared = require('../common/shared'); +shared.test(); +``` + +## Available APIs + +In this initial release of Rocky.js, we have focused on the ability to create +watchfaces only. We will be adding more and more APIs as time progresses, and +we're determined for JavaScript to have feature parity with the rest of the +Pebble developer ecosystem. + +We've developed our API in-line with standard Web APIs, which may appear strange +to existing Pebble developers, but we're confident that this will facilitate +code re-use and provide a better experience overall. + +### System Events + +We've provided a series of events which every watchface will likely require, and +each of these events allow you to provide a callback method which is emitted +when the event occurs. + +Existing Pebble developers will be familiar with the tick style events, +including: +[`secondchange`](/docs/rockyjs/rocky/#on), +[`minutechange`](/docs/rockyjs/rocky/#on), +[`hourchange`](/docs/rockyjs/rocky/#on) and +[`daychange`](/docs/rockyjs/rocky/#on). By using these events, instead of +[`setInterval`](/docs/rockyjs), we're automatically kept in sync with the wall +clock time. + +We also have a +[`message`](/docs/rockyjs/rocky/#on) event for +receiving JavaScript JSON objects from the `pkjs` component, and a +[`draw`](/docs/rockyjs/rocky/#on) event which you'll use to control the screen +updates. + +```javascript +rocky.on('minutechange', function(event) { + // Request the screen to be redrawn on next pass + rocky.requestDraw(); +}); +``` + +### Drawing Canvas + +The canvas is a 2D rendering context and represents the display of the Pebble +smartwatch. We use the canvas context for drawing text and shapes. We're aiming +to support standard Web API methods and properties where possible, so the canvas +has been made available as a +[CanvasRenderingContext2D](/docs/rockyjs/CanvasRenderingContext2D/). + +> Please note that the canvas isn't fully implemented yet, so certain methods +> and properties are not available yet. We're still working on this, so expect +> more in future updates! + +```javascript +rocky.on('draw', function(event) { + var ctx = event.context; + + ctx.fillStyle = 'red'; + ctx.textAlign = 'center'; + ctx.font = '14px Gothic'; + + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + ctx.fillText('Rocky.js Rocks!', w / 2, h / 2); +}); +``` + +## Limitations + +We are still in the early beta phase and there are some limitations and +restrictions that you need to be aware of: + +* Don't publish Rocky.js watchfaces to the Pebble appstore yet, they will stop +working. +* Rocky.js watchfaces only run on the 4.0 emulators/firmware. The 4.0 firmware +will be available soon for Basalt, Chalk and Diorite. +* No support for custom fonts, images and other resources, yet. +* No C code allowed. +* No messageKeys. +* There are file size and memory considerations with your Rocky.js projects. +If you include a large JS library, it probably won't work. + +## SDK 4.0 + +We have published an updated version of +[CloudPebble]({{site.links.cloudpebble}}) which now supports creating +Rocky.js watchfaces in JavaScript. + +If you prefer our local tools, we've also published everything you need to begin +creating Rocky.js watchfaces. Run the following command to install the Pebble +Tool and 4.0 SDK: + +```nc|text +$ brew upgrade pebble-sdk +$ pebble sdk install latest +``` + +## How to Get Started + +We created a [2-part tutorial](/tutorials/js-watchface-tutorial/part1/) for +getting started with Rocky.js watchfaces. It explains everything you need to +know about creating digital and analog watchfaces, plus how to retrieve +weather conditions from the internet. + +If you're looking for more detailed information, check out the +[API Documentation](/docs/rockyjs). + +## Sample Watchfaces + +We've already created a few sample watchfaces: + +* [Tictoc](https://github.com/pebble-examples/rocky-watchface-tutorial-part1) - +Simple analog watchface. +* [Tictoc Weather](https://github.com/pebble-examples/rocky-watchface-tutorial-part2) - +Simple analog watchface with weather data from a REST API. +* [Leco with Weather](https://github.com/orviwan/rocky-leco-weather) - Simple +digital watchface with weather data from a REST API. +* [Leco with Clay](https://github.com/orviwan/rocky-leco-clay) - Simple analog +watchface which uses Clay for configurable settings. +* [Simplicity](https://github.com/orviwan/simplicity-rockyjs) - Rocky.js version +of the classic Simplicity watchface. + +## Feedback and Help + +We hope you're as exicited about Rocky.js as we are. If you need assistance, +have feedback or just want to send us some love, we're in #rockyjs on +[Discord]({{ site.links.discord_invite }}) or the +[Pebble developer forums](https://forums.pebble.com/c/development). + +We're already working on the next update of Rocky.js and your feedback will help +shape the future! diff --git a/devsite/source/_posts/2016-08-19-prime-time-is-approaching-for-os-4.0.md b/devsite/source/_posts/2016-08-19-prime-time-is-approaching-for-os-4.0.md new file mode 100644 index 00000000..eabfd012 --- /dev/null +++ b/devsite/source/_posts/2016-08-19-prime-time-is-approaching-for-os-4.0.md @@ -0,0 +1,165 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Prime Time is Approaching for OS 4.0 +author: jonb +tags: +- Freshly Baked +--- + +Pebble developers of the world unite! Pebble OS 4.0 is just around the corner +and it's now time to put those finishing touches on your projects, and prepare +them for publishing! + + +Pebble Time owners will be receiving OS 4.0 before Kickstarter backers receive +their Pebble 2 watches, so we're recommending that developers publish their +4.0 watchapps and watchfaces into the appstore **from August 31st onwards**. + +We'll be promoting new and updated apps which utilize the new SDK 4.0 APIs and +features by creating a brand new category in the appstore called +'Optimized for 4.0'. This is your time to shine! + +If you haven't even begun to prepare for 4.0, there are some really quick wins +you can use to your advantage. + +## Menu Icon in the Launcher + +The new launcher in 4.0 allows developers to provide a custom icon for +their watchapps and watchfaces. + +
    +
    + {% markdown %} + ![Launcher Icon](/images/blog/2016-08-19-pikachu-icon.png) + {% endmarkdown %} +
    +
    + {% markdown %} + ![Launcher >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-19-pikachu-launcher.png) + {% endmarkdown %} +
    +
    + +> If your `png` file is color, we will use the luminance of the image to add +> some subtle gray when rendering it in the launcher, rather than just black +> and white. Transparency will be preserved. + +You should add a 25x25 `png` to the `resources.media` section of the +`package.json` file, and set `"menuIcon": true`. +Please note that icons that are larger will be ignored and +your app will have the default icon instead. + +```js +"resources": { + "media": [ + { + "menuIcon": true, + "type": "png", + "name": "IMAGE_MENU_ICON", + "file": "images/icon.png" + } + ] +} +``` + +## Timeline Quick View + +This new feature really brings your timeline into the present, with your +next appointment appearing as a modal notification over the active watchface. + +We introduced the ``UnobstructedArea`` API in 4.0 to allow developers to detect +if the screen is being obstructed by a system modal. Below you can see two +examples of the Simplicity watchface. The Pebble on the left is not using the +``UnobstructedArea`` API, and the Pebble on the right is. + +
    +
    + {% markdown %} + ![Simplicity >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-08-19-simplicity-std.png) + {% endmarkdown %} +

    Watchface not updated

    +
    +
    + {% markdown %} + ![Simplicity >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-08-19-simplicity-qv.png) + {% endmarkdown %} +

    Respects new 4.0 unobstructed area

    +
    +
    + +You can detect changes to the available screen real-estate and then move, scale, +or hide their layers to achieve an optimal layout while the screen is partially +obscured. This provides a fantastic experience to your users. + +There's {% guide_link user-interfaces/unobstructed-area "a dedicated guide" %} +which goes into more detail about the ``UnobstructedArea`` API, it contains +examples and links to sample implementations. + +## AppGlances + +While your static app icon and app title from the `package.json` are being used as the +default to present your app to the user, +_AppGlances_ allow developers to control this content at runtime and +to provide meaningful feedback to users directly from the new launcher. +The API exposes the ability to dynamically change +the `icon` and `subtitle_template_string` text of your application in the +launcher. + + +

    Preview version of 4.0 launcher

    + +Utilizing the ``App Glance`` API doesn't need to be difficult. We've provided +guides, examples and sample applications for using the +{% guide_link user-interfaces/appglance-c "AppGlance C API" %} and the +{% guide_link user-interfaces/appglance-pebblekit-js "AppGlance PebbleKit JS API" %}. + +## The Diorite Platform + +The Diorite platform was created for the new Pebble 2 devices. Its display is +rectangular, 144x168 pixels, with 2 colors (black and white). It has a +microphone and heart rate monitor, but it doesn't have a compass. If your app +works with Aplite it will already work with Diorite, but there are some important considerations to note: + +* If you chose to limit your app to some platforms, you need to add `"diorite"` to your `targetPlatforms` in the `package.json`. +* Check for the appropriate usage of +{% guide_link best-practices/building-for-every-pebble#available-defines-and-macros "compiler directives" %}. + +In general, it's better to use capability and feature detection, rather than platform +detection. For example, when dealing with resources, use the suffix `~bw` instead of `~aplite` so they are picked on all black and white platforms. + +## What's Next + +We're really excited for the release of Pebble OS 4.0 and the new features it +brings. It's now time for you to take advantage of the new APIs and +enhance your existing projects, or even create entirely new ones! + +Why not build something like the +{% guide_link design-and-interaction/one-click-actions "One Click Action" %} +application, which utilizes the new ``App Glance`` and ``AppExitReason`` APIs. + +Please remember that we will promote watchfaces and watchapps +that make use of these new 4.0 APIs if you +submit them to the appstore **from August 31st onwards**. + +Let us know on [Twitter](https://twitter.com/pebbledev) if you build something +cool using the new APIs! We'd love to hear about your experiences with the SDK. + +Happy Hacking! + +Team Pebble + + diff --git a/devsite/source/_posts/2016-08-30-announcing-pebble-sdk4.md b/devsite/source/_posts/2016-08-30-announcing-pebble-sdk4.md new file mode 100644 index 00000000..600416a0 --- /dev/null +++ b/devsite/source/_posts/2016-08-30-announcing-pebble-sdk4.md @@ -0,0 +1,110 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Announcing Pebble SDK 4.0 +author: jonb +tags: +- Freshly Baked +--- + +Today we have the pleasure of announcing the release of Pebble SDK 4.0. We have +published an updated version of the Pebble Tool, the SDK itself, and we've +deployed 4.0 onto [CloudPebble]({{ site.links.cloudpebble }}). It's time to get +busy! + + + +## What's New + +Pebble OS 4.0 has been released and users with Pebble Time, Pebble Time Steel +and Pebble Time Round will be receiving the update today. + +We covered the new features in detail +[very recently](/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/), but +let's have a quick reminder of the new APIs and functionality that's included. + +### Rocky.js + +Javascript on the freakin' watch! Although still in beta, +[Rocky.js](/docs/rockyjs/) lets you start developing watchfaces in JavaScript +using standard Web APIs, which you can then run directly on your watch. It's an +embedded JavaScript revolution! + +Read the updated +[Rocky.js blog post](/blog/2016/08/15/introducing-rockyjs-watchfaces/) +to get started. + +### Timeline Quick View + +Timeline Quick View displays upcoming information from your timeline on your +watchface. We introduced the ``UnobstructedArea`` API to allow developers to +detect if the screen is being obstructed by a system modal, such as Timeline +Quick View. Developers can use this new API to adapt their watchface layout +according to the available screen real estate. + +Read the {% guide_link user-interfaces/unobstructed-area "UnobstructedArea guide" %} +to get started. + +### AppGlances + +With the new ``AppGlance`` API, developers can dynamically change the icon and +subtitle of their watchapp within the system launcher, at runtime. This allows +developers to provide meaningful feedback to users without the need for their +application to be launched. + +Read the +{% guide_link user-interfaces/appglance-c "AppGlance C guide" %} and the +{% guide_link user-interfaces/appglance-pebblekit-js "AppGlance PebbleKit JS guide" %} +to get started. + +### Diorite Platform + +The new Pebble 2 devices use apps built for the Diorite platform, so you'll need +SDK 4.0 to develop applications which target those devices. + +Take a look at the +{% guide_link tools-and-resources/hardware-information "Hardware Information guide" %} +to find out about the capabilities of the Pebble 2. + +### One Click Action application + +The One Click Action application pattern promotes a type of watchapp which +serves a single purpose. It launches, performs an action, and then terminates. +This pattern utilizes the new ``AppGlance`` and ``AppExitReason`` APIs. + +Take a look at the +{% guide_link design-and-interaction/one-click-actions "One Click Actions guide" %} +to get started. + +## Dude, Where's my HRM API? + +We had planned on shipping the Heart Rate API with 4.0, but it's been pushed +back into 4.1 so that we can add even more awesomeness. Pebble 2 devices will +begin to appear on wrists after firmware 4.1 ships, so you'll still have time to +begin implementing HRM data into your watchapps and watchfaces. We will announce +details of the HRM API as soon as it's available. + +## What's Next + +Please remember that we will promote watchfaces and watchapps that make use of +these new 4.0 APIs if you submit them to the appstore **from August 31st +onwards**. + +Let us know on [Twitter]({{ site.links.twitter }}) if you build something +cool using the new APIs! We'd love to hear about your experiences with the SDK. + +Happy Hacking! + +Team Pebble diff --git a/devsite/source/_posts/2016-09-06-timeline-the-future-of-the-past.md b/devsite/source/_posts/2016-09-06-timeline-the-future-of-the-past.md new file mode 100644 index 00000000..ad793e94 --- /dev/null +++ b/devsite/source/_posts/2016-09-06-timeline-the-future-of-the-past.md @@ -0,0 +1,119 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Timeline - The Future of the Past! +author: jonb +tags: +- Freshly Baked +--- + +If you’ve been living under a rock, or have just been sunning yourself on the +beach for the past week, you might have missed the +[launch of Pebble OS 4.0](/blog/2016/08/30/announcing-pebble-sdk4/). This new +version introduced some fantastic new features, such as the +[updated health app](https://blog.getpebble.com/2016/08/30/fw4-0/), +{% guide_link user-interfaces/unobstructed-area "timeline quick view" %}, +{% guide_link user-interfaces/appglance-c "app glances" %}, +[Rocky.js](/blog/2016/08/15/introducing-rockyjs-watchfaces/) and a new +[system launcher](/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/#menu-icon-in-the-launcher). + + +### The Past and the Furious + +However, there was one change which was met with mixed feedback from both users +and developers alike, the removal of timeline past. Previously accessible via +the UP button, timeline past was removed as part of the new 4.0 user +experience (UX). In 4.0 we introduced new APIs to give developers more options +to improve their application’s UX and potentially shift away from using the past +for interactions + +Unfortunately, this change prevented users from accessing any timeline pin which +wasn’t in the present or future, negatively affecting a number of apps and their +use cases for the timeline. + +We carefully listened to feedback and suggestions from our developer community +via the [forums](https://forums.pebble.com), +[Reddit](https://www.reddit.com/r/pebble), +[Twitter](https://twitter.com/pebbledev) and +[Discord]({{ site.links.discord_invite }}), and we are happy to announce that timeline past +has returned in the v4.0.1 update. Users who need to access the timeline past +can now assign it to one of their quick launch buttons. + +![Quick Launch >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-09-06-quick-launch.gif) + +And so, with the reintroduction of timeline past, balanced was restored, and +that was the end of the story. Or was it? + +### Back to the Future! + +If you’re the developer of an application which relies upon timeline past, you +will probably want to inform your users about how they can access timeline past, +as it will not be enabled by default. Fortunately, there are multiple ways in +which you can do this easily. + +#### 1. App Store Description + +Use the app store description of your app to explain that your application +utilizes timeline past and that users will need to assign it to quick launch. +For example: + +> This application utilizes pins in the timeline past. On your Pebble, go to +> ‘Settings’, ‘Quick Launch’, ‘Up Button’, then select ‘Timeline Past’. You can +> then access timeline past by long pressing UP. + +#### 2. Display a Splash Screen + +Add a splash screen to your application which only runs once, and display a +message informing users how to enable timeline past. You could use the +‘[about-window](https://www.npmjs.com/package/@smallstoneapps/about-window)’ +Pebble package for a really quick and easy solution. + +![About Window >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-09-06-about-window.png) + +#### 3. Display a One-Time Notification + +Display a +[system notification](https://developer.pebble.com/guides/communication/using-pebblekit-js/#showing-a-notification) +which only fires once, and display a message informing users how to enable +timeline past. + +![System Notification >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-09-06-system-notification.png) + +#### 4. Display a Timeline Notification + +Display a +[timeline notification](https://developer.pebble.com/guides/pebble-timeline/), +and display a message informing users how to enable timeline past. + +![Timeline Notification >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-09-06-timeline-notification.png) + +### The Future of the Past + +For now, developers can continue to utilize timeline past, but over time we +would like to provide a more diverse set of functionality that allows developers +to surface information to their users. For example, some use cases of timeline +past may be more appropriate as using an app glance, or timeline quick view +instead. + +### We’re Listening! + +Your feedback is incredibly important to us, it’s how we keep making awesome +things. We love to receive your product and feature +[suggestions](http://pbl.io/ideas) too. + +We’re particularly interested to hear about your use cases and ideas for +timeline as we travel further into the future! Let us know via +[the forums](https://forums.pebble.com), +[Twitter](https://twitter.com/pebbledev) and [Discord]({{ site.links.discord_invite }})! diff --git a/devsite/source/_posts/2016-10-11-Emery-SDK-Beta.md b/devsite/source/_posts/2016-10-11-Emery-SDK-Beta.md new file mode 100644 index 00000000..5233edcc --- /dev/null +++ b/devsite/source/_posts/2016-10-11-Emery-SDK-Beta.md @@ -0,0 +1,155 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: 4.2-beta4 SDK - Emery Edition! +author: jonb +tags: +- Freshly Baked +--- + +We're incredibly excited to announce the first public beta of the Pebble SDK +4.2. This is the first time that developers can experience the new 'Emery' +platform which is specifically created for the upcoming +[Pebble Time 2](https://www.pebble.com/buy-pebble-time-2-smartwatch). + + +![Pebble Time 2](/images/blog/2016-10-11-intro.jpg) + + +## All About Those Pixels + +The new display on the *Pebble Time 2* is almost 53% physically larger, and the +[Pixels per inch](https://en.wikipedia.org/wiki/Pixel_density) (PPI) has been +increased from 182 PPI to 202 PPI. This is a massive 200x228 pixels, compared to +144x168 on the *Pebble Time Steel*, that's an 88% increase in the total amount +of pixels!. + +Take a look at our +{% guide_link tools-and-resources/hardware-information "Hardware guide" %} for +further information about the specifications of the Pebble Time 2. + +Watchfaces and watchapps that have not been updated for the Emery platform will +run in *Bezel Mode*. This means they will appear centered on screen at their +original resolution (144x168), but due to the change in PPI, they appear +slightly smaller. + +![Pebble Time 2 Bezel](/images/blog/2016-10-11-bezel.png) +

    Left: Pebble Time Steel, Middle: Pebble Time 2 - +Bezel Mode, Right: Optimized for Pebble Time 2.
    +Concentricity +Watchface

    + +The increased number of pixels would immediately make you think that you can +just add more elements to your watchface design, but due to the increase in PPI, +existing elements and fonts may feel slightly smaller than expected when viewed +on an actual device. + +The image below simulates how the PPI difference makes a bezel mode application +feel smaller on the Pebble Time 2. + +![Pebble Time 2 PPI](/images/blog/2016-10-11-dpi-comparison.png) +

    Left: Pebble Time Steel, Right: Pebble Time 2 - Bezel Mode.

    + +We've now seen that the increased amount of pixels doesn't necessarily equate to +bigger text on our devices, due to the change in PPI, that's why we've created +the new [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API. + + +## Introducing the ContentSize API + +All existing Pebble smartwatches provide the ability for users to change the +size of text for notifications and some system UI components using *Settings > +Notifications > Text Size*. + +The new +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API now exposes this setting to developers, in order for +them to adapt their application design and layout based on this user preference. + +In the previous section, we saw how the PPI increase on Pebble Time 2 affected +the relative size of a watchface. In the image below you can see precisely how +text sizes are affected by the PPI change. + +![Pebble Time 2 ContentSize](/images/blog/2016-10-11-contentsize.png) +

    Yellow: Pebble Time Steel, Green: Pebble Time 2.

    + +To negate the effects of this reduced font size, the Emery platform uses larger +fonts by default. Developers can use the +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API to match this +behaviour within their own applications. + +The following table explains how the text size setting affects the content size +on each platform: + +Platform | Text Size: Small | Text Size: Medium | Text Size: Large +---------|------------------|-------------------|----------------- +Aplite, Basalt, Chalk, Diorite | ContentSize: Small | ContentSize: Medium | ContentSize: Large +Emery | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large + +Developers should aim to implement designs which adapt to the capabilities of +the device, but also the accessibility requirements of the user. The +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API satisfies both of these goals. + +For more information, read the +{% guide_link user-interfaces/content-size "ContentSize guide" %}. + + +## New for Rocky.js + +With the release of SDK 4.2, we're extremely proud to announce that +[Rocky.js](/docs/rockyjs/) watchfaces can be published into the Pebble appstore! +As usual, don't publish apps with the beta SDK into the appstore, please wait +for the final release. + +In addition to finalizing the memory contract for Rocky.js apps, we've also +added a few new APIs to work with: + + +### Memory Pressure + +The `memorypressure` event has been added to allow developers to handle the +situations which occur when the amount of memory available to their application +has changed. Initially we've only implemented the `high` memory pressure level, +which occurs right before your application is about to be terminated, due to a +lack of memory. If you can free sufficient memory within the callback for this +event, your application will continue to run. Please refer to the Rocky.js +[documentation](/docs/rockyjs/rocky/#RockyMemoryPressureCallback) and the memory +pressure [example application](https://github.com/pebble-examples/rocky-memorypressure). + + +### UserPreferences + +The first watchface setting exposed in the new `UserPreferences` object is +`contentSize`. This exposes the new +[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size) +API to Rocky.js watchface +developers. Find out more in the Rocky.js +[UserPreferences documentation](/docs/rockyjs/rocky/#userPreferences). + + +## What's Next + +Check out the [release notes](/sdk/changelogs/4.2-beta4/) for a full list of +changes and fixes that were included in SDK 4.2-beta4. + +Let us know on [Twitter]({{ site.links.twitter }}) if you built something +cool using the new APIs! We'd love to hear about your experiences with the SDK. + +Happy Hacking! + +Team Pebble diff --git a/devsite/source/_posts/2016-11-23-Announcing-Pebble-SDK-4.3.md b/devsite/source/_posts/2016-11-23-Announcing-Pebble-SDK-4.3.md new file mode 100644 index 00000000..493bedd7 --- /dev/null +++ b/devsite/source/_posts/2016-11-23-Announcing-Pebble-SDK-4.3.md @@ -0,0 +1,180 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Announcing Pebble SDK 4.3 +author: jonb +tags: +- Freshly Baked +--- + +Things have been a bit 'quiet' recently, but we're back with another fresh +Pebble SDK! In this release we've included one of the most frequently requested +APIs, exposed the raw HRM sensor value, released PebbleKit 4.0, plus added an +exciting new BLE HRM mode. + + +## It's. Oh. So quiet. Ssshhhhhh. + +We've added an often requested method for developers to detect if *Quiet Time* +is enabled. To say that we had a lot of requests for this would be an +understatement. + +*Quiet Time* can be enabled manually, via calendar events, or via scheduled +times, so we've made a simple method for querying whether it's currently active. + +All you need to do is check each minute if it's active. This is easily done +within the tick event, as follows: + +```c +static void handle_tick(struct tm *tick_time, TimeUnits units_changed) { + if (units_changed & MINUTE_UNIT) { + if (quiet_time_is_active()) { + // It's nice and quiet + } else { + // Starts another big riot + } + } +} +``` + +You may also peek this value, for example, to prevent your application from +vibrating during *Quiet Time*: + +```c +static void do_vibration() { + if (!quiet_time_is_active()) { + vibes_short_pulse(); + } +} +``` + + +## Raw HRM BPM + +The Pebble [Health API](``HealthService``) now exposes the raw BPM value from +the Heart Rate Monitor sensor. This raw value is not filtered and is useful for +applications which need to display the real-time sensor value, similar to the +Pebble Workout app. + +For example, in order to peek the current real-time HRM sensor, you would do the +following: + +```c +HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateRawBPM, time(NULL), time(NULL)); +if (hr & HealthServiceAccessibilityMaskAvailable) { + HealthValue val = health_service_peek_current_value(HealthMetricHeartRateRawBPM); + if(val > 0) { + // Display raw HRM value + static char s_hrm_buffer[8]; + snprintf(s_hrm_buffer, sizeof(s_hrm_buffer), "%lu BPM", (uint32_t)val); + text_layer_set_text(s_hrm_layer, s_hrm_buffer); + } +} +``` + +Find out more in the +{% guide_link events-and-services/hrm "Heart Rate Monitor API guide" %}. + + +## PebbleKit 4.0 + +PebbleKit for [iOS](https://github.com/pebble/pebble-ios-sdk/) and +[Android](https://github.com/pebble/pebble-android-sdk/) facilitates +communication between Pebble watchapps and 3rd party companion phone apps. +Version 4.0 introduces a number of new features and bug fixes, including: + +* Support for Pebble 2 *(required for iOS only)* +* Removal of Bluetooth Classic (iOS) +* Sports API - Support 3rd party HRM +* Sports API - Custom data field and label +* Sports API - Helper object to simplify usage, and minimize updates via +Bluetooth + +For further information about the specific platform changes, view the full +[iOS](https://github.com/pebble/pebble-ios-sdk/), or +[Android](https://github.com/pebble/pebble-android-sdk/) changelogs. + + +## BLE HRM Mode + +Ever wanted to use your *Pebble 2 HRM* as a dedicated BLE HRM device with your +favourite mobile fitness app? Well now you can! Firmware 4.3 now implements the +standard +[Bluetooth Heart Rate Service profile](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=239866). +In order to enable this profile, users must enable 'Pebble Health', then enable +'Heart Rate Monitoring' within the `Pebble Health` settings within the Pebble +mobile app. + +Developers who are looking to integrate directly with this profile should be +aware of the following: + +The Heart Rate Service UUID will be present in the advertising payload of +Pebble, but you must open the Bluetooth settings on the Pebble to make it +advertise and be discoverable over Bluetooth. + +Because it's highly likely that the Pebble is already connected to the phone, it +will not be advertising. Therefore, we recommend that your mobile app also +enumerates already connected devices, to see if any of them support the Heart +Rate Service. This is in addition to scanning for advertisements to find new +devices that support HRS. By enumerating connected devices, you improve the user +experience: users will not have to go into the Bluetooth settings menu on Pebble +if their Pebble is already connected to the phone. + +The first time an application subscribes to the Heart Rate Measurement +characteristic, a UI prompt will appear on the Pebble, asking the user to allow +sharing of their heart rate data. This permission is stored for the duration of +the Bluetooth connection. + +When HR data sharing is active, the HR sensor will run continuously at ~1Hz +sample rate. This means there is a significant battery impact when using this +feature for an extended period of time. + +When all applications have unsubscribed from the Heart Rate Measurement +characteristic, the HR sensor automatically returns to its default state. + +Mobile apps should unsubscribe from the Heart Rate Measurement characteristic as +soon as the data is no longer required. For example, a workout app should +unsubscribe when the workout has finished and/or the application is exited. + +If the Heart Rate Service is used continuously for a prolonged period of time +(currently 4hrs), a notification is presented on the watch to remind the user +that the HR data is still being shared. + +The user can stop sharing HRM data from *Settings > Bluetooth > Device*. If the +user chooses to stop sharing, the Bluetooth connection is briefly disconnected +and reconnected to forcefully remove all subscribers. Unfortunately the +Bluetooth GATT specification does not provide a better mechanism for a service +to unsubscribe subscribers, only subscribers can unsubscribe themselves. + +Service Characteristics Notes: + +* 'Sensor Contact' field is used. +* 'Body Sensor' characteristic is used. The value is constant though (It will +read "Wrist" / 0x02) +* 'RR Interval' is currently NOT used. +* 'Energy Expended' field is currently NOT used. +* 'Heart Rate Control Point' characteristic is currently NOT used. + +## What's Next + +Check out the [release notes](/sdk/changelogs/4.3/) for a full list of +changes and fixes that were included in SDK 4.3. + +Let us know on [Twitter]({{ site.links.twitter }}) if you built something +cool using the new APIs! We'd love to hear about your experiences with the SDK. + +Happy Hacking! + +Team Pebble diff --git a/devsite/source/_posts/2016-12-22-pebblejs-package.md b/devsite/source/_posts/2016-12-22-pebblejs-package.md new file mode 100644 index 00000000..f4684251 --- /dev/null +++ b/devsite/source/_posts/2016-12-22-pebblejs-package.md @@ -0,0 +1,145 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Pebble.js - Pebble Package Edition! +author: jonb +tags: +- Freshly Baked +--- + +We're pleased to announce that [Pebble.js](https://pebble.github.io/pebblejs/) +has now been [published](https://www.npmjs.com/package/pebblejs) as a +{% guide_link pebble-packages "Pebble Package" %}. Pebble.js lets developers +easily create Pebble applications using JavaScript by executing the JavaScript +code within the mobile application on a user's phone, rather than on the watch. + + +![Pebble.js as a Pebble Package](/images/blog/2016-12-22-pebble-js.jpg) + +Making Pebble.js a Pebble Package means Pebble.js projects can be converted to +standard Pebble C projects. This gives benefits like the ability to +easily utilize other Pebble Packages, such as +[Clay for Pebble](https://www.npmjs.com/package/pebble-clay), or easily +importing and exporting the project with +[CloudPebble]({{ site.links.cloudpebble }}). + +The Pebble.js package is using the +[`develop`](https://github.com/pebble/pebblejs/tree/develop) branch from the +[Pebble.js repository](https://github.com/pebble/pebblejs) on Github, and +can be updated independently from CloudPebble deployments. + +**It also supports the Diorite platform!**. + + +## Creating a New Project + +The initial steps vary if you're using CloudPebble or the Local SDK. Follow the +appropriate steps below to create a new project. + +#### CloudPebble + +If you're using CloudPebble, follow these initial steps: + +1. Create a new project: + * Project Type = Pebble C SDK + * Template = Empty Project + +2. Add the following dependency: + * Package Name = pebblejs + * Version = 1.0.0 + +3. Add a new `main.c` file and an `index.js` file. + +Now continue to add the [default project files](#default-project-files). + +#### Local SDK + +If you're using the Local SDK, just create a new C project with Javascript +support: + +```nc|text +$ pebble new-project PROJECTNAME --javascript +``` + +Now continue to add the [default project files](#default-project-files). + +#### Default Project Files + +Copy and paste these default project files into your project, replacing any +existing file contents: + +**your-main.c** + +```c +#include +#include "pebblejs/simply.h" + +int main(void) { + Simply *simply = simply_create(); + app_event_loop(); + simply_destroy(simply); +} +``` + +**index.js** + +```javascript +require('pebblejs'); +var UI = require('pebblejs/ui'); + +var card = new UI.Card({ + title: 'Hello World', + body: 'This is your first Pebble app!', + scrollable: true +}); + +card.show(); +``` + +At this point you should be able to compile and run your new project. + + +## Migrating an Existing Project + +Unfortunately there isn't an automated way to migrate your existing Pebble.js +project, but the steps are fairly straightforward. + +1. Create a new project, following the [steps above](#creating-a-new-project). + +2. Change the project settings to match your old project settings, including the +UUID. + +3. Copy your project resources (images, fonts etc.), and source files into the +new project. + +4. Compile and enjoy your new C project with Pebble.js support. + +> Note: `index.js` is a direct replacement for `app.js`, which may be your old +Javascript file. + + +## Next Steps? + +Want to add Clay support to your project? It's now easy by following the +standard Clay [Getting Started](https://github.com/pebble/clay#clay) +instructions! + +If you have any questions or problems, post the details on the +[developer forum](https://forums.pebble.com/t/pebble-js-pebble-package-edition/27315) +or [Discord](http://discord.gg/aRUAYFN). + +Happy Holidays! + +Jon Barlow + Team #PEBBLExFITBIT diff --git a/devsite/source/_sass/app.scss b/devsite/source/_sass/app.scss new file mode 100644 index 00000000..c91bbd59 --- /dev/null +++ b/devsite/source/_sass/app.scss @@ -0,0 +1,273 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import 'bourbon/bourbon'; + +@import 'colors'; + +@import 'base/base'; +@import 'responsive/responsive'; +@import 'font-awesome/font-awesome'; + +@import 'header'; +@import 'footer'; + +* { + @include box-sizing(border-box); +} + +body, +html { + height: 100%; + margin: 0; + padding: 0; +} + +body { + padding-top: $header-height; + background-color: $gray-01; +} + +.anchor[id], +.anchor[name] { + &::before { + content: ' '; + display: block; + height: $header-height; + margin-top: -0.7 * $header-height; + visibility: hidden; + } +} + +.hidden { + visibility: hidden; +} + +code { + background-color: $white; + border-radius: 2px; + color: darken($base-font-color, 20); + font-family: $monospace; + font-size: $base-font-size; + padding: 2px 4px; + box-shadow: inset 0 0 2px #bbb; +} + +a code { + color: darken($base-link-color, 10); +} + +.container { + max-width: none; + padding: 1em; + position: relative; +} + +.vcenter { + display: table-cell; + vertical-align: middle; +} + +.vcenter--wrapper { + display: table; + width: 100%; +} + +.hcenter { + display: table-cell; + position: relative; + left: 50%; + transform: translateX(-50%); +} + +.hcenter--wrapper { + display: table; + width: 100%; + text-align: center; + margin: 0 auto; +} + +.pagetitle { + text-transform: uppercase; + line-height: 1.1; + + a { + color: $base-text-color; + + &:hover { + text-decoration: underline; + } + } +} + +.pagetitle--skinny { + margin: 0; +} + +.pagesubtitle { + font-weight: normal; + font-size: 1.4em; + text-transform: uppercase; + color: #999; + margin-bottom: 1em; +} + +.noscript--hide { +} + +.noscript--show { + display: none; +} + +.git-contributors { + li { + display: inline-block; + margin-right: 0.5em; + + &:after { + content: ', '; + } + + &:last-child { + + &:after { + content: ''; + } + } + } +} + +.stop-scrolling { + height: 100%; + overflow: hidden; +} + +@import 'elements/alert'; +@import 'elements/buttons'; +@import 'elements/form'; +@import 'elements/mobile-nav'; +@import 'elements/highlight'; +@import 'elements/inline-list'; +@import 'elements/markdown'; +@import 'elements/screenshot'; +@import 'elements/sidebar'; +@import 'elements/spinner'; +@import 'elements/table'; +// @import 'elements/terminal'; +@import 'elements/toc'; +// @import 'elements/typeahead'; +// @import 'elements/video'; +@import 'elements/bigbox'; +@import 'elements/full-height'; +@import 'elements/sectionmenu'; +@import 'elements/search'; +@import 'elements/gray-box'; +@import 'elements/ribbon'; +@import 'elements/pagination'; +@import 'elements/landing-slider'; +@import 'elements/platform-choice'; +@import 'elements/dual-image'; + +@import 'sections/getting-started'; + +@import 'libs/slick'; +@import 'mmenu/jquery.mmenu.all.scss'; + +@import 'sections/events'; +@import 'sections/blog'; +@import 'sections/documentation'; +@import 'sections/examples'; +@import 'sections/guides'; +@import 'sections/retreat'; +@import 'sections/inspiration'; +@import 'sections/round'; +@import 'sections/sdk'; + +@import 'debug'; + +.mm-page { + height: 100%; + position: inherit; + + > div { + height: 100%; + } +} + +.mmenu__wrapper { + height: 100%; +} + +.mm-opened, +.mm-opening { + .mm-page { + margin-top: -1 * $header-height; + } + + .content { + padding-top: $header-height; + } +} + +.color-picker polygon { + cursor: pointer; +} + +.size-helper { + position: fixed; + bottom: 0; + right: 0; + background-color: #222; + color: #fff; + font-size: 0.8em; +} + +.modal-content { + background-color: $gray-01; +} + +.header--skinny { + font-weight: normal; +} + +.header--flat-bottom { + margin-bottom: 0; +} + +video { + max-width: 100%; +} + +.video--fullwidth { + width: 100%; +} + +.embed.embed--youtube { + position: relative; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + max-width: 100%; + + iframe, + .video-wrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} diff --git a/devsite/source/_sass/base/_base.scss b/devsite/source/_sass/base/_base.scss new file mode 100644 index 00000000..2bf129d0 --- /dev/null +++ b/devsite/source/_sass/base/_base.scss @@ -0,0 +1,40 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Bitters v0.10.0 +// http://bitters.bourbon.io + +// Variables +@import 'variables'; + +// Neat Settings -- uncomment if using Neat -- must be imported before Neat +// @import 'grid-settings'; + +// Mixins +@import 'mixins/flash'; + +// Extends +@import 'extends/button'; +@import 'extends/clearfix'; +@import 'extends/hide-text'; + +// Typography and Elements +@import 'typography'; +// @import 'forms'; +@import 'tables'; +@import 'lists'; +@import 'flashes'; +// @import 'buttons'; diff --git a/devsite/source/_sass/base/_buttons.scss b/devsite/source/_sass/base/_buttons.scss new file mode 100644 index 00000000..d5c0534e --- /dev/null +++ b/devsite/source/_sass/base/_buttons.scss @@ -0,0 +1,26 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +button, +input[type="submit"] { + @extend %button; + @include appearance(none); + border: none; + cursor: pointer; + user-select: none; + vertical-align: middle; + white-space: nowrap; +} diff --git a/devsite/source/_sass/base/_flashes.scss b/devsite/source/_sass/base/_flashes.scss new file mode 100644 index 00000000..4bb1a503 --- /dev/null +++ b/devsite/source/_sass/base/_flashes.scss @@ -0,0 +1,31 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%flash-alert { + @include flash($alert-color); +} + +%flash-error { + @include flash($error-color); +} + +%flash-notice { + @include flash($notice-color); +} + +%flash-success { + @include flash($success-color); +} diff --git a/devsite/source/_sass/base/_forms.scss b/devsite/source/_sass/base/_forms.scss new file mode 100644 index 00000000..b1c2e375 --- /dev/null +++ b/devsite/source/_sass/base/_forms.scss @@ -0,0 +1,94 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +fieldset { + background: lighten($base-border-color, 10); + border: 1px solid $base-border-color; + margin: 0 0 ($base-line-height / 2) 0; + padding: $base-line-height; +} + +input, +label, +select { + display: block; + font-family: $form-font-family; + font-size: $form-font-size; +} + +label { + font-weight: bold; + margin-bottom: $base-line-height / 4; + + &.required:after { + content: "*"; + } + + abbr { + display: none; + } +} + +textarea, +#{$all-text-inputs}, +select[multiple=multiple] { + @include box-sizing(border-box); + @include transition(border-color); + background-color: white; + border-radius: $form-border-radius; + border: 1px solid $form-border-color; + box-shadow: $form-box-shadow; + font-family: $form-font-family; + font-size: $form-font-size; + margin-bottom: $base-line-height / 2; + padding: ($base-line-height / 3) ($base-line-height / 3); + width: 100%; + + &:hover { + border-color: $form-border-color-hover; + } + + &:focus { + border-color: $form-border-color-focus; + box-shadow: $form-box-shadow-focus; + outline: none; + } +} + +textarea { + resize: vertical; +} + +input[type="search"] { + @include appearance(none); +} + +input[type="checkbox"], input[type="radio"] { + display: inline; + margin-right: $base-line-height / 4; +} + +input[type="file"] { + margin-bottom: $base-line-height / 2; + padding-bottom: ($base-line-height / 3); + width: 100%; +} + +select { + width: auto; + max-width: 100%; + margin-bottom: $base-line-height; +} diff --git a/devsite/source/_sass/base/_grid-settings.scss b/devsite/source/_sass/base/_grid-settings.scss new file mode 100644 index 00000000..fec0ac6c --- /dev/null +++ b/devsite/source/_sass/base/_grid-settings.scss @@ -0,0 +1,30 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import '../neat/neat-helpers'; + +// Neat Overrides +// $column: 90px; +// $gutter: 30px; +// $grid-columns: 12; +// $max-width: em(1200); + +// Neat Breakpoints +$medium-screen: em(640); +$large-screen: em(860); + +$medium-screen-up: new-breakpoint(min-width $medium-screen 4); +$large-screen-up: new-breakpoint(min-width $large-screen 8); diff --git a/devsite/source/_sass/base/_lists.scss b/devsite/source/_sass/base/_lists.scss new file mode 100644 index 00000000..b6473ae8 --- /dev/null +++ b/devsite/source/_sass/base/_lists.scss @@ -0,0 +1,46 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +ul, ol { + margin: 0; + padding: 0; + list-style-type: none; + + &%default-ul { + list-style-type: disc; + margin-bottom: $base-line-height / 2; + padding-left: $base-line-height; + } + + &%default-ol { + list-style-type: decimal; + margin-bottom: $base-line-height / 2; + padding-left: $base-line-height; + } +} + +dl { + margin-bottom: $base-line-height / 2; + + dt { + font-weight: bold; + margin-top: $base-line-height / 2; + } + + dd { + margin: 0; + } +} diff --git a/devsite/source/_sass/base/_tables.scss b/devsite/source/_sass/base/_tables.scss new file mode 100644 index 00000000..a759ad4e --- /dev/null +++ b/devsite/source/_sass/base/_tables.scss @@ -0,0 +1,33 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +table { + width: 100%; +} + +th { + font-weight: bold; + padding: ($base-line-height / 2) 0; + text-align: left; +} + +td { + padding: ($base-line-height / 2) 0; +} + +tr, td, th { + vertical-align: middle; +} diff --git a/devsite/source/_sass/base/_typography.scss b/devsite/source/_sass/base/_typography.scss new file mode 100644 index 00000000..7738bb53 --- /dev/null +++ b/devsite/source/_sass/base/_typography.scss @@ -0,0 +1,103 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +body { + -webkit-font-smoothing: antialiased; + background-color: $base-background-color; + color: $base-font-color; + font-family: $base-font-family; + font-size: $base-font-size; + line-height: $unitless-line-height; +} + +h1, h2, h3, h4, h5, h6 { + font-family: $header-font-family; + line-height: $header-line-height; + margin: 0 0 0.5em 0; + text-rendering: optimizeLegibility; // Fix the character spacing for headings +} + +h1 { + font-size: $base-font-size * 2.25; // 16 * 2.25 = 36px +} + +h2 { + font-size: $base-font-size * 2; // 16 * 2 = 32px +} + +h3 { + font-size: $base-font-size * 1.75; // 16 * 1.75 = 28px +} + +h4 { + font-size: $base-font-size * 1.5; // 16 * 1.5 = 24px +} + +h5 { + font-size: $base-font-size * 1.25; // 16 * 1.25 = 20px +} + +h6 { + font-size: $base-font-size; +} + +p { + margin: 0 0 ($base-line-height * .5); +} + +a { + @include transition(color 0.1s linear); + color: $base-link-color; + text-decoration: none; + + &:hover { + color: $hover-link-color; + } + + &:active, &:focus { + color: $hover-link-color; + outline: none; + } +} + +hr { + border-bottom: 1px solid $base-border-color; + border-left: none; + border-right: none; + border-top: none; + margin: $base-line-height 0; +} + +img { + margin: 0; + max-width: 100%; +} + +blockquote { + border-left: 2px solid $base-border-color; + color: lighten($base-font-color, 15); + margin: $base-line-height 0; + padding-left: $base-line-height / 2; +} + +cite { + color: lighten($base-font-color, 25); + font-style: italic; + + &:before { + content: '\2014 \00A0'; + } +} diff --git a/devsite/source/_sass/base/_variables.scss b/devsite/source/_sass/base/_variables.scss new file mode 100644 index 00000000..6c752a5f --- /dev/null +++ b/devsite/source/_sass/base/_variables.scss @@ -0,0 +1,61 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Typography +$sans-serif: 'Open Sans'; +$serif: $georgia; +$monospace: 'Inconsolata'; +$base-font-family: $sans-serif; +$header-font-family: $base-font-family; + +// Sizes +$base-font-size: 15px; +$base-line-height: 1.4 * $base-font-size; +$unitless-line-height: 1.4; // Strip units from line-height: https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#Prefer_unitless_numbers_for_line-height_values +$header-line-height: 1.3; +$base-border-radius: em(3); + +// Background Color +$base-background-color: #fff; + +// Font Colors +$base-font-color: $gray-03; +$base-accent-color: $lightblue; + +// Link Colors +$base-link-color: $base-accent-color; +$hover-link-color: darken($base-accent-color, 15); +$base-button-color: $base-link-color; +$hover-button-color: $hover-link-color; + +// Border color +$base-border-color: $gray-03; + +// Flash Colors +$alert-color: $yellow; +$error-color: $red; +$notice-color: $yellow; +$success-color: $green; + +// Forms +$form-border-color: $base-border-color; +$form-border-color-hover: darken($base-border-color, 10); +$form-border-color-focus: $base-accent-color; +$form-border-radius: $base-border-radius; +$form-box-shadow: inset 0 1px 3px rgba(0,0,0,0.06); +$form-box-shadow-focus: $form-box-shadow, 0 0 5px rgba(darken($form-border-color-focus, 5), 0.7); +$form-font-size: $base-font-size; +$form-font-family: $base-font-family; diff --git a/devsite/source/_sass/base/extends/_button.scss b/devsite/source/_sass/base/extends/_button.scss new file mode 100644 index 00000000..99a8e8ea --- /dev/null +++ b/devsite/source/_sass/base/extends/_button.scss @@ -0,0 +1,33 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%button { + -webkit-font-smoothing: antialiased; + background-color: $base-button-color; + border-radius: $base-border-radius; + color: white; + display: inline-block; + font-size: $base-font-size; + font-weight: bold; + line-height: 1; + padding: .75em 1em; + text-decoration: none; + + &:hover { + background-color: $hover-button-color; + color: white; + } +} diff --git a/devsite/source/_sass/base/extends/_clearfix.scss b/devsite/source/_sass/base/extends/_clearfix.scss new file mode 100644 index 00000000..a0c9a923 --- /dev/null +++ b/devsite/source/_sass/base/extends/_clearfix.scss @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%clearfix { + @include clearfix; +} diff --git a/devsite/source/_sass/base/extends/_hide-text.scss b/devsite/source/_sass/base/extends/_hide-text.scss new file mode 100644 index 00000000..ef26a0aa --- /dev/null +++ b/devsite/source/_sass/base/extends/_hide-text.scss @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%hide-text { + @include hide-text; +} diff --git a/devsite/source/_sass/base/mixins/_flash.scss b/devsite/source/_sass/base/mixins/_flash.scss new file mode 100644 index 00000000..bce4f407 --- /dev/null +++ b/devsite/source/_sass/base/mixins/_flash.scss @@ -0,0 +1,31 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@mixin flash($color) { + background: $color; + color: darken($color, 60); + font-weight: bold; + margin-bottom: $base-line-height / 2; + padding: $base-line-height / 2; + + a { + color: darken($color, 70); + + &:hover { + color: darken($color, 90); + } + } +} diff --git a/devsite/source/_sass/col.scss b/devsite/source/_sass/col.scss new file mode 100644 index 00000000..ecebf461 --- /dev/null +++ b/devsite/source/_sass/col.scss @@ -0,0 +1,39 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$pebble-black: #111; +$pebble-blue: #16b5d8; +$pebble-blue-alt: darken(#16b5d8, 20); +$pebble-gray: #959fa0; +$pebble-green: #aed70d; +$pebble-orange: #f57216; +$pebble-pink: #ff568a; +$pebble-red: #b93231; +$pebble-silver: #bdc3c7; + +$green: #00a94b; +$blue: #16aee5; +$red: #ff1200; +$dark-grey: #444; +$darker-grey: #333; +$pink: #d84ba1; +$yellow: #e5e327; +$purple: #9d49d5; +$orange: #e79b22; + +$color-guides: $yellow; +$color-documentation: $pink; +$color-community: $purple; \ No newline at end of file diff --git a/devsite/source/_sass/colors.scss b/devsite/source/_sass/colors.scss new file mode 100644 index 00000000..3a115137 --- /dev/null +++ b/devsite/source/_sass/colors.scss @@ -0,0 +1,97 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$pebble-black: #111; +$pebble-blue: #16b5d8; +$pebble-blue-alt: darken(#16b5d8, 20); +$pebble-gray: #959fa0; +$pebble-green: #aed70d; +$pebble-orange: #f57216; +$pebble-pink: #ff568a; +$pebble-red: #b93231; +$pebble-silver: #bdc3c7; + +$header-orange: #ef6725; + +$white: #fff; +$breakfast-room-white: #FFFFF0; +$black: #000; +$green: #00a94b; +$blue: #16aee5; +$red: #ff1200; +$dark-grey: #444; +$darker-grey: #333; +$pink: #d84ba1; +$yellow: #e5e327; +$mustard-yellow: #f5d04c; +$purple: #9d49d5; +$orange: #e59a25; +$orange-02: #e27c3f; +$lightblue: #4cafe2; +$darkerlightblue: #4c96e2; +$dark-red: #d55248; +$dark-blue: #3f94c0; +$turquoise: #35a2b2; +$android-green: #A4C739; + +$bigbox--blue: $lightblue; +$bigbox--green: #22a048; +$bigbox--darkgreen: darken($bigbox--green, 20); +$bigbox--dark-blue: darken($darkerlightblue, 20); + +$color-guides: $yellow; +$color-docs: $pink; +$color-community: $purple; +$color-more: $green; +$color-getting-started: $dark-red; +$color-sdk: $lightblue; +$color-appstore: $orange; +$color-other: $dark-grey; + +$gray-01: #f4f4f4; +$gray-02: #444; +$gray-03: #484848; +$gray-04: #eee; +$gray-05: #e5e4e4; +$gray-06: #d6d6d6; +$gray-07: #272822; +$gray-08: #ddd; +$gray-09: #bebebe; +$gray-10: #d9d9d9; +$gray-11: #666; +$gray-12: #ccc; + +$bigbox--blue: $lightblue; +$bigbox--green: #22a048; + +$color-guides: $yellow; +$color-docs: $pink; +$color-community: $purple; +$color-more: $green; +$color-getting-started: $dark-red; +$color-sdk: $lightblue; +$color-appstore: $gray-03; +$color-blog: $orange-02; +$color-examples: #2c67ce; + +$modifier-color-names: 'guides', 'docs', 'community', 'more', 'getting-started', + 'sdk', 'appstore', 'blog'; + +$modifier-colors: $color-guides, $color-docs, $color-community, $color-more, + $color-getting-started, $color-sdk, $color-appstore, + $color-blog; + +$modifier-colors-fg: $white $white $white $white $white $white $white $white; diff --git a/devsite/source/_sass/debug.scss b/devsite/source/_sass/debug.scss new file mode 100644 index 00000000..31d6656a --- /dev/null +++ b/devsite/source/_sass/debug.scss @@ -0,0 +1,24 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.visibility-checker { + background-color: #444; + bottom: 0; + color: #fff; + position: fixed; + right: 0; + z-index: 10000; +} diff --git a/devsite/source/_sass/elements/alert.scss b/devsite/source/_sass/elements/alert.scss new file mode 100644 index 00000000..55cfd84c --- /dev/null +++ b/devsite/source/_sass/elements/alert.scss @@ -0,0 +1,79 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.alert { + background-color: #ddd; + color: $white; + margin-bottom: 1em; + padding: 0.5em; + + p { + font-size: 0.9em; + + &:last-child { + margin-bottom: 0; + } + } + + ul { + @extend %default-ul; + + &:last-child { + margin-bottom: 0; + } + } +} + +.alert--large { + font-size: 1.1em; + padding: 1em; +} + +.alert--medium { + font-size: 1em; + line-height: 1.3; + padding: 0.7em; +} + +$alert-colors: $white $green $blue $red $purple $yellow $orange $lightblue $dark-red $color-blog; +$alert-names: 'white' 'green' 'blue' 'red' 'purple' 'yellow' 'orange' 'lightblue' 'dark-red' 'blog'; + +@each $color in $alert-colors { + $i: index($alert-colors, $color); + + .alert--fg-#{nth($alert-names, $i)} { + color: $color; + + a { + color: $color; + text-decoration: underline; + } + } + + .alert--bg-#{nth($alert-names, $i)} { + background-color: $color; + } +} + +.alert--error { + background-color: $dark-red; + color: #fff; +} + +.alert--success { + background-color: $green; + color: #fff; +} diff --git a/devsite/source/_sass/elements/bigbox.scss b/devsite/source/_sass/elements/bigbox.scss new file mode 100644 index 00000000..763facce --- /dev/null +++ b/devsite/source/_sass/elements/bigbox.scss @@ -0,0 +1,188 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.bigbox { + text-align: center; + padding: 2em; + overflow: hidden; + position: relative; + + p { + font-size: 1.2 * $base-font-size; + } + + h2, + h3, + h4 { + font-weight: 600; + + a { + color: $base-font-color; + + &:hover { + text-decoration: underline; + } + } + } + + .btn { + margin-top: 1em; + } + + .vcenter, + .vcenter--wrapper { + height: 100%; + width: 100%; + } +} + +.bigbox--half { + height: 50%; +} + +.bigbox--third { + height: 33.33%; +} + +.bigbox--lightblue { + background-color: $lightblue; + color: $white; +} + +.bigbox--blue { + background-color: $bigbox--blue; + color: $white; +} + +.bigbox--dark-blue { + background-color: $bigbox--blue; + color: $white; +} + +.bigbox--green { + background-color: $bigbox--green; + color: $white; +} + +.bigbox--dark-green { + background-color: $bigbox--darkgreen; + color: $white; +} + +.bigbox--orange { + background-color: $orange; + color: $white; +} + +.bigbox--red { + background-color: $red; + color: $white; +} + +.bigbox--dark-red { + background-color: $dark-red; + color: $white; +} + +.bigbox--lightgray { + background-color: $gray-04; +} + +.bigbox--gray { + background-color: $gray-05; +} + +.bigbox--darkgray { + background-color: $gray-02; + color: $white; +} + + +.bigbox--meetups--map { + background-size: cover; + background-repeat: no-repeat; + background-position: center center; +} + +.bigbox--android { + color: $white; + background-color: $android-green; + + i { + font-size: 6em; + padding-bottom: 20px; + } +} + + +.bigbox--ios { + color: $black; + background-color: $white; + + i { + font-size: 6em; + padding-bottom: 20px; + } +} + +.bigbox__banner { + position: absolute; + top: 0; + left: 0; + right: 0; + background-color: $green; + color: $white; + padding: 0.50rem; + + &:hover { + color: $white; + text-decoration: underline; + } +} + +.bigbox--has-banner { + padding-top: 2.5em; +} + +.bigbox__highlight { + &:hover { + -webkit-filter: brightness(110%); + } +} + +.bigbox__guides-beautiful { + background-color: $blue; + color: $breakfast-room-white; + background-image: url('/assets/images/landing-page/first_time_bg.png'); +} + +.bigbox__guides-building { + background-color: $gray-02; + color: $breakfast-room-white; + background-image: url('/assets/images/landing-page/back_for_more_bg.png'); +} + +.bigbox__guides-publishing { + background-color: $blue; + color: $breakfast-room-white; + background-image: url('/assets/images/landing-page/meta_bg.png'); +} + +.bigbox__guides-interactive { + background-color: $gray-02; + color: $breakfast-room-white; + background-image: url('/assets/images/landing-page/interactive_bg.png'); +} diff --git a/devsite/source/_sass/elements/buttons.scss b/devsite/source/_sass/elements/buttons.scss new file mode 100644 index 00000000..6f33a35e --- /dev/null +++ b/devsite/source/_sass/elements/buttons.scss @@ -0,0 +1,129 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.btn { + background-color: $white; + border-radius: 4px; + color: $darker-grey; + font-weight: 700; + padding: 0.5em 2em; + text-transform: uppercase; + display: inline-block; + text-align: center; + + &:hover { + color: $darker-grey; + } +} + +input.btn, +submit.btn { + border: none; + font-size: inherit; +} + +$btn-colors: $white $gray-02 $green $blue $red $purple $yellow $orange $lightblue $dark-red $orange-02 $android-green; +$btn-names: 'white' gray-02 'green' 'blue' 'red' 'purple' 'yellow' 'orange' 'lightblue' 'dark-red' 'orange-02' 'android-green'; + +@each $color in $btn-colors { + $i: index($btn-colors, $color); + + .btn--fg-#{nth($btn-names, $i)} { + color: $color; + + &:hover, + &:active, + &:focus { + color: $color; + } + } + + .btn--bg-#{nth($btn-names, $i)} { + background-color: $color; + + &:hover, + &:active, + &:focus { + background-color: darken($color, 10); + } + } +} + +// Generate a pair of button classes for each section (original and alternate). +// This should be used when adding buttons in each section, in case we change +// the colours in the future. +@each $color in $modifier-colors { + $i: index($modifier-colors, $color); + + .btn--#{nth($modifier-color-names, $i)} { + background-color: $color; + color: nth($modifier-colors-fg, $i); + + &:hover, + &:focus { + color: nth($modifier-colors-fg, $i); + background-color: darken($color, 10); + } + + a { + color: nth($modifier-colors-fg, $i); + } + } + + .btn--#{nth($modifier-color-names, $i)}--alt { + background-color: complement($color); + color: nth($modifier-colors-fg, $i); + + &:hover, + &:focus { + color: nth($modifier-colors-fg, $i); + background-color: darken(complement($color), 10); + } + + a { + color: nth($modifier-colors-fg, $i); + } + } +} + + +.btn + .btn { + margin-left: 1em; +} + +.btn--wide { + width: 100%; + padding: 0.5em 0; +} + +.btn--center { + display: table; + margin: 0 auto; +} + +.btn--small { + font-size: 0.9em; + padding: 0.5em 1.5em; +} + +.btn--square { + border-radius: 0 +} + +.btn--ios { + color: $white; + background-color: $black; +} \ No newline at end of file diff --git a/devsite/source/_sass/elements/calendar.scss b/devsite/source/_sass/elements/calendar.scss new file mode 100644 index 00000000..a5729eba --- /dev/null +++ b/devsite/source/_sass/elements/calendar.scss @@ -0,0 +1,48 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.calendar { + + td, + th { + padding: 0.5em 0; + text-align: center; + } + + tbody { + td { + width: 1/7 * 100%; + } + } +} + +.calendar__month { + text-align: center; +} + +.calendar__day--off { + color: lighten($base-text-color, 50); +} + +.calendar__day--today { + color: $base-link-color; + font-weight: bold; +} + +.calendar__day--event { + background-color: $base-link-color; + color: #fff; +} diff --git a/devsite/source/_sass/elements/dual-image.scss b/devsite/source/_sass/elements/dual-image.scss new file mode 100644 index 00000000..ecb200b8 --- /dev/null +++ b/devsite/source/_sass/elements/dual-image.scss @@ -0,0 +1,27 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* View 2 images side by side */ +.pebble-dual-image { + overflow: hidden; + margin: 2em 1em; + .panel { + width: 50%; + display: inline; + float: left; + min-width: 244px; + } +} diff --git a/devsite/source/_sass/elements/form.scss b/devsite/source/_sass/elements/form.scss new file mode 100644 index 00000000..ea51a446 --- /dev/null +++ b/devsite/source/_sass/elements/form.scss @@ -0,0 +1,115 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.form__group { + display: table; + margin-bottom: 1em; + width: 100%; + + input, + select, + textarea { + background-color: white; + border: 1px solid $gray-06; + border-left: none; + border-radius: 0 5px 5px 0; + color: $gray-02; + display: table-cell; + font-family: $sans-serif; + font-size: 15px; + margin: 0; + padding: 7px; + text-align: left; + width: 100%; + + &.no-label { + border-radius: 5px; + } + + &:focus { + box-shadow: none; + outline: none; + } + } + + textarea { + text-align: left; + margin-bottom: -5px; + } + + select { + height: 2em; + } + + .select-style { + background-color: white; + border-radius: 0 5px 5px 0; + border: 1px solid $gray-06; + border-left-width: 0; + color: transparentize(#444, 0.5); + display: table-cell; + font-family: $sans-serif; + font-size: 15px; + margin: 0; + overflow: hidden; + padding: 3px 7px; + text-align: left; + width: 100%; + + select { + -webkit-appearance: none; + background-image: none; + background: transparent; + border: none; + box-shadow: none; + padding: 5px 8px; + width: 130%; + } + + select:focus { + outline: none; + } + + &.no-label { + border-left-width: 1px; + } + } + + label { + background-color: #fff; + border: 1px solid $gray-06; + border-right: none; + border-radius: 5px 0 0 5px; + color: #111; + display: table-cell; + font-size: 14px; + font-weight: 400; + margin: 0; + padding: 7px 10px; + vertical-align: top; + white-space: nowrap; + width: 25%; + } + + input.mce_inline_error { + background-color: lighten($dark-red, 40); + border: 1px solid $dark-red; + } +} + +input[type="submit"] { + cursor: pointer; +} diff --git a/devsite/source/_sass/elements/full-height.scss b/devsite/source/_sass/elements/full-height.scss new file mode 100644 index 00000000..89e1ed54 --- /dev/null +++ b/devsite/source/_sass/elements/full-height.scss @@ -0,0 +1,64 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.full-height { + height: 100%; +} + +@include bp(xs) { + .full-height--xs { + height: 100%; + } +} + +@include bp(s) { + .full-height--s { + height: 100%; + } +} + +@include bp(m) { + .full-height--m { + height: 100%; + } +} + +@include bp(l) { + .full-height--l { + height: 100%; + } +} + +.half-height { + height: 50%; +} + +.half-height--bottom { + height: 50%; + padding-top: 50%; +} + +.one-third-height { + height: 33%; +} + +.two-thirds-height { + height: 67%; +} + +.quarter-height { + height: 25%; +} diff --git a/devsite/source/_sass/elements/gray-box.scss b/devsite/source/_sass/elements/gray-box.scss new file mode 100644 index 00000000..598dd3b9 --- /dev/null +++ b/devsite/source/_sass/elements/gray-box.scss @@ -0,0 +1,68 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.gray-box { + background-color: $gray-04; + border-radius: $base-border-radius; + margin-bottom: 1em; + overflow-x: hidden; + padding: 1em; + + ul { + margin-bottom: 1em; + + &:last-child { + margin-bottom: 0; + } + } + + li { + margin-bottom: 0.2em; + + &:last-child { + margin-bottom: 0; + } + } + + h3 { + color: $gray-02; + font-size: 1.3em; + font-weight: 600; + text-transform: uppercase; + + a, + a:hover, + a:active { + color: $gray-02; + } + + a:hover, + a:active { + text-decoration: underline; + } + } +} + +.gray-box--fixed { + overflow-y: auto; +} + +.white-box { + background-color: $white; + border-radius: $base-border-radius; + margin-bottom: 1em; + padding: 0.5em; +} diff --git a/devsite/source/_sass/elements/highlight.scss b/devsite/source/_sass/elements/highlight.scss new file mode 100644 index 00000000..8ac17b9e --- /dev/null +++ b/devsite/source/_sass/elements/highlight.scss @@ -0,0 +1,297 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.highlight { + background-color: $gray-07; + border-radius: $base-border-radius; + color: #f8f8f2; + font: 13px/1.3em $monospace; + margin: 0 0 0.75em; + padding: 10px; + position: relative; + + @include bp-max (xs) { + font-size: 12px; + } + + &.squished { + border-radius: 0; + margin: 0; + } + + pre { + margin: 0; + overflow-x: auto; + } + + .code-copy-link { + background-color: $turquoise; + border-radius: $base-border-radius; + color: $white; + opacity: 0.1; + padding: 0.3em 0.3em 0.3em 0.35em; + position: absolute; + right: 0.4em; + top: 0.4em; + transition: opacity 0.2s; + + &.zeroclipboard-is-hover { + opacity: 1; + } + + span { + display: inline-block; + margin-right: 0.5em; + } + } + + &:hover { + .code-copy-link { + opacity: 1; + } + } + + // Error + .err { + background-color: #1e0010; + color: #960050; + } + + // Keyword + .k { + color: #66d9ef; + } + + // Literal + .l { + color: #ae81ff; + } + + // Name + .n { + color: #f8f8f2; + } + + // Operator + .o { + color: #f92672; + } + + // Punctuation + .p { + color: #f8f8f2; + } + + // Comment.Multiline + // Comment.Preproc + // Comment.Single + // Comment.Special + // Comment + .c, + .cm, + .cp, + .c1, + .cs { + color: #e4b21f; + font-weight: 600; + } + + // Generic.Emph + .ge { + font-style: italic; + } + + // Generic.Strong + .gs { + font-weight: bold; + } + + // Keyword.Constant + // Keyword.Declaration + .kc, + .kd { + color: #66d9ef; + } + + // Keyword.Namespace + .kn { + color: #f92672; + } + + // Keyword.Pseudo + // Keyword.Reserved + // Keyword.Type + .kp, + .kr, + .kt { + color: #66d9ef; + } + + // Literal.Date + .ld { + color: #e6db74; + } + + // Literal.Number + .m { + color: #ae81ff; + } + + // Literal.String + .s { + color: #e6db74; + } + + // Name.Attribute + .na { + color: #a6e22e; + } + + // Name.Builtin + .nb { + color: #f8f8f2; + } + + // Name.Class + .nc { + color: #a6e22e; + } + + // Name.Constant + .no { + color: #66d9ef; + } + + // Name.Decorator + .nd { + color: #a6e22e; + } + + // Name.Entity + .ni { + color: #f8f8f2; + } + + // Name.Exception + // Name.Function + .ne, + .nf { + color: #a6e22e; + } + + // Name.Label + // Name.Namespace + .nl, + .nn { + color: #f8f8f2; + } + + // Name.Other + .nx { + color: #a6e22e; + } + + // Name.Property + .py { + color: #f8f8f2; + } + + // Name.Tag + .nt { + color: #f92672; + } + + // Name.Variable + .nv { + color: #f8f8f2; + } + + // Operator.Word + .ow { + color: #f92672; + } + + // Text.Whitespace + .w { + color: #f8f8f2; + } + + // Literal.Number.Float + // Literal.Number.Hex + // Literal.Number.Integer + // Literal.Number.Oct + .mf, + .mh, + .mi, + .mo { + color: #ae81ff; + } + + // Literal.String.Backtick + // Literal.String.Char + // Literal.String.Doc + // Literal.String.Double + .sb, + .sc, + .sd, + .s2 { + color: #e6db74; + } + + // Literal.String.Escape + .se { + color: #ae81ff; + } + + // Literal.String.Heredoc + // Literal.String.Interpol + // Literal.String.Other + // Literal.String.Regex + // Literal.String.Single + // Literal.String.Symbol + .sh, + .si, + .sx, + .sr, + .s1, + .ss { + color: #e6db74; + } + + // Name.Builtin.Pseudo + // Name.Variable.Class + // Name.Variable.Global + // Name.Variable.Instance + .bp, + .vc, + .vg, + .vi { + color: #f8f8f2; + } + + // Literal.Number.Integer.Long + .il { + color: #ae81ff; + } +} + +.modal-content .highlight { + position: relative; +} + +.highlight--large { + font: 15px/1.3em $monospace; +} diff --git a/devsite/source/_sass/elements/inline-list.scss b/devsite/source/_sass/elements/inline-list.scss new file mode 100644 index 00000000..8e70ca05 --- /dev/null +++ b/devsite/source/_sass/elements/inline-list.scss @@ -0,0 +1,65 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@charset "UTF-8"; + +ul.inline-list { + margin: 0; + padding: 0; + + li { + display: inline-block; + + a { + display: inline-block; + } + } +} + +.inline-list--piped li::after { + color: lighten($base-font-color, 30); + content: ' | '; + display: inline; +} + +.inline-list--piped li:last-child::after { + content: ''; +} + +.inline-list--dashed li::after { + color: lighten($base-font-color, 40); + content: ' — '; + display: inline; +} + +.inline-list--dashed li:last-child::after { + content: ''; +} + +.inline-list--tags { + + li { + background-color: #444; + border-radius: 2px; + color: $white; + display: inline-block; + font-size: 0.65em; + font-weight: bold; + margin-right: 0.2rem; + padding: 0.1rem 0.3rem; + text-transform: uppercase; + } +} \ No newline at end of file diff --git a/devsite/source/_sass/elements/landing-slider.scss b/devsite/source/_sass/elements/landing-slider.scss new file mode 100644 index 00000000..90c25a18 --- /dev/null +++ b/devsite/source/_sass/elements/landing-slider.scss @@ -0,0 +1,32 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.landing-slider__slide { + position: relative; + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + + .vcenter { + height: 300px; + } +} + +.landing-slider__strap { + background-color: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 1em 2em; +} diff --git a/devsite/source/_sass/elements/markdown.scss b/devsite/source/_sass/elements/markdown.scss new file mode 100644 index 00000000..5ab1be35 --- /dev/null +++ b/devsite/source/_sass/elements/markdown.scss @@ -0,0 +1,126 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.markdown { + + ul { + @extend %default-ul; + } + + ol { + @extend %default-ol; + } + + $table-border-color: $gray-06; + $table-border: 1px solid $table-border-color; + $table-background: $white; + $table-header-background: $gray-02; + $table-header-color: $white; + $table-hover-color: darken($table-background, 5); + $table-stripe-color: darken($table-background, 4); + $table-stripe-color-hover: darken($table-stripe-color, 5); + $table-padding: 0.5em; + + table { + border: $table-border; + border-collapse: separate; + border-left: 0; + border-spacing: 0; + margin-bottom: 1em; + font-size: 0.9em; + } + + tbody { + background-color: $table-background; + + td { + border-bottom: 0; + border-left: 1px solid $table-border-color; + border-top: 1px solid $table-border-color; + padding: $table-padding; + } + + tr:hover > td, + tr:hover > th { + background-color: $table-hover-color; + } + + tr:nth-child(even) { + background-color: $table-stripe-color; + + &:hover > td { + background-color: $table-stripe-color-hover; + } + } + } + + thead { + + th { + background-color: $table-header-background; + border-bottom: 0; + border-left: 1px solid $table-border-color; + color: $table-header-color; + padding: $table-padding; + font-weight: 600; + } + } + + svg { + margin-bottom: 1em; + } +} + +.markdown--staff, +.markdown--user { + // margin: 0 auto; + max-width: 43em; +} + +.markdown--staff { + + img { + display: block; + margin: 0 auto; + max-width: 100%; + } +} + +.markdown--legal { + + p { + text-align: justify; + } + + ol { + list-style: none; + margin: 0; + padding: 0; + + li { + margin-bottom: 0.5em; + } + + li strong:first-child { + display: inline-block; + width: 4em; + } + } +} + +.markdown--retreat { + padding-right: 1.5rem; +} diff --git a/devsite/source/_sass/elements/mobile-nav.scss b/devsite/source/_sass/elements/mobile-nav.scss new file mode 100644 index 00000000..642385ef --- /dev/null +++ b/devsite/source/_sass/elements/mobile-nav.scss @@ -0,0 +1,113 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$hamburger-width: $header-height; + +.mobile-nav { + background-color: $gray-03; + bottom: 0; + left: 0; + overflow-y: auto; + position: fixed; + top: $header-height; + width: 90%; + z-index: 200; + + .open { + display: inherit; + } + + .mainmenu__item a { + color: #fff; + display: block; + font-size: 13px; + font-weight: bold; + height: 4.4em; + margin: 0.5em 0; + padding: 1.5em; + position: relative; + text-transform: uppercase; + + &:hover { + background-color: lighten($gray-03, 10); + } + } + + .mainmenu__item.active a { + background-color: $gray-06; + color: $gray-03; + } +} + +.mobile-nav__hamburger { + display: none; + + @include bp-max (xs) { + border-bottom: 1px solid $gray-10; + display: block; + position: fixed; + background-color: $white; + left: 0; + width: $hamburger-width; + top: 0; + height: $header-height; + padding: $base-font-size / 1.5 0; + font-size: $base-font-size * 2; + text-align: center; + color: $gray-03; + + &:hover, + &:active { + color: $gray-03; + } + } +} + +.mobile-nav--home { + border-left: 5px solid transparent; +} + +.mobile-nav--tutorials { + border-left: 5px solid $color-getting-started; +} + +.mobile-nav--sdk { + border-left: 5px solid $color-sdk; +} + +.mobile-nav--guides { + border-left: 5px solid $color-guides; +} + +.mobile-nav--docs { + border-left: 5px solid $color-docs; +} + +.mobile-nav--examples { + border-left: 5px solid $color-examples; +} + +.mobile-nav--community { + border-left: 5px solid $color-community; +} + +.mobile-nav--blog { + border-left: 5px solid $color-blog; +} + +.mobile-nav--more { + border-left: 5px solid $color-more; +} diff --git a/devsite/source/_sass/elements/pagination.scss b/devsite/source/_sass/elements/pagination.scss new file mode 100644 index 00000000..e14a4dc9 --- /dev/null +++ b/devsite/source/_sass/elements/pagination.scss @@ -0,0 +1,44 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.pagination { + margin: 0.5rem 0; + + li { + display: inline-block; + } + + a { + border: 1px solid $gray-08; + border-radius: $base-border-radius; + color: $gray-02; + display: block; + padding: 0.5rem 1rem; + + &:hover { + // background-color: lighten($base-link-color, 20); + border-color: $base-link-color; + color: $base-link-color; + } + } + + .active a { + background-color: $base-link-color; + color: $white; + font-weight: bold; + border-color: $base-link-color; + } +} diff --git a/devsite/source/_sass/elements/platform-choice.scss b/devsite/source/_sass/elements/platform-choice.scss new file mode 100644 index 00000000..088b0c8e --- /dev/null +++ b/devsite/source/_sass/elements/platform-choice.scss @@ -0,0 +1,88 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.platform-choice { + padding: 0.5em; + border-radius: $base-border-radius; +} + +.platform-choice--large { + font-size: 1.1em; + text-align: center; +} + +.platform-choice--small { + + p { + margin: 0; + } + + img { + display: block; + float: left; + height: $unitless-line-height * 1em; + margin-right: 0.5em; + } + + a { + color: #fff; + font-weight: bold; + white-space: nowrap; + + &:hover { + text-decoration: underline; + } + } +} + +.platform-choice--hidden { + display: none; +} + +.platform-choice--link { + display: inline-block; + margin: 0 1em; + + img { + display: block; + height: 3em; + margin: 0 auto; + } + + h4 { + color: $white; + font-size: 0.9em; + margin: 0.5em 0 0; + text-align: center; + text-transform: uppercase; + } + + &:hover { + h4 { + text-decoration: underline; + } + } +} + +// Hide all of the platform specific instructions by default. +.platform-specific { + display: none; +} + +// Unhide CloudPebble instructions to handle lack of JS support / JS failures. +.platform-specific[data-sdk-platform="cloudpebble"] { + display: inherit; +} diff --git a/devsite/source/_sass/elements/ribbon.scss b/devsite/source/_sass/elements/ribbon.scss new file mode 100644 index 00000000..dc6074a3 --- /dev/null +++ b/devsite/source/_sass/elements/ribbon.scss @@ -0,0 +1,65 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.ribbon { + @include transform(rotate(45deg)); + background-color: $pebble-green; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); + color: #fff; + font-size: 19px; + font-weight: bold; + left: -5px; + line-height: 15px; + padding: 10px 0 8px; + position: relative; + text-align: center; + top: 15px; + width: 120px; +} + +.ribbon__wrapper { + height: 88px; + overflow: hidden; + position: absolute; + right: 0; + top: 0; + width: 85px; +} + +.ribbon__container { + position: relative; +} + +.ribbon--bg-white { + background-color: $white; +} + +.ribbon--fg-dark-grey { + color: $dark-grey; +} + +.ribbon--bg-dark-red { + background-color: $dark-red; +} + +.ribbon--fg-dark-red { + color: $dark-red; +} + +.ribbon--fg-white { + color: $white; +} + diff --git a/devsite/source/_sass/elements/screenshot.scss b/devsite/source/_sass/elements/screenshot.scss new file mode 100644 index 00000000..4a9ef43b --- /dev/null +++ b/devsite/source/_sass/elements/screenshot.scss @@ -0,0 +1,300 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.pebble-screenshot { + background-repeat: no-repeat; + display: inline-block; + height: 407px; + padding: 112px 49px 127px; + text-align: left; + width: 242px; + background-size: contain; +} + +.pebble-screenshot--steel-black, +.pebble-screenshot--steel-stainless { + height: 424px; + padding: 113px 50px 143px; + width: 244px; +} + +.pebble-screenshot--time-black, +.pebble-screenshot--time-white, +.pebble-screenshot--time-red { + height: 350px; + padding-top: 91px; + padding-left: 50px; + padding-right: 50px; + padding-bottom: 91px; + width: 244px; +} + +.pebble-screenshot--time-round-black-20, +.pebble-screenshot--time-round-silver-20 { + height: 390px; + padding-top: 110px; + padding-left: 55px; + padding-right: 54px; + padding-bottom: 100px; + width: 289px; +} + +.pebble-screenshot--time-round-red-14, +.pebble-screenshot--time-round-rosegold-14, +.pebble-screenshot--time-round-silver-14 { + height: 390px; + padding-top: 107px; + padding-left: 58px; + padding-right: 56px; + padding-bottom: 103px; + width: 294px; +} + +.pebble-screenshot--pebble2-black, +.pebble-screenshot--pebble2-black-lime, +.pebble-screenshot--pebble2-black-red, +.pebble-screenshot--pebble2-white, +.pebble-screenshot--pebble2-white-teal { + height: 390px; + padding-top: 103px; + padding-left: 48px; + padding-right: 86px; + padding-bottom: 100px; + width: 294px; +} + +.pebble-screenshot--steel-black { + background-image: url('/assets/images/pebbles/steel_black.png'); +} + +.pebble-screenshot--steel-stainless { + background-image: url('/assets/images/pebbles/steel_stainless.png'); +} + +.pebble-screenshot--time-black { + background-image: url('/assets/images/pebbles/snowy-black.png'); +} + +.pebble-screenshot--time-red { + background-image: url('/assets/images/pebbles/snowy-red.png'); +} + +.pebble-screenshot--time-white { + background-image: url('/assets/images/pebbles/snowy-white.png'); +} + +.pebble-screenshot--red { + background-image: url('/assets/images/pebbles/red.png'); +} + +.pebble-screenshot--black { + background-image: url('/assets/images/pebbles/black.png'); +} + +.pebble-screenshot--orange { + background-image: url('/assets/images/pebbles/orange.png'); +} + +.pebble-screenshot--white { + background-image: url('/assets/images/pebbles/white.png'); +} + +.pebble-screenshot--grey { + background-image: url('/assets/images/pebbles/grey.png'); +} + +.pebble-screenshot--white-black { + background-image: url('/assets/images/pebbles/white_black.png'); +} + +.pebble-screenshot--time-round-black-20, +.pebble-screenshot--time-round-silver-20 { + background-image: url('/assets/images/pebbles/time-round-black-20.png'); +} + +.pebble-screenshot--time-round-red-14, +.pebble-screenshot--time-round-rosegold-14, +.pebble-screenshot--time-round-silver-14 { + background-image: url('/assets/images/pebbles/time-round-red-14.png'); +} + +.pebble-screenshot--pebble2-black { + background-image: url('/assets/images/pebbles/device_pebble2_black.png'); +} + +.pebble-screenshot--pebble2-black-lime { + background-image: url('/assets/images/pebbles/device_pebble2_blacklime.png'); +} + +.pebble-screenshot--pebble2-black-red { + background-image: url('/assets/images/pebbles/device_pebble2_blackred.png'); +} + +.pebble-screenshot--pebble2-white { + background-image: url('/assets/images/pebbles/device_pebble2_white.png'); +} + +.pebble-screenshot--pebble2-white-teal { + background-image: url('/assets/images/pebbles/device_pebble2_whiteteal.png'); +} + +.pebble-screenshot--strapless { + background-position: 0 -34px; + height: 339px; + padding-bottom: 93px; + padding-top: 78px; + + &.pebble-screenshot--steel-black, + &.pebble-screenshot--steel-stainless { + background-position: 0 -50px; + height: 324px; + padding-top: 63px; + } +} + +.pebble-screenshot--smaller { + + &.pebble-screenshot--steel-black, + &.pebble-screenshot--steel-stainless { + height: 212px; + padding: 56px 25px 72px; + width: 122px; + } + + &.pebble-screenshot--time-black, + &.pebble-screenshot--time-white, + &.pebble-screenshot--time-red { + height: 175px; + padding-top: 45px; + padding-left: 25px; + padding-right: 25px; + padding-bottom: 45px; + width: 122px; + } + + &.pebble-screenshot--time-round-black-20, + &.pebble-screenshot--time-round-red-14, + &.pebble-screenshot--time-round-rosegold-14, + &.pebble-screenshot--time-round-silver-14, + &.pebble-screenshot--time-round-silver-20 { + height: 175px; + padding-top: 41px; + padding-left: 17px; + padding-right: 19px; + padding-bottom: 42px; + width: 128px; + } +} + +.pebble-screenshot--small { + + &.pebble-screenshot--steel-black, + &.pebble-screenshot--steel-stainless { + height: 318px; + padding: 82px 38px 107px 37px; + width: 183px; + } + + &.pebble-screenshot--time-black, + &.pebble-screenshot--time-white, + &.pebble-screenshot--time-red { + height: 263px; + padding-top: 68px; + padding-left: 37px; + padding-right: 38px; + padding-bottom: 68px; + width: 183px; + } + + &.pebble-screenshot--time-round-black-20, + &.pebble-screenshot--time-round-red-14, + &.pebble-screenshot--time-round-rosegold-14, + &.pebble-screenshot--time-round-silver-14, + &.pebble-screenshot--time-round-silver-20 { + height: 263px; + padding-top: 62px; + padding-left: 26px; + padding-right: 25px; + padding-bottom: 64px; + width: 188px; + } +} + +.screenshot-viewer { + margin-bottom: 1rem; + + max-width: 100%; + overflow-y: scroll; +} + +.screenshot-viewer__screenshots { + display: -webkit-flex; + display: flex; + align-items: flex-end; + -webkit-align-items: flex-end; +} + +.screenshot-viewer__platform { + flex: 1 0 auto; + -webkit-flex: 1 0 auto; + text-align: center; + margin-right: 1rem; + + &:last-child { + margin-right: 0; + } +} + +.screenshot-viewer__tabs { + display: -webkit-flex; + display: flex; + align-items: flex-end; + -webkit-align-items: flex-end; + + h4 { + background-color: $gray-02; + color: $white; + font-weight: normal; + text-transform: uppercase; + font-size: 1.2em; + padding: 0.2em; + margin-top: 0.5em; + flex: 1 0 auto; + -webkit-flex: 1 0 auto; + text-align: center; + margin-right: 1rem; + + &:last-child { + margin-right: 0; + } + } +} + +.screenshot-viewer--tabbed { + .screenshot-viewer__tabs { + h4 { + border: 2px solid $gray-02; + background-color: $gray-06; + color: $gray-02; + cursor: pointer; + + &.selected { + background-color: $gray-02; + color: $white; + } + } + } +} diff --git a/devsite/source/_sass/elements/search.scss b/devsite/source/_sass/elements/search.scss new file mode 100644 index 00000000..990e18c8 --- /dev/null +++ b/devsite/source/_sass/elements/search.scss @@ -0,0 +1,248 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.search { + background-color: $white; + border-bottom: 1px solid $gray-10; + height: $header-height; + left: $sidebar-width; + padding: $base-font-size / 1.2; + position: fixed; + right: 0; + top: 0; + z-index: 100; + + @include bp-max ($sidebar-hide-at) { + left: 0; + padding-left: $hamburger-width; + } + + input { + -webkit-appearance: textfield; + border: 0; + box-shadow: none; + font-size: $base-font-size * 1.2; + padding: $base-font-size / 2; + padding-left: 2em; + width: 100%; + + @include bp-max ($sidebar-hide-at) { + padding-left: 0; + } + + &:focus { + box-shadow: none; + outline: none; + } + + &:before { + content: '\f002'; + font-family: 'FontAwesome'; + } + } +} + +.search__icon { + font-size: $base-font-size * 1.2; + left: $base-font-size / 0.6; + position: absolute; + top: $base-font-size / 0.6; + + @include bp-max ($sidebar-hide-at) { + display: none; + } +} + +.content--narrow .search { + left: $sidebar--wide-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} + +.content--section-menu .search { + left: $sidebar-width + $section-menu-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} + +.quicksearch { + background-color: #fff; + border-bottom: 1px solid #d9d9d9; + bottom: 10%; + left: $sidebar-width; + overflow-y: scroll; + padding: 0 1em 1em; + position: fixed; + right: 0; + top: $header-height - 2; + z-index: 200; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } + + h3 { + font-size: 1.2 * $base-font-size; + margin: 0.5em 0; + text-transform: uppercase; + } + + ul { + margin: 0; + } +} + +.quicksearch__block { + h3 { + color: $white; + padding: 0 0.25rem; + } +} + +.quicksearch__block--guides { + h3 { + background-color: $color-guides; + color: $base-font-color; + } +} + +.quicksearch__block--docs { + h3 { + background-color: $color-docs; + } +} + +.quicksearch__block--community { + h3 { + background-color: $color-community; + } +} + +.quicksearch__block--more{ + h3 { + background-color: $color-more; + } +} + +.quicksearch__block--other { + h3 { + background-color: $color-other; + } +} + +.quicksearch__result { + margin-bottom: 0.5 * $base-font-size; + overflow: hidden; + text-overflow: ellipsis; + +} + +.quicksearch__summary { + font-size: 0.9 * $base-font-size; + line-height: 0.9 * $base-line-height; + margin: 0; + max-height: 0.9 * $base-line-height; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.search__result__highlight { + text-decoration: underline; +} + +.quicksearch__result__tag { + background-color: $color-docs; + border-radius: 2px; + color: #fff; + display: inline-block; + float: left; + font-size: 0.7em; + padding: 0.2em 0.3em; + margin: 0.1em 0.5em 0.1em 0; +} + +.quicksearch__section { + margin: 0; + font-size: 0.75em; + text-transform: uppercase; + color: $gray-11; + font-weight: bold; + width: auto; + display: table; +} + +.quicksearch__result--active { + background-color: $gray-01; + border-left: 2px solid $gray-03; + border-right: 2px solid $gray-03; + margin: -5px -5px 2.5px; + padding: 5px; +} + +.quicksearch__no-results { + font-size: 3em; + font-weight: 300; + text-align: center; + padding: 1em; + color: $gray-09; +} + +.content--narrow .quicksearch { + left: $sidebar--wide-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} + +.content--section-menu .quicksearch { + left: $sidebar-width + $section-menu-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} + +#search__blackout { + background-color: rgba(0, 0, 0, 0.7); + bottom: 0; + left: $sidebar-width; + position: fixed; + right: 0; + top: $header-height; + z-index: 150; +} + +.content--narrow #search__blackout { + left: $sidebar--wide-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} + +.content--section-menu #search__blackout { + left: $sidebar-width + $section-menu-width; + + @include bp-max ($sidebar-hide-at) { + left: 0; + } +} diff --git a/devsite/source/_sass/elements/sectionmenu.scss b/devsite/source/_sass/elements/sectionmenu.scss new file mode 100644 index 00000000..3b36783e --- /dev/null +++ b/devsite/source/_sass/elements/sectionmenu.scss @@ -0,0 +1,242 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$section-menu-width: 240px; + +.sidebar__wrapper--sectionmenu { + width: $sidebar-width + $section-menu-width; +} + +.section-menu { + border-right: 1px solid $gray-08; + bottom: 0; + left: $sidebar-width; + margin-top: $header-height; + position: absolute; + top: 0; + width: $section-menu-width; + overflow-y: auto; + + > ul { + padding: 1em; + } + + @include bp-max ($sidebar-hide-at) { + display: none; + } +} + +.content--section-menu { + margin-left: $sidebar-width + $section-menu-width; + + @include bp-max ($sidebar-hide-at) { + margin-left: 0; + } +} + +.section-menu__header { + height: $header-height; + padding: 0 1em; + position: fixed; + top: 0; + width: $section-menu-width; + + h3 { + border-bottom: 1px solid rgba(68,68,68,0.2); + font-size: $base-font-size * 1.2; + font-weight: 400; + margin-top: 3px; + padding: $base-line-height / 1.2 0; + text-transform: uppercase; + width: 100%; + } +} + +.documentation-menu { + background-color: $color-docs; + border-bottom: 1px solid rgba(68, 68, 68, 0.3); + border-right: 1px solid darken($color-docs, 10); + box-shadow: rgba(68, 68, 68, 0.3) 0 5px 5px -5px; + display: none; + margin: -1em; + + a { + display: block; + padding: 0.5em 1em; + + &:hover { + background-color: darken($color-docs, 5); + } + } +} + +.documentation-menu__arrow { + float: right; +} + +.documentation-menu--visible { + display: block; +} + +.section-menu__item { + + > a { + color: $gray-02; + display: inline-block; + font-size: 13px; + font-weight: bold; + margin: $base-line-height / 4 0; + padding: 0.5em; + } + + > ul { + display: none; + } + + &.open > ul { + display: block; + } +} + +.section-menu__subitem { + + > a { + color: $gray-02; + display: block; + font-size: 13px; + font-weight: 600; + margin: 0 0 0.25em 1em; + text-transform: none; + + span { + display: inline-block; + padding: 0.5em; + } + } + + ul { + display: none; + } + + &.open ul { + display: block; + } +} + +.section-menu__subsubitem { + + > a { + color: $gray-02; + display: block; + font-size: 12px; + font-weight: normal; + margin: 0 0 0.25em 2em; + text-transform: none; + + span { + display: inline-block; + padding: 0.5em; + } + } +} + +.section-menu--dark { + + &, + a, + .section-menu__header a { + color: $white; + } + + .section-menu__item.active > a, + .section-menu__subitem.active > a, + .section-menu__subsubitem.active > a span { + background-color: $white; + border-radius: $base-border-radius; + color: $gray-02; + } +} + +.section-menu--light { + + &, + a, + .section-menu__header a { + color: $gray-02; + } + + .section-menu__item.active > a, + .section-menu__subitem.active > a, + .section-menu__subsubitem.active > a span { + background-color: $gray-02; + border-radius: $base-border-radius; + color: $white; + } +} + +.section-menu--guides { + background-color: $color-guides; + border-right-color: darken($color-guides, 10); + + .section-menu__header { + background-color: $color-guides; + } +} + +.section-menu--docs { + background-color: $color-docs; + border-right-color: darken($color-docs, 10); + + .section-menu__header { + background-color: $color-docs; + } +} + +.section-menu--community { + background-color: $color-community; + border-right-color: darken($color-community, 10); + + .section-menu__header { + background-color: $color-community; + } +} + +.section-menu--sdk { + background-color: $color-sdk; + border-right-color: darken($color-sdk, 10); + + .section-menu__header { + background-color: $color-sdk; + } +} + +.section-menu--more { + background-color: $color-more; + border-right-color: darken($color-more, 10); + + .section-menu__header { + background-color: $color-more; + } +} + +.section-menu--getting-started { + background-color: $color-getting-started; + border-right-color: darken($color-getting-started, 10); + + .section-menu__header { + background-color: $color-getting-started; + } +} diff --git a/devsite/source/_sass/elements/sidebar.scss b/devsite/source/_sass/elements/sidebar.scss new file mode 100644 index 00000000..d1480460 --- /dev/null +++ b/devsite/source/_sass/elements/sidebar.scss @@ -0,0 +1,269 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$sidebar-width: 105px; +$sidebar--wide-width: $header-logo-width; +$sidebar-hide-at: xs; + +.sidebar__wrapper { + position: fixed; + width: $sidebar-width; + top: 0; + bottom: 0; + left: 0; + z-index: 10; + + @include bp-max ($sidebar-hide-at) { + display: none; + } +} + +.sidebar { + background-color: #f6f6f6; + bottom: 0; + left: 0; + padding-top: $header-height; + position: absolute; + top: 0; + width: $sidebar-width; + + @include bp-max ($sidebar-hide-at) { + display: none; + } +} + +.sidebar--wide { + width: $sidebar--wide-width; +} + +.sidebar__header { + background-color: #222; + border-bottom: 2px solid $black; + color: $white; + font-size: $base-font-size * 1.2; + height: $header-height; + left: 0; + padding: $base-line-height / 1.2 0; + position: absolute; + text-align: center; + top: 0; + width: $sidebar-width; + + &:hover { + background-color: $black; + color: $white; + } + + span { + display: inline-block; + } + + li ul { + display: none; + } + + li.active { + + ul { + display: block; + } + + > a { + background-color: lighten($base-link-color, 30); + color: #fff; + } + } +} + +.sidebar--wide .sidebar__header { + width: $sidebar--wide-width; +} + +.content { + margin-left: $sidebar-width; + height: 100%; + + @include bp-max ($sidebar-hide-at) { + margin-left: 0; + } +} + +.content--narrow { + margin-left: $sidebar--wide-width; + + @include bp-max ($sidebar-hide-at) { + margin: 0; + } +} + +.sidebar__legal { + position: absolute; + left: 0; + right: 0; + bottom: 3 * $base-line-height; + font-size: 0.85em; + padding: 1em 0; + text-align: center; + + a { + color: $gray-03; + + &:hover { + text-decoration: underline; + } + } +} + +.sidebar__footer { + background-color: $color-appstore; + border-right: 1px solid darken($color-appstore, 10); + bottom: 0; + color: $white; + font-weight: bold; + height: 3 * $base-line-height; + left: 0; + padding: $base-line-height 0; + position: absolute; + right: 0; + text-align: center; + text-transform: uppercase; + + &:hover, + &.active { + background-color: darken($color-appstore, 10); + border-color: darken($color-appstore, 20); + color: $white; + } +} + +.sidebar--narrow { + + .mainmenu__item { + + span { + background-color: #444; + border-radius: $base-border-radius; + color: $white; + display: none; + left: 80px; + margin-top: -1.2em; + padding: 0.5em; + position: absolute; + top: 50%; + white-space: nowrap; + z-index: 100; + } + + &:hover { + span { + display: block; + } + } + } +} + +.mainmenu { + border-right: 1px solid $gray-08; + height: 100%; + // I would like to enable scrolling on the Y axis but it breaks the tooltips. + // overflow-x: visible; + // overflow-y: scroll; + padding-top: 1em; + + li:first-child { + margin-top: -13px; + } + + .mainmenu__item a { + background-position: center center; + background-repeat: no-repeat; + background-size: 3em; + color: $gray-02; + display: block; + font-size: 13px; + font-weight: bold; + height: 4em; + margin: 0.5em 0; + padding: 1.2em; + position: relative; + text-transform: uppercase; + + @media screen and ( max-height: 640px ){ + margin: 0; + } + + &:hover { + background-color: #e3e3e3; + } + + @media screen and (max-height: 600px) { + background-size: 2.5em; + height: 3em; + padding: 0.75em; + } + } + + .mainmenu__item.active a { + background-color: #e3e3e3; + } + + .mainmenu__item--overview a { + background-image: url('../images/menu/overview.svg'); + } + + .mainmenu__item--getting-started a { + background-image: url('../images/menu/getting-started.svg'); + } + + .mainmenu__item--guides a { + background-image: url('../images/menu/guides.svg'); + } + + .mainmenu__item--docs a { + background-image: url('../images/menu/docs.svg'); + } + + .mainmenu__item--community a { + background-image: url('../images/menu/community.svg'); + } + + .mainmenu__item--sdk a { + background-image: url('../images/menu/sdk.svg'); + } + + .mainmenu__item--blog a { + background-image: url('../images/menu/blog.svg'); + } + + .mainmenu__item--examples a { + background-image: url('../images/menu/examples.svg'); + } + + .mainmenu__item--more a { + background-image: url('../images/menu/more.svg'); + } +} + +.sidebar--wide .mainmenu__item a { + background-position: 1em center; + background-size: 3em; + padding-left: 5em; + + @media screen and (max-height: 600px) { + background-size: 2.5em; + padding-left: 4.5em; + } +} diff --git a/devsite/source/_sass/elements/spinner.scss b/devsite/source/_sass/elements/spinner.scss new file mode 100644 index 00000000..a965d5c0 --- /dev/null +++ b/devsite/source/_sass/elements/spinner.scss @@ -0,0 +1,104 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@charset "UTF-8"; + +// Spinner by CSS Wizadry: http://jsfiddle.net/csswizardry/M2D4M/ + +// A simple, semantic, usable-anywhere spinner. It takes its coloring from its +// parent element, meaning it can be dropped anywhere without modification. + +// 1. Positioning context. +// 2. Define dimensions in ems so that we can… +// 3. …adjust spinner size by just changing its `font-size`. +// 4. Do not explicitly define a color (allow border to inherit current text +// color). This makes the spinner usable on any color background. We’re also +// only defining a bottom border here; this is what actually gives the +// illusion of something spinning. +// 5. Kellum method hidden text: +// zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement + +.spinner { + @include animation(0.5s spinner linear infinite); + border-bottom: 1px solid; // 4 + display: inline-block; + font-size: 32px; // 3 + height: 1em; // 2 + overflow: hidden; // 5 + position: relative; // 1 + text-indent: 100%; // 5 + vertical-align: middle; + width: 1em; // 2 + + // 1. Make the spinner a circle. + &, + &:after { + border-radius: 100%; // 1 + } + + // The (optically) non-spinning part of the spinner. + // 1. Border around entire element fills in the rest of the ring. + // 2. Paler than the part that appears to spin. + &:after { + border: 1px solid; // 1 + bottom: 0; + content: ''; + left: 0; + opacity: 0.5; // 2 + position: absolute; + right: 0; + top: 0; + } +} + +// Size variants (built by adjusting `font-size`). +.spinner--small { font-size: 16px; } +.spinner--large { font-size: 64px; } + +// Color overrides. +.spinner--light { color: $white; } +.spinner--dark { color: #333; } + +.spinner--center { + display: block; + margin: 0 auto; +} + +.spinner--padded { + margin-bottom: 1rem; + margin-top: 1rem; +} + +@-webkit-keyframes spinner { + + to { + -webkit-transform: rotate(360deg); + } +} + +@-moz-keyframes spinner { + + to { + -moz-transform: rotate(360deg); + } +} + +@keyframes spinner { + + to { + transform: rotate(360deg); + } +} diff --git a/devsite/source/_sass/elements/table.scss b/devsite/source/_sass/elements/table.scss new file mode 100644 index 00000000..8b14c48c --- /dev/null +++ b/devsite/source/_sass/elements/table.scss @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.table--skinny { + td { + padding: 0.25em 0; + } + + th { + padding: 0.25em 0; + } +} diff --git a/devsite/source/_sass/elements/toc.scss b/devsite/source/_sass/elements/toc.scss new file mode 100644 index 00000000..ce391c76 --- /dev/null +++ b/devsite/source/_sass/elements/toc.scss @@ -0,0 +1,46 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.toc__item { + overflow: hidden; + text-overflow: ellipsis; +} + +.toc__item--level1 { + font-size: 1em; +} + +.toc__item--level2 { + font-size: 0.95em; + padding-left: 0.5rem; +} + +.toc__item--level3 { + font-size: 0.90em; + padding-left: 1rem; +} + +.toc__item--level4 { + font-size: 0.85em; + padding-left: 1.5rem; +} + +.toc__item--active { + font-weight: 600; + a { + color: darken($base-link-color, 20); + } +} diff --git a/devsite/source/_sass/elements/typeahead.scss b/devsite/source/_sass/elements/typeahead.scss new file mode 100644 index 00000000..9cc376ac --- /dev/null +++ b/devsite/source/_sass/elements/typeahead.scss @@ -0,0 +1,71 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Styles for the primary site search, powered by Twitter's Typeahead.js + +.twitter-typeahead { + width: 100%; +} + +.tt-dropdown-menu { + background-color: $white; + border-bottom: 1px solid lighten($base-border-color, 60); + border-left: 1px solid lighten($base-border-color, 60); + border-right: 1px solid lighten($base-border-color, 60); + margin-top: -0.75rem; + width: 100%; +} + +.tt-group-header { + background-color: lighten($base-border-color, 30); + color: $white; + font-size: 1rem; + font-weight: normal; + padding: 0.2rem; + text-align: center; +} + +.tt-suggestion { + border-bottom: 1px solid lighten($base-border-color, 60); + cursor: pointer; + padding: 0.2rem 0.5rem; + + .tt-suggestion-highlight { + color: lighten($base-font-color, 30); + font-size: 0.8rem; + height: 1em; + line-height: 0.8rem; + overflow: hidden; + text-overflow: ellipsis; + } + + p { + margin: 0; + } + + &.tt-cursor { + background-color: $base-link-color; + color: $white; + + .tt-suggestion-highlight { + color: $white; + } + } + + &:last-child { + border-bottom: 0; + } +} diff --git a/devsite/source/_sass/elements/video.scss b/devsite/source/_sass/elements/video.scss new file mode 100644 index 00000000..99182927 --- /dev/null +++ b/devsite/source/_sass/elements/video.scss @@ -0,0 +1,71 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.video { + margin: auto; + width: 100%; + + .video__wrapper { + height: 0; + position: relative; + } + + iframe { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } +} + +.video--widescreen .video__wrapper { + padding-bottom: 56.25%; +} + +.video--standard .video__wrapper { + padding-bottom: 75%; +} + +.video--preview { + cursor: pointer; + + .video__wrapper::after { + background-color: rgba(200, 200, 200, 0.8); + border-radius: 50%; + color: #444; + content: '\f144'; + font-family: FontAwesome; + font-size: 80px; + left: 50%; + line-height: 68px; + margin-left: -40px; + margin-top: -40px; + padding: 10px; + position: absolute; + top: 50%; + @include transition(color 0.3s linear); + @include transition(background-color 0.3s linear); + } + + &:hover { + + .video__wrapper::after { + background-color: rgba(50, 50, 50, 0.8); + color: #d00; + } + } +} diff --git a/devsite/source/_sass/footer.scss b/devsite/source/_sass/footer.scss new file mode 100644 index 00000000..94ed9cef --- /dev/null +++ b/devsite/source/_sass/footer.scss @@ -0,0 +1,80 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$footer-height: 2rem; +$footer-height-mobile: 9.95rem; +$footer-background-color: #333; +$footer-text-color: #bbb; +$footer-bp: s; + +.wrapper { + height: auto !important; + margin: 0 auto -1 * $footer-height-mobile; + min-height: 100%; + @include clearfix; + + @include bp-min ($footer-bp) { + margin: 0 auto -1 * $footer-height; + } +} + +.push { + height: $footer-height-mobile; + + @include bp-min ($footer-bp) { + height: $footer-height; + } +} + +.footer { + background-color: $footer-background-color; + display: table; + height: $footer-height-mobile; + position: absolute; + width: 100%; + z-index: 100; + + @include bp-min ($footer-bp) { + height: $footer-height; + } + + ul { + display: table-row; + } + + li { + @include bp-min ($footer-bp) { + display: table-cell; + width: 2%; + } + } + + a { + @include transition (all 0.2s ease-in-out); + background-color: $footer-background-color; + color: $footer-text-color; + display: block; + font-size: 0.7rem; + font-weight: bold; + padding: 0.65rem 0 0.4rem; + text-align: center; + text-transform: uppercase; + + &:hover { + background-color: darken($footer-background-color, 20); + } + } +} diff --git a/devsite/source/_sass/header.scss b/devsite/source/_sass/header.scss new file mode 100644 index 00000000..54f523c4 --- /dev/null +++ b/devsite/source/_sass/header.scss @@ -0,0 +1,18 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$header-height: 3 * $base-line-height; +$header-logo-width: 226px; diff --git a/devsite/source/_sass/sections/blog.scss b/devsite/source/_sass/sections/blog.scss new file mode 100644 index 00000000..7cae00ce --- /dev/null +++ b/devsite/source/_sass/sections/blog.scss @@ -0,0 +1,86 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.blog__meta { + border-bottom: 1px solid $gray-09; + border-top: 1px solid $gray-09; + padding: 0.5em; + margin-bottom: 1em; +} + +.blog__more { + margin-top: 2em; +} + +.image-list { + img { + display: inline; + } +} + + +.blog-index__post { + margin-bottom: 2em; + + .blog--no-index { + display: none; + } + + h3 { + font-weight: 600; + font-size: 1.4em; + margin: -0.2em 0 0; + line-height: 1.1; + } + + p.blog-index__tags { + margin: 0 0 0.25em 0; + font-size: 0.9em; + + a { + color: darken($gray-09, 20); + } + } + + .blog-index__meta { + margin-right: 1em; + + img { + border-radius: 5px; + } + + p { + color: $gray-09; + font-size: 0.85em; + text-align: center; + } + } + + .blog-index__body { + float: left; + max-width: 43em; + + @include bp-min (m) { + width: 83%; + } + } +} + +.blog__image-text { + font-size: 0.9em; + font-style: italic; + text-align: center; +} \ No newline at end of file diff --git a/devsite/source/_sass/sections/documentation.scss b/devsite/source/_sass/sections/documentation.scss new file mode 100644 index 00000000..237eb550 --- /dev/null +++ b/devsite/source/_sass/sections/documentation.scss @@ -0,0 +1,217 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$docs-header-color: $gray-06; + +.documentation { + max-width: 43em; + + a, + a code { + color: darken($base-link-color, 20); + font-weight: 600; + } + + a:hover, + a:active { + color: darken($base-link-color, 40); + } +} + +.docs_tree { + ul { + @extend %default-ul; + } +} + +.docs__item { + ul { + @extend %default-ul; + } +} + + +.docs__item__param { + white-space: nowrap; + + a { + text-decoration: underline; + + &:hover, + &:active, + &:focus { + color: $gray-02; + } + } +} + +.docs__module { + h4 { + margin: 0; + } + + a { + font-weight: normal; + text-decoration: none; + } +} + +.docs__item { + margin-bottom: 2em; + + &:last-child { + margin-bottom: 1em; + } + + &::before { + content: ' '; + display: block; + height: $header-height; + margin-top: -1 * $header-height; + visibility: hidden; + } + + section { + display: none; + + &[data-platform="aplite"] { + display: block; + } + } +} + +.docs__item__tabs { + list-style: none; + margin: 0; + padding: 0; + + li { + display: inline-block; + } + + a { + background-color: lighten($docs-header-color, 8); + border-radius: 4px 4px 0 0; + color: lighten($gray-02, 10); + display: block; + padding: 0.4em 0.8em; + text-transform: uppercase; + + &.active { + background-color: $docs-header-color; + color: $gray-02; + } + } +} + +.docs__item__header { + background-color: $docs-header-color; + color: $gray-02; + font-size: 0.9em; + font-weight: 600; + padding: 0.5em; + + a, + a:hover, + a:active { + color: $gray-02; + } +} + +.docs__item__name { + color: $gray-02; + font-weight: bold; +} + +.docs__item__summary, +.docs__item__description { + margin-bottom: 0.5em; + + &:last-child { + margin: 0; + } +} + +.docs__item__body { + border: 1px solid $docs-header-color; + border-top: 0; + padding: 0.5em; + + h4 { + border-bottom: 1px solid $color-docs; + text-transform: uppercase; + font-weight: 600; + font-size: 1.2em; + } + + dl { + margin-top: 0; + + &:last-child { + margin-bottom: 0; + } + } + + dd { + margin-left: 1em; + } + + dt { + margin: 0; + } + + p:last-child { + margin-bottom: 0; + } +} + +.docs__item--missing { + border: 1px solid $gray-09; + padding: 0.5em; + + p { + margin-bottom: 0; + } +} + +.alert--docs-notes { + background-color: $gray-05; + border-bottom: 2px solid $color-docs; + border-top: 2px solid $color-docs; + color: $gray-03; + margin: 0 -0.5em 1em; + + h5 { + font-size: 1.1em; + margin: 0; + text-transform: uppercase; + } +} + +.docs__item__anchor { + &::before { + content: ' '; + display: block; + height: $header-height; + margin-top: -1 * $header-height; + visibility: hidden; + } +} + +@import 'sections/documentation/c'; +@import 'sections/documentation/android'; +@import 'sections/documentation/ios'; +@import 'sections/documentation/js'; \ No newline at end of file diff --git a/devsite/source/_sass/sections/documentation/android.scss b/devsite/source/_sass/sections/documentation/android.scss new file mode 100644 index 00000000..415a90b3 --- /dev/null +++ b/devsite/source/_sass/sections/documentation/android.scss @@ -0,0 +1,442 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.documentation__android { + + ul { + list-style-type:disc; + } + code, tt { + font-size:1.2em; + } + dt code { + font-size:1.2em; + } + table tr td dt code { + font-size:1.2em; + vertical-align:top; + } + sup { + font-size:.6em; + } + /* + Document title and Copyright styles + */ + .clear { + clear:both; + height:0px; + overflow:hidden; + } + .aboutLanguage { + float:right; + padding:0px 21px; + font-size:.8em; + z-index:200; + margin-top:-7px; + } + .legalCopy { + margin-left:.5em; + } + .bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; + } + .bar a:hover, .bar a:focus { + color:#bb7a2a; + } + .tab { + background-color:#0066FF; + background-image:url(resources/titlebar.gif); + background-position:left top; + background-repeat:no-repeat; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; + } + /* + Navigation bar styles + */ + .bar { + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:1em; + margin:0; + } + .topNav { + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + } + .bottomNav { + margin-top:10px; + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + } + .subNav { + background-color:#dee3e9; + border-bottom:1px solid #9eadc0; + float:left; + width:100%; + overflow:hidden; + } + .subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + } + ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; + } + ul.navList li{ + list-style:none; + float:left; + padding:3px 6px; + } + ul.subNavList li{ + list-style:none; + float:left; + font-size:90%; + } + .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + } + .topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + } + .navBarCell1Rev { + background-image:url(resources/tab.gif); + background-color:#a88834; + color:#FFFFFF; + margin: auto 5px; + border:1px solid #c9aa44; + } + /* + Page header and footer styles + */ + .header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; + } + .indexHeader { + margin:10px; + position:relative; + } + .indexHeader h1 { + font-size:1.3em; + } + .title { + color:#2c4557; + margin:10px 0; + } + .subTitle { + margin:5px 0 0 0; + } + .header ul { + margin:0 0 25px 0; + padding:0; + } + .footer ul { + margin:20px 0 5px 0; + } + .header ul li, .footer ul li { + list-style:none; + font-size:1.2em; + } + /* + Heading styles + */ + div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + margin:0 0 6px -8px; + padding:2px 5px; + } + ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + margin:0 0 6px -8px; + padding:2px 5px; + } + ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; + } + ul.blockList li.blockList h2 { + padding:0px 0 20px 0; + } + /* + Page layout container styles + */ + .contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; + } + .indexContainer { + margin:10px; + position:relative; + font-size:1.0em; + } + .indexContainer h2 { + font-size:1.1em; + padding:0 0 3px 0; + } + .indexContainer ul { + margin:0; + padding:0; + } + .indexContainer ul li { + list-style:none; + } + .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:1.1em; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; + } + .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:10px 0 10px 20px; + } + .serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; + } + .serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; + } + /* + List styles + */ + ul.horizontal li { + display:inline; + font-size:0.9em; + } + ul.inheritance { + margin:0; + padding:0; + } + ul.inheritance li { + display:inline; + list-style:none; + } + ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; + } + ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; + } + ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:25px; + } + ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #9eadc0; + background-color:#f9f9f9; + } + ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:1px solid #9eadc0; + border-top:none; + } + ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; + border-bottom:1px solid #9eadc0; + } + ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; + } + table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; + } + /* + Table styles + */ + .contentContainer table, .classUseContainer table, .constantValuesContainer table { + border-bottom:1px solid #9eadc0; + width:100%; + } + .contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table { + width:100%; + } + .contentContainer .description table, .contentContainer .details table { + border-bottom:none; + } + .contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td{ + vertical-align:top; + padding-right:20px; + } + .contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast, + .contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast, + .contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne, + .contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne { + padding-right:3px; + } + .overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#FFFFFF; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + margin:0px; + } + caption a:link, caption a:hover, caption a:active, caption a:visited { + color:#FFFFFF; + } + .overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span { + white-space:nowrap; + padding-top:8px; + padding-left:8px; + display:block; + float:left; + background-image:url(resources/titlebar.gif); + height:18px; + } + .overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd { + width:10px; + background-image:url(resources/titlebar_end.gif); + background-repeat:no-repeat; + background-position:top right; + position:relative; + float:left; + } + ul.blockList ul.blockList li.blockList table { + margin:0 0 12px 0px; + width:100%; + } + .tableSubHeadingColor { + background-color: #EEEEFF; + } + .altColor { + background-color:#eeeeef; + } + .rowColor { + background-color:#ffffff; + } + .overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td { + text-align:left; + padding:3px 3px 3px 7px; + } + th.colFirst, th.colLast, th.colOne, .constantValuesContainer th { + background:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + text-align:left; + padding:3px 3px 3px 7px; + } + td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; + } + td.colFirst, th.colFirst { + border-left:1px solid #9eadc0; + white-space:nowrap; + } + td.colLast, th.colLast { + border-right:1px solid #9eadc0; + } + td.colOne, th.colOne { + border-right:1px solid #9eadc0; + border-left:1px solid #9eadc0; + } + table.overviewSummary { + padding:0px; + margin-left:0px; + } + table.overviewSummary td.colFirst, table.overviewSummary th.colFirst, + table.overviewSummary td.colOne, table.overviewSummary th.colOne { + width:25%; + vertical-align:middle; + } + table.packageSummary td.colFirst, table.overviewSummary th.colFirst { + width:25%; + vertical-align:middle; + } + /* + Content styles + */ + .description pre { + margin-top:0; + } + .deprecatedContent { + margin:0; + padding:10px 0; + } + .docSummary { + padding:0; + } + /* + Formatting effect styles + */ + .sourceLineNo { + color:green; + padding:0 30px 0 0; + } + h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:.9em; + } + .block { + display:block; + margin:3px 0 0 0; + } + .strong { + font-weight:bold; + } +} diff --git a/devsite/source/_sass/sections/documentation/c.scss b/devsite/source/_sass/sections/documentation/c.scss new file mode 100644 index 00000000..7aa2aa42 --- /dev/null +++ b/devsite/source/_sass/sections/documentation/c.scss @@ -0,0 +1,28 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.documentation__c { + max-width: 43em; + + .gcolor_sample { + width: 3em; + height: 1.3em; + display: block; + float: left; + border: 1px solid #000; + margin-right: 0.5em; + } +} diff --git a/devsite/source/_sass/sections/documentation/ios.scss b/devsite/source/_sass/sections/documentation/ios.scss new file mode 100644 index 00000000..7a9e13cc --- /dev/null +++ b/devsite/source/_sass/sections/documentation/ios.scss @@ -0,0 +1,580 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.documentation__ios { + + code { + white-space: normal; + } + + // font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; + // font-size: 13px; + + code { + // font-family: Courier, Consolas, monospace; + // font-size: 13px; + color: #666; + } + + pre { + // font-family: Courier, Consolas, monospace; + font-size: 13px; + line-height: 18px; + tab-interval: 0.5em; + border: 1px solid #C7CFD5; + background-color: #F1F5F9; + color: #666; + padding: 0.3em 1em; + } + + ul { + list-style-type: square; + } + + li { + margin-bottom: 10px; + } + + a, a code { + text-decoration: none; + color: #36C; + } + + a:hover, a:hover code { + text-decoration: underline; + color: #36C; + } + + h2 { + border-bottom: 1px solid #8391A8; + color: #3C4C6C; + font-size: 187%; + font-weight: normal; + margin-top: 1.75em; + padding-bottom: 2px; + } + + table { + margin-bottom: 4em; + border-collapse:collapse; + vertical-align: middle; + } + + td { + border: 1px solid #9BB3CD; + padding: .667em; + font-size: 100%; + } + + th { + border: 1px solid #9BB3CD; + padding: .3em .667em .3em .667em; + background: #93A5BB; + font-size: 103%; + font-weight: bold; + color: white; + text-align: left; + } + + #top_header { + height: 91px; + left: 0; + min-width: 598px; + position: absolute; + right: 0; + top: 0; + z-index: 900; + } + + #footer { + clear: both; + padding-top: 20px; + text-align: center; + } + + .copyright { + font-size: 12px; + } + + .generator { + font-size: 11px; + } + + .main-navigation ul li { + display: inline; + margin-left: 15px; + list-style: none; + } + + .navigation-top { + clear: both; + float: right; + } + + .navigation-bottom { + clear: both; + float: right; + margin-top: 20px; + margin-bottom: -10px; + } + + .open > .disclosure { + background-image: url("../img/disclosure_open.png"); + } + + .disclosure { + background: url("../img/disclosure.png") no-repeat scroll 0 0; + } + + .disclosure, .nodisclosure { + display: inline-block; + height: 8px; + margin-right: 5px; + position: relative; + width: 9px; + } + + #top_header #library { + background: url("../img/library_background.png") repeat-x 0 0 #485E78; + background-color: #ccc; + height: 35px; + font-size: 115%; + } + + #top_header #library #libraryTitle { + color: #FFFFFF; + margin-left: 15px; + text-shadow: 0 -1px 0 #485E78; + top: 8px; + position: absolute; + } + + #libraryTitle { + left: 0; + } + + #top_header #library #developerHome { + color: #92979E; + right: 15px; + top: 8px; + position: absolute; + } + + #top_header #library a:hover { + text-decoration: none; + } + + #top_header #title { + background: url("../img/title_background.png") repeat-x 0 0 #8A98A9; + border-bottom: 1px solid #757575; + height: 25px; + overflow: hidden; + } + + #top_header h1 { + font-size: 105%; + font-weight: normal; + margin: 0; + padding: 3px 0 2px; + text-align: center; + white-space: nowrap; + } + + #headerButtons { + background-color: #D8D8D8; + background-image: url("../img/button_bar_background.png"); + border-bottom: 0px solid #EDEDED; + border-top: 0px solid #a8a8a8; + font-size: 8pt; + height: 28px; + left: 0; + list-style: none outside none; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + right: 0; + top: 61px; + } + + #headerButtons li { + background-repeat: no-repeat; + display: inline; + margin-top: 0; + margin-bottom: 0; + padding: 0; + } + + #toc_button button { + background-color: #EBEEF1; + border-color: #ACACAC; + border-style: none solid none none; + border-width: 0 1px 0 0; + height: 28px; + margin: 0; + padding-left: 30px; + text-align: left; + width: 230px; + } + + li#jumpto_button { + left: 230px; + margin-left: 0; + position: absolute; + } + + li#jumpto_button select { + height: 22px; + margin: 5px 2px 0 10px; + max-width: 300px; + } + + + #tocContainer.isShowingTOC { + border-right: 1px solid #ACACAC; + display: block; + overflow-x: hidden; + overflow-y: auto; + padding: 0; + } + + #tocContainer { + background-color: #EBEEF1; + border-top: 1px solid #ACACAC; + bottom: 0; + display: none; + left: 0; + overflow: hidden; + position: absolute; + top: 90px; + width: 229px; + } + + #tocContainer > ul#toc { + font-size: 11px; + margin: 0; + padding: 12px 0 18px; + width: 209px; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; + } + + #tocContainer > ul#toc > li { + margin: 0; + padding: 0 0 7px 30px; + text-indent: -15px; + } + + #tocContainer > ul#toc > li > .sectionName a { + color: #000000; + font-weight: bold; + } + + #tocContainer > ul#toc > li > .sectionName a:hover { + text-decoration: none; + } + + #tocContainer > ul#toc li.children > ul { + display: none; + height: 0; + } + + #tocContainer > ul#toc > li > ul { + margin: 0; + padding: 0; + } + + #tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { + margin-left: 0; + margin-bottom: 0; + padding-left: 15px; + } + + #tocContainer > ul#toc > li ul { + list-style: none; + margin-right: 0; + padding-right: 0; + } + + #tocContainer > ul#toc li.children.open > ul { + display: block; + height: auto; + margin-left: -15px; + padding-left: 0; + } + + #tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { + margin-left: 0; + padding-left: 15px; + } + + #tocContainer li ul li { + margin-top: 0.583em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + #tocContainer li ul li span.sectionName { + white-space: normal; + } + + #tocContainer > ul#toc > li > ul > li > .sectionName a { + font-weight: bold; + } + + #tocContainer > ul#toc > li > ul a { + color: #4F4F4F; + } + + .index-title { + font-size: 13px; + font-weight: normal; + } + + .index-column { + float: left; + width: 30%; + min-width: 200px; + font-size: 11px; + } + + .index-column ul { + margin: 8px 0 0 0; + padding: 0; + list-style: none; + } + + .index-column ul li { + margin: 0 0 3px 0; + padding: 0; + } + + .hierarchy-column { + min-width: 400px; + } + + .hierarchy-column ul { + margin: 3px 0 0 15px; + } + + .hierarchy-column ul li { + list-style-type: square; + } + + .title { + font-weight: normal; + font-size: 215%; + margin-top:0; + } + + .subtitle { + font-weight: normal; + font-size: 180%; + color: #3C4C6C; + border-bottom: 1px solid #5088C5; + } + + .subsubtitle { + font-weight: normal; + font-size: 145%; + height: 0.7em; + } + + .note { + border: 1px solid #5088C5; + background-color: white; + margin: 1.667em 0 1.75em 0; + padding: 0 .667em .083em .750em; + } + + .warning { + border: 1px solid #5088C5; + background-color: #F0F3F7; + margin-bottom: 0.5em; + padding: 0.3em 0.8em; + } + + .bug { + border: 1px solid #000; + background-color: #ffffcc; + margin-bottom: 0.5em; + padding: 0.3em 0.8em; + } + + .deprecated { + color: #F60425; + } + + .section { + margin-top: 3em; + } + + .section-specification { + margin-left: 2.5em; + margin-right: 2.5em; + font-size: 12px; + } + + .section-specification table { + margin-bottom: 0em; + border-top: 1px solid #d6e0e5; + } + + .section-specification td { + vertical-align: top; + border-bottom: 1px solid #d6e0e5; + border-left-width: 0px; + border-right-width: 0px; + border-top-width: 0px; + padding: .6em; + } + + .section-specification .specification-title { + font-weight: bold; + } + + .task-list { + list-style-type: none; + padding-left: 0px; + } + + .task-list li { + margin-bottom: 3px; + } + + .task-item-suffix { + color: #996; + font-size: 12px; + font-style: italic; + margin-left: 0.5em; + } + + span.tooltip span.tooltip { + font-size: 1.0em; + display: none; + padding: 0.3em; + border: 1px solid #aaa; + background-color: #fdfec8; + color: #000; + text-align: left; + } + + span.tooltip:hover span.tooltip { + display: block; + position: absolute; + margin-left: 2em; + } + + .section-method { + margin-top: 2.3em; + } + + .method-title { + margin-bottom: 1.5em; + } + + .method-subtitle { + margin-top: 0.7em; + margin-bottom: 0.2em; + } + + .method-subsection p { + margin-top: 0.4em; + margin-bottom: 0.8em; + } + + .method-declaration { + margin-top:1.182em; + margin-bottom:.909em; + } + + .method-declaration code { + font:14px Courier, Consolas, monospace; + color:#000; + } + + .declaration { + color: #000; + } + + .termdef { + margin-bottom: 10px; + margin-left: 0px; + margin-right: 0px; + margin-top: 0px; + } + + .termdef dt { + margin: 0; + padding: 0; + } + + .termdef dd { + margin-bottom: 6px; + margin-left: 16px; + margin-right: 0px; + margin-top: 1px; + } + + .termdef dd p { + margin-bottom: 6px; + margin-left: 0px; + margin-right: 0px; + margin-top: -1px; + } + + .argument-def { + margin-top: 0.3em; + margin-bottom: 0.3em; + } + + .argument-def dd { + margin-left: 1.25em; + } + + .see-also-section ul { + list-style-type: none; + padding-left: 0px; + margin-top: 0; + } + + .see-also-section li { + margin-bottom: 3px; + } + + .declared-in-ref { + color: #666; + } + + #tocContainer.hideInXcode { + display: none; + border: 0px solid black; + } + + #top_header.hideInXcode { + display: none; + } + + #contents.hideInXcode { + border: 0px solid black; + top: 0px; + left: 0px; + } + + +} \ No newline at end of file diff --git a/devsite/source/_sass/sections/documentation/js.scss b/devsite/source/_sass/sections/documentation/js.scss new file mode 100644 index 00000000..625bc9db --- /dev/null +++ b/devsite/source/_sass/sections/documentation/js.scss @@ -0,0 +1,23 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.documentation__js { + .desc-list { + list-style: circle; + margin-left: 25px; + margin-bottom: 20px + } +} \ No newline at end of file diff --git a/devsite/source/_sass/sections/events.scss b/devsite/source/_sass/sections/events.scss new file mode 100644 index 00000000..7d878fca --- /dev/null +++ b/devsite/source/_sass/sections/events.scss @@ -0,0 +1,145 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.events__map { + height: 260px; + width: 100%; + margin-bottom: 16px; +} + +.events__calendar { + width: 100%; + background-color: $white; + margin-bottom: 1em; + + th, + td { + font-weight: 600; + text-align: center; + } + + .calendar__month, + .calendar__arrow { + background-color: $gray-09; + font-weight: 700; + color: $white; + } + + .calendar__arrow { + font-size: 1.2em; + padding: 0; + + a { + color: $white; + } + } + + .calendar__month { + text-transform: uppercase; + font-size: 13px; + } + + .calendar__days { + th { + font-size: 12px; + } + } + + .calendar__day--past { + color: $gray-06; + } + + .calendar__day--event { + background-color: $green; + color: #fff; + } + + .calendar__day--past-event { + background-color: $gray-06; + color: #fff; + } + + td { + padding: 0.25em; + width: 1/7 * 100%; + } +} + +.events__list { + + .event { + margin-bottom: 0.5em; + + h3 { + margin: 0; + overflow: hidden; + font-size: 1.3em; + } + + .event__meta { + font-size: 0.9em; + color: darken($gray-09, 20); + margin-bottom: 0.5em; + } + } + + .event--highlighted { + padding: 0 10px; + border-left: 10px solid $color-community; + margin-left: -20px; + } + + .event--past { + a { + color: lighten($base-link-color, 15); + } + } +} + +.retreat-2015__page { + background-color: rgb(47, 89, 108); + background-image: url('/assets/images/community/events/developer-retreat-2015/san-francisco.jpg'); + background-repeat: no-repeat; + background-size: cover; + background-position: center center; +} + +.retreat-2015__stripe { + text-align: center; + background-color: rgba(40, 40, 40, 0.8); + color: #fff; + text-transform: uppercase; + padding: 2em; + + h1 { + font-weight: 500; + font-size: 3em; + margin-bottom: 0; + } + + h2 { + font-weight: 300; + margin-bottom: 0; + } + + form { + margin-top: 3em; + } + + .form_group { + margin-bottom: 0; + } +} diff --git a/devsite/source/_sass/sections/examples.scss b/devsite/source/_sass/sections/examples.scss new file mode 100644 index 00000000..b69fa70b --- /dev/null +++ b/devsite/source/_sass/sections/examples.scss @@ -0,0 +1,66 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.examples__filters { + margin-bottom: 1em; + + &:last-child { + margin-bottom: 0; + } + + h3 { + margin: 0; + } +} + +.examples__tag { + background-color: $color-examples; + border-radius: 3px; + color: $white; + display: inline-block; + font-size: 0.75em; + line-height: 1; + margin-bottom: 0.2em; + min-width: 3em; + padding: 0.3em; + text-align: center; + text-transform: uppercase; + + &:hover, + &:active, + &:focus { + color: $white; + background-color: darken($color-examples, 20); + } +} + +.examples__filters--selected { + .examples__tag { + background-color: lighten($color-examples, 20); + + &.selected { + background-color: $color-examples; + } + } +} + +.example__meta { + margin-top: -1em; + margin-bottom: 0.5em; + font-size: 0.8em; + text-transform: uppercase; + color: $gray-09; +} diff --git a/devsite/source/_sass/sections/getting-started.scss b/devsite/source/_sass/sections/getting-started.scss new file mode 100644 index 00000000..bba1c25a --- /dev/null +++ b/devsite/source/_sass/sections/getting-started.scss @@ -0,0 +1,35 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.getting-started { + + .title-image { + background-position: center; + background-repeat: no-repeat; + height: 100%; + } + + .watchface-image { + background-image: url("/assets/images/getting-started/long-pebble.png"); + } + .one-click-action-image { + background-image: url("/assets/images/getting-started/pebble-long-grey.svg"); + } + .book-image { + background-image: url("/assets/images/getting-started/pebble-long-grey.svg"); + } + +} diff --git a/devsite/source/_sass/sections/guides.scss b/devsite/source/_sass/sections/guides.scss new file mode 100644 index 00000000..7b7fb66c --- /dev/null +++ b/devsite/source/_sass/sections/guides.scss @@ -0,0 +1,52 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.guides__toc { + + h2 { + margin: 0; + font-weight: 300; + font-size: 1.5em; + } + + h3 { + margin: 0; + font-weight: 300; + font-size: 1.3em; + } + + h4 { + margin: 0; + font-weight: 600; + font-size: 1.1em; + } + + p { + margin: 0 0 0.5em 0; + } +} + +.centered-table { + + td, th { + text-align:center; + } +} + +table td hr { + margin: 0; + border-bottom-color: $gray-06; +} diff --git a/devsite/source/_sass/sections/inspiration.scss b/devsite/source/_sass/sections/inspiration.scss new file mode 100644 index 00000000..a1958617 --- /dev/null +++ b/devsite/source/_sass/sections/inspiration.scss @@ -0,0 +1,42 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.inspiration-page { + h2 { + color: $purple; + font-size: 1.5rem; + margin: 1rem 0 0; + text-transform: uppercase; + line-height: 1; + } + + blockquote { + font-size: 1.3rem; + border: 0; + color: lighten($base-font-color, 20); + margin: 0; + padding: 0; + + p { + margin: 0.5rem 0 1rem; + line-height: 1; + } + } + + h3 { + font-size: 1.1rem; + } +} diff --git a/devsite/source/_sass/sections/retreat.scss b/devsite/source/_sass/sections/retreat.scss new file mode 100644 index 00000000..b6242196 --- /dev/null +++ b/devsite/source/_sass/sections/retreat.scss @@ -0,0 +1,178 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$banner-blue: #066AC4; + +.retreat-page { + font-size: 1.3rem; + + h1 { + background-color: $banner-blue; + color: #fff; + font-size: 1.6em; + margin: 0; + padding: 2rem 0; + text-align: center; + text-transform: uppercase; + width: 100%; + } + + .anchor { + &::before { + content: ' '; + display: block; + height: $header-height + 60px; + margin-top: -1 * ($header-height + 60px); + visibility: hidden; + } + } + + .container { + margin-top: 1rem; + } +} + +.retreat-banner { + background-color: #066AC4; + overflow: hidden; + position: relative; + width: 100%; + max-height: 620px; + + @include bp-max (xs) { + padding-top: 3rem; + } + + img, + svg { + width: 100%; + display: block; + max-width: 1300px; + margin: 0 auto; + } +} + +.retreat-nav { + background-color: transparentize(#222, 0.3); + left: $sidebar-width + $section-menu-width; + position: fixed; + text-align: center; + top: $header-height; + right: 0; + z-index: 100; + + @include bp-max ($sidebar-hide-at) { + left: 0; + width: 100%; + } + + .nav { + display: inline-block; + margin: 0; + } + + ul { + @include display(flex); + } + + li { + @include flex(1 0 auto); + display: block; + + &:last-child > a { + margin-right: 0 !important; + } + + > a { + color: #fff; + display: block; + padding: 1rem 0; + + &:hover, + &:focus { + background-color: transparentize(darken($banner-blue, 30), 0.3); + } + } + } +} + +.retreat-location { + + .row { + background-image: url('../images/community/events/developer-retreat-2015/san-francisco.jpg'); + height: 35vw; + background-size: cover; + background-position: center bottom; + min-height: 300px; + } + + .retreat-location-strap { + background-color: rgba(255, 255, 255, 0.7); + text-align: center; + margin-bottom: 0; + padding: 1em 0; + font-size: 1.2rem; + margin-top: 5vw; + + @include bp-min(s) { + padding: 2em 0; + margin-top: 20vw; + font-size: 1.3em; + } + } + +} + +.retreat-faq { + + dt, + dd { + line-height: 1.3em; + margin: 0 0 1em; + padding: 0.5em 0 0 2.5em; + position: relative; + + &::before { + border-radius: $base-border-radius; + color: $white; + font-size: 1.4em; + font-weight: normal; + left: 0; + line-height: 1em; + padding: 0.5rem 0; + position: absolute; + text-align: center; + top: 0; + width: 1.4em; + } + } + + dt { + + &::before { + background-color: $pebble-pink; + content: 'Q'; + } + } + + dd { + + &::before { + background-color: $pebble-green; + content: 'A'; + } + } +} diff --git a/devsite/source/_sass/sections/round.scss b/devsite/source/_sass/sections/round.scss new file mode 100644 index 00000000..4f821c59 --- /dev/null +++ b/devsite/source/_sass/sections/round.scss @@ -0,0 +1,99 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.round-landing { + background-image: url('../images/round/splash.jpg'); + background-repeat: no-repeat; + background-position: center bottom; + background-size: cover; + + @include bp-max (xs) { + height: 120%; + } +} + +.round-landing__contents { + max-width: 800px; + width: 80%; + color: $white; + text-align: center; + padding-top: 5%; + margin: 0 auto; +} + +.round-landing__header { + font-size: 3rem; +} + +.round-landing__text { + font-size: 1.5rem; + width: 80%; + margin: 0 auto 2rem; +} + +@include bp-max (s) { + .round-landing__buttons { + .btn { + display: block; + width: 100%; + margin-bottom: 1rem; + } + + .btn + .btn { + margin-left: 0; + } + } +} + +@include bp-min (m) { + .round-landing__buttons { + display: -webkit-flex; + display: flex; + + .btn { + flex: 1 1 auto; + -webkit-flex: 1 1 auto; + } + } +} + +.round-landing__lab { + font-size: 1.2em; + font-weight: 300; + background-color: rgba(200, 200, 200, 0.7); + color: $black; + padding: 0.5em 0.8em; + margin: 1em 0; + + a { + color: $black; + text-decoration: underline; + + &:hover { + color: $black; + } + } + + @include bp-min (m) { + position: absolute; + left: $sidebar-width + 20px; + bottom: 20px; + width: 50%; + min-width: 300px; + text-align: left; + margin: 0; + } +} diff --git a/devsite/source/_sass/sections/sdk.scss b/devsite/source/_sass/sections/sdk.scss new file mode 100644 index 00000000..b4c2db51 --- /dev/null +++ b/devsite/source/_sass/sections/sdk.scss @@ -0,0 +1,26 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.sdk4-landing { + background-image: url('../images/landing-page/devblog.jpg'); + background-repeat: no-repeat; + background-position: center bottom; + background-size: cover; + + @include bp-max (xs) { + height: 120%; + } +} diff --git a/devsite/source/assets/css/legal.scss b/devsite/source/assets/css/legal.scss new file mode 100644 index 00000000..cafedff9 --- /dev/null +++ b/devsite/source/assets/css/legal.scss @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +--- +--- + +.markdown { + + ol ol { + list-style: lower-alpha; + } +} diff --git a/devsite/source/assets/css/main.scss b/devsite/source/assets/css/main.scss new file mode 100644 index 00000000..f5a236f9 --- /dev/null +++ b/devsite/source/assets/css/main.scss @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +--- +--- +@import 'app'; \ No newline at end of file diff --git a/devsite/source/assets/css/noscript.css b/devsite/source/assets/css/noscript.css new file mode 100644 index 00000000..9ef2f1da --- /dev/null +++ b/devsite/source/assets/css/noscript.css @@ -0,0 +1,23 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.noscript--show { + display: inherit; +} + +.noscript--hide { + display: none; +} \ No newline at end of file diff --git a/devsite/source/assets/favicon.png b/devsite/source/assets/favicon.png new file mode 100644 index 00000000..619d216c Binary files /dev/null and b/devsite/source/assets/favicon.png differ diff --git a/devsite/source/assets/images/404-pebble.png b/devsite/source/assets/images/404-pebble.png new file mode 100644 index 00000000..e4e78993 Binary files /dev/null and b/devsite/source/assets/images/404-pebble.png differ diff --git a/devsite/source/assets/images/blog/1219-an-notif.png b/devsite/source/assets/images/blog/1219-an-notif.png new file mode 100644 index 00000000..49d50047 Binary files /dev/null and b/devsite/source/assets/images/blog/1219-an-notif.png differ diff --git a/devsite/source/assets/images/blog/1219-an-pause.png b/devsite/source/assets/images/blog/1219-an-pause.png new file mode 100644 index 00000000..7c4df41b Binary files /dev/null and b/devsite/source/assets/images/blog/1219-an-pause.png differ diff --git a/devsite/source/assets/images/blog/1219-an-pebble-interrupt.png b/devsite/source/assets/images/blog/1219-an-pebble-interrupt.png new file mode 100644 index 00000000..56e4e52c Binary files /dev/null and b/devsite/source/assets/images/blog/1219-an-pebble-interrupt.png differ diff --git a/devsite/source/assets/images/blog/1219-header.jpg b/devsite/source/assets/images/blog/1219-header.jpg new file mode 100644 index 00000000..d7a4e172 Binary files /dev/null and b/devsite/source/assets/images/blog/1219-header.jpg differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image00.png b/devsite/source/assets/images/blog/2016-03-07-image00.png new file mode 100644 index 00000000..0b2caf78 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image00.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image01.png b/devsite/source/assets/images/blog/2016-03-07-image01.png new file mode 100644 index 00000000..39bd4358 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image01.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image02.png b/devsite/source/assets/images/blog/2016-03-07-image02.png new file mode 100644 index 00000000..5d69960b Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image02.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image03.png b/devsite/source/assets/images/blog/2016-03-07-image03.png new file mode 100644 index 00000000..ddb0b822 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image03.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image04.png b/devsite/source/assets/images/blog/2016-03-07-image04.png new file mode 100644 index 00000000..3ee14904 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image04.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image05.jpg b/devsite/source/assets/images/blog/2016-03-07-image05.jpg new file mode 100644 index 00000000..cdd0d2de Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image05.jpg differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image06.png b/devsite/source/assets/images/blog/2016-03-07-image06.png new file mode 100644 index 00000000..7c1b41c2 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image06.png differ diff --git a/devsite/source/assets/images/blog/2016-03-07-image07.png b/devsite/source/assets/images/blog/2016-03-07-image07.png new file mode 100644 index 00000000..ed94ca9e Binary files /dev/null and b/devsite/source/assets/images/blog/2016-03-07-image07.png differ diff --git a/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/pebblejs.png b/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/pebblejs.png new file mode 100644 index 00000000..44ef880b Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/pebblejs.png differ diff --git a/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/settings.png b/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/settings.png new file mode 100644 index 00000000..5fabe99f Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-13-pebble-js-round/settings.png differ diff --git a/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/core.jpg b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/core.jpg new file mode 100644 index 00000000..2fa8eb7e Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/core.jpg differ diff --git a/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif new file mode 100644 index 00000000..a4990001 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif differ diff --git a/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/peek.png b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/peek.png new file mode 100644 index 00000000..4db55c90 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/peek.png differ diff --git a/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/smartwatches.png b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/smartwatches.png new file mode 100644 index 00000000..cfabe06a Binary files /dev/null and b/devsite/source/assets/images/blog/2016-05-24-kickstarter-3/smartwatches.png differ diff --git a/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/uber.gif b/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/uber.gif new file mode 100644 index 00000000..d7c60bcf Binary files /dev/null and b/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/uber.gif differ diff --git a/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/virtual-pet.png b/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/virtual-pet.png new file mode 100644 index 00000000..707e1510 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-06-15-sdk4-preview/virtual-pet.png differ diff --git a/devsite/source/assets/images/blog/2016-06-24-introducing-clay/clay-example.png b/devsite/source/assets/images/blog/2016-06-24-introducing-clay/clay-example.png new file mode 100644 index 00000000..fee1dfa3 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-06-24-introducing-clay/clay-example.png differ diff --git a/devsite/source/assets/images/blog/2016-08-16-jotw.png b/devsite/source/assets/images/blog/2016-08-16-jotw.png new file mode 100644 index 00000000..86da3727 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-08-16-jotw.png differ diff --git a/devsite/source/assets/images/blog/2016-08-19-simplicity-qv.png b/devsite/source/assets/images/blog/2016-08-19-simplicity-qv.png new file mode 100644 index 00000000..676e8163 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-08-19-simplicity-qv.png differ diff --git a/devsite/source/assets/images/blog/2016-08-19-simplicity-std.png b/devsite/source/assets/images/blog/2016-08-19-simplicity-std.png new file mode 100644 index 00000000..31dd727d Binary files /dev/null and b/devsite/source/assets/images/blog/2016-08-19-simplicity-std.png differ diff --git a/devsite/source/assets/images/blog/2016-09-06-about-window.png b/devsite/source/assets/images/blog/2016-09-06-about-window.png new file mode 100644 index 00000000..391924dd Binary files /dev/null and b/devsite/source/assets/images/blog/2016-09-06-about-window.png differ diff --git a/devsite/source/assets/images/blog/2016-09-06-quick-launch.gif b/devsite/source/assets/images/blog/2016-09-06-quick-launch.gif new file mode 100644 index 00000000..437bb504 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-09-06-quick-launch.gif differ diff --git a/devsite/source/assets/images/blog/2016-09-06-system-notification.png b/devsite/source/assets/images/blog/2016-09-06-system-notification.png new file mode 100644 index 00000000..4aa72a8a Binary files /dev/null and b/devsite/source/assets/images/blog/2016-09-06-system-notification.png differ diff --git a/devsite/source/assets/images/blog/2016-09-06-timeline-notification.png b/devsite/source/assets/images/blog/2016-09-06-timeline-notification.png new file mode 100644 index 00000000..cb3146c4 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-09-06-timeline-notification.png differ diff --git a/devsite/source/assets/images/blog/2016-10-11-bezel.png b/devsite/source/assets/images/blog/2016-10-11-bezel.png new file mode 100644 index 00000000..ee582c8c Binary files /dev/null and b/devsite/source/assets/images/blog/2016-10-11-bezel.png differ diff --git a/devsite/source/assets/images/blog/2016-10-11-contentsize.png b/devsite/source/assets/images/blog/2016-10-11-contentsize.png new file mode 100644 index 00000000..25d9a99f Binary files /dev/null and b/devsite/source/assets/images/blog/2016-10-11-contentsize.png differ diff --git a/devsite/source/assets/images/blog/2016-10-11-dpi-comparison.png b/devsite/source/assets/images/blog/2016-10-11-dpi-comparison.png new file mode 100644 index 00000000..0376f360 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-10-11-dpi-comparison.png differ diff --git a/devsite/source/assets/images/blog/2016-10-11-intro.jpg b/devsite/source/assets/images/blog/2016-10-11-intro.jpg new file mode 100644 index 00000000..12e8432d Binary files /dev/null and b/devsite/source/assets/images/blog/2016-10-11-intro.jpg differ diff --git a/devsite/source/assets/images/blog/2016-12-22-pebble-js.jpg b/devsite/source/assets/images/blog/2016-12-22-pebble-js.jpg new file mode 100644 index 00000000..c0a7f3f1 Binary files /dev/null and b/devsite/source/assets/images/blog/2016-12-22-pebble-js.jpg differ diff --git a/devsite/source/assets/images/blog/3.x-on-tintin.png b/devsite/source/assets/images/blog/3.x-on-tintin.png new file mode 100644 index 00000000..430b4fcd Binary files /dev/null and b/devsite/source/assets/images/blog/3.x-on-tintin.png differ diff --git a/devsite/source/assets/images/blog/app_font_browser.png b/devsite/source/assets/images/blog/app_font_browser.png new file mode 100644 index 00000000..ad315fe5 Binary files /dev/null and b/devsite/source/assets/images/blog/app_font_browser.png differ diff --git a/devsite/source/assets/images/blog/bezier-banner.png b/devsite/source/assets/images/blog/bezier-banner.png new file mode 100644 index 00000000..fe3e0cc9 Binary files /dev/null and b/devsite/source/assets/images/blog/bezier-banner.png differ diff --git a/devsite/source/assets/images/blog/bezier-result.png b/devsite/source/assets/images/blog/bezier-result.png new file mode 100644 index 00000000..c231874f Binary files /dev/null and b/devsite/source/assets/images/blog/bezier-result.png differ diff --git a/devsite/source/assets/images/blog/block-world.gif b/devsite/source/assets/images/blog/block-world.gif new file mode 100644 index 00000000..641a4b6d Binary files /dev/null and b/devsite/source/assets/images/blog/block-world.gif differ diff --git a/devsite/source/assets/images/blog/checkbox.png b/devsite/source/assets/images/blog/checkbox.png new file mode 100644 index 00000000..8ac96a6c Binary files /dev/null and b/devsite/source/assets/images/blog/checkbox.png differ diff --git a/devsite/source/assets/images/blog/developer-relations-team-update/faces.png b/devsite/source/assets/images/blog/developer-relations-team-update/faces.png new file mode 100644 index 00000000..990313ff Binary files /dev/null and b/devsite/source/assets/images/blog/developer-relations-team-update/faces.png differ diff --git a/devsite/source/assets/images/blog/developer-relations-team-update/retreat.jpg b/devsite/source/assets/images/blog/developer-relations-team-update/retreat.jpg new file mode 100644 index 00000000..4ffd2a69 Binary files /dev/null and b/devsite/source/assets/images/blog/developer-relations-team-update/retreat.jpg differ diff --git a/devsite/source/assets/images/blog/dictation-flow.png b/devsite/source/assets/images/blog/dictation-flow.png new file mode 100644 index 00000000..f9538688 Binary files /dev/null and b/devsite/source/assets/images/blog/dictation-flow.png differ diff --git a/devsite/source/assets/images/blog/dictation-recognizer.png b/devsite/source/assets/images/blog/dictation-recognizer.png new file mode 100644 index 00000000..76c795d7 Binary files /dev/null and b/devsite/source/assets/images/blog/dictation-recognizer.png differ diff --git a/devsite/source/assets/images/blog/getting-started-timeline.png b/devsite/source/assets/images/blog/getting-started-timeline.png new file mode 100644 index 00000000..aafc9894 Binary files /dev/null and b/devsite/source/assets/images/blog/getting-started-timeline.png differ diff --git a/devsite/source/assets/images/blog/package-tracker-pin.png b/devsite/source/assets/images/blog/package-tracker-pin.png new file mode 100644 index 00000000..43762476 Binary files /dev/null and b/devsite/source/assets/images/blog/package-tracker-pin.png differ diff --git a/devsite/source/assets/images/blog/package-tracker.png b/devsite/source/assets/images/blog/package-tracker.png new file mode 100644 index 00000000..837148a6 Binary files /dev/null and b/devsite/source/assets/images/blog/package-tracker.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_abc.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_abc.png new file mode 100644 index 00000000..a6ee5239 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_digits.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_digits.png new file mode 100644 index 00000000..7bded3bc Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_30_black_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_34_medium_numbers_digits.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_34_medium_numbers_digits.png new file mode 100644 index 00000000..aca26171 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_34_medium_numbers_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_abc.png new file mode 100644 index 00000000..e7128ac9 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_digits.png new file mode 100644 index 00000000..15e64bb8 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_abc.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_abc.png new file mode 100644 index 00000000..9ee07169 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_digits.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_digits.png new file mode 100644 index 00000000..e14ddf44 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_light_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/bitham_42_medium_numbers_digits.png b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_medium_numbers_digits.png new file mode 100644 index 00000000..a14ab93d Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/bitham_42_medium_numbers_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_abc.png new file mode 100644 index 00000000..2f50948e Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_digits.png new file mode 100644 index 00000000..f67fe5f5 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/droid_28_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_14_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_abc.png new file mode 100644 index 00000000..53cd5c55 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_abc.png new file mode 100644 index 00000000..caec9a15 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_digits.png new file mode 100644 index 00000000..a9cd3ab3 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_14_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_digits.png new file mode 100644 index 00000000..ae16bd41 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_14_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_18_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_abc.png new file mode 100644 index 00000000..d32bf88b Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_abc.png new file mode 100644 index 00000000..3e13c0d1 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_digits.png new file mode 100644 index 00000000..b7a61469 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_18_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_digits.png new file mode 100644 index 00000000..5b920aeb Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_18_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_24_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_abc.png new file mode 100644 index 00000000..b5ed629c Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_abc.png new file mode 100644 index 00000000..5c537f7d Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_digits.png new file mode 100644 index 00000000..58599fd3 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_24_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_digits.png new file mode 100644 index 00000000..3f27653b Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_24_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_28_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_abc.png new file mode 100644 index 00000000..331d3bc1 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_abc.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_abc.png new file mode 100644 index 00000000..657c3e8d Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_digits.png new file mode 100644 index 00000000..446b280c Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_bold_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/gothic_28_digits.png b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_digits.png new file mode 100644 index 00000000..784d99c7 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/gothic_28_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_abc.png b/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_abc.png new file mode 100644 index 00000000..3384b0a4 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_abc.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_digits.png b/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_digits.png new file mode 100644 index 00000000..c5dcd33c Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/roboto_21_condensed_digits.png differ diff --git a/devsite/source/assets/images/blog/pebble-fonts/roboto_49_bold_subset_digits.png b/devsite/source/assets/images/blog/pebble-fonts/roboto_49_bold_subset_digits.png new file mode 100644 index 00000000..bd8408a1 Binary files /dev/null and b/devsite/source/assets/images/blog/pebble-fonts/roboto_49_bold_subset_digits.png differ diff --git a/devsite/source/assets/images/blog/road30-actionbar.png b/devsite/source/assets/images/blog/road30-actionbar.png new file mode 100644 index 00000000..48a1e73a Binary files /dev/null and b/devsite/source/assets/images/blog/road30-actionbar.png differ diff --git a/devsite/source/assets/images/blog/road30-actionmenu.gif b/devsite/source/assets/images/blog/road30-actionmenu.gif new file mode 100644 index 00000000..6f774d19 Binary files /dev/null and b/devsite/source/assets/images/blog/road30-actionmenu.gif differ diff --git a/devsite/source/assets/images/blog/road30-aplite-differences.png b/devsite/source/assets/images/blog/road30-aplite-differences.png new file mode 100644 index 00000000..ce6a2e18 Binary files /dev/null and b/devsite/source/assets/images/blog/road30-aplite-differences.png differ diff --git a/devsite/source/assets/images/blog/road30-appfaces.png b/devsite/source/assets/images/blog/road30-appfaces.png new file mode 100644 index 00000000..09282064 Binary files /dev/null and b/devsite/source/assets/images/blog/road30-appfaces.png differ diff --git a/devsite/source/assets/images/blog/road30-banner.png b/devsite/source/assets/images/blog/road30-banner.png new file mode 100644 index 00000000..07e799dc Binary files /dev/null and b/devsite/source/assets/images/blog/road30-banner.png differ diff --git a/devsite/source/assets/images/blog/road30-card.gif b/devsite/source/assets/images/blog/road30-card.gif new file mode 100644 index 00000000..0236675e Binary files /dev/null and b/devsite/source/assets/images/blog/road30-card.gif differ diff --git a/devsite/source/assets/images/blog/road30-menulayer.gif b/devsite/source/assets/images/blog/road30-menulayer.gif new file mode 100644 index 00000000..9c20e415 Binary files /dev/null and b/devsite/source/assets/images/blog/road30-menulayer.gif differ diff --git a/devsite/source/assets/images/blog/road30-statusbar.png b/devsite/source/assets/images/blog/road30-statusbar.png new file mode 100644 index 00000000..d92502fc Binary files /dev/null and b/devsite/source/assets/images/blog/road30-statusbar.png differ diff --git a/devsite/source/assets/images/blog/road30-timeline.gif b/devsite/source/assets/images/blog/road30-timeline.gif new file mode 100644 index 00000000..37ae127e Binary files /dev/null and b/devsite/source/assets/images/blog/road30-timeline.gif differ diff --git a/devsite/source/assets/images/blog/tips-result-aplite.png b/devsite/source/assets/images/blog/tips-result-aplite.png new file mode 100644 index 00000000..ad7f793e Binary files /dev/null and b/devsite/source/assets/images/blog/tips-result-aplite.png differ diff --git a/devsite/source/assets/images/blog/tips-result-basalt.png b/devsite/source/assets/images/blog/tips-result-basalt.png new file mode 100644 index 00000000..e4ca4dbc Binary files /dev/null and b/devsite/source/assets/images/blog/tips-result-basalt.png differ diff --git a/devsite/source/assets/images/blog/tips-transparency-globe.png b/devsite/source/assets/images/blog/tips-transparency-globe.png new file mode 100644 index 00000000..6e34cac1 Binary files /dev/null and b/devsite/source/assets/images/blog/tips-transparency-globe.png differ diff --git a/devsite/source/assets/images/blog/tut_1_lifecycle.png b/devsite/source/assets/images/blog/tut_1_lifecycle.png new file mode 100644 index 00000000..10853245 Binary files /dev/null and b/devsite/source/assets/images/blog/tut_1_lifecycle.png differ diff --git a/devsite/source/assets/images/blog/tut_1_preview.png b/devsite/source/assets/images/blog/tut_1_preview.png new file mode 100644 index 00000000..f6de8761 Binary files /dev/null and b/devsite/source/assets/images/blog/tut_1_preview.png differ diff --git a/devsite/source/assets/images/blog/tut_2_frame.png b/devsite/source/assets/images/blog/tut_2_frame.png new file mode 100644 index 00000000..35123cde Binary files /dev/null and b/devsite/source/assets/images/blog/tut_2_frame.png differ diff --git a/devsite/source/assets/images/blog/tut_2_frame_added.png b/devsite/source/assets/images/blog/tut_2_frame_added.png new file mode 100644 index 00000000..2fa43c60 Binary files /dev/null and b/devsite/source/assets/images/blog/tut_2_frame_added.png differ diff --git a/devsite/source/assets/images/blog/tut_2_largetext.png b/devsite/source/assets/images/blog/tut_2_largetext.png new file mode 100644 index 00000000..c1987ca9 Binary files /dev/null and b/devsite/source/assets/images/blog/tut_2_largetext.png differ diff --git a/devsite/source/assets/images/getting-started/long-pebble.png b/devsite/source/assets/images/getting-started/long-pebble.png new file mode 100644 index 00000000..30a82179 Binary files /dev/null and b/devsite/source/assets/images/getting-started/long-pebble.png differ diff --git a/devsite/source/assets/images/getting-started/pebble-long-grey.svg b/devsite/source/assets/images/getting-started/pebble-long-grey.svg new file mode 100644 index 00000000..301db5f2 --- /dev/null +++ b/devsite/source/assets/images/getting-started/pebble-long-grey.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/getting-started/pebble-long.svg b/devsite/source/assets/images/getting-started/pebble-long.svg new file mode 100644 index 00000000..ba283892 --- /dev/null +++ b/devsite/source/assets/images/getting-started/pebble-long.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~aplite.png new file mode 100644 index 00000000..6c9419ca Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~basalt.png new file mode 100644 index 00000000..6c9419ca Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~chalk.png new file mode 100644 index 00000000..91cbc3c3 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-blank~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~aplite.png new file mode 100644 index 00000000..23f48d8d Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~basalt.png new file mode 100644 index 00000000..23f48d8d Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~chalk.png new file mode 100644 index 00000000..e7972c51 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-textlayer-test~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~aplite.png new file mode 100644 index 00000000..75be395a Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~basalt.png new file mode 100644 index 00000000..75be395a Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~chalk.png new file mode 100644 index 00000000..927ea9ed Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/1-time~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~aplite.png new file mode 100644 index 00000000..3a1c8407 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~basalt.png new file mode 100644 index 00000000..3a1c8407 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~chalk.png new file mode 100644 index 00000000..ac528547 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-custom-font~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-final.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final.png new file mode 100644 index 00000000..1ed91fa6 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~aplite.png new file mode 100644 index 00000000..2488d808 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~basalt.png new file mode 100644 index 00000000..2488d808 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~chalk.png new file mode 100644 index 00000000..1c0b23e3 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/2-final~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-final.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final.png new file mode 100644 index 00000000..1641f9bc Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~aplite.png new file mode 100644 index 00000000..2b8dac86 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~basalt.png new file mode 100644 index 00000000..2b8dac86 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~chalk.png new file mode 100644 index 00000000..64bdf7f3 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-final~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading.png new file mode 100644 index 00000000..dbab8d2d Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~aplite.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~aplite.png new file mode 100644 index 00000000..ccdf0096 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~aplite.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~basalt.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~basalt.png new file mode 100644 index 00000000..5a0b861a Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~basalt.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~chalk.png b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~chalk.png new file mode 100644 index 00000000..97e7fe63 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/3-loading~chalk.png differ diff --git a/devsite/source/assets/images/getting-started/watchface-tutorial/background.png b/devsite/source/assets/images/getting-started/watchface-tutorial/background.png new file mode 100644 index 00000000..4c5b7cf4 Binary files /dev/null and b/devsite/source/assets/images/getting-started/watchface-tutorial/background.png differ diff --git a/devsite/source/assets/images/guides/3.0/timeline-architecture.png b/devsite/source/assets/images/guides/3.0/timeline-architecture.png new file mode 100644 index 00000000..c209dc83 Binary files /dev/null and b/devsite/source/assets/images/guides/3.0/timeline-architecture.png differ diff --git a/devsite/source/assets/images/guides/app-resources/cp-bitmap-attributes.png b/devsite/source/assets/images/guides/app-resources/cp-bitmap-attributes.png new file mode 100644 index 00000000..a2ba14c4 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/cp-bitmap-attributes.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/bitham_30_black_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/bitham_30_black_preview.png new file mode 100644 index 00000000..020a0cf8 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/bitham_30_black_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/bitham_34_medium_numbers_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/bitham_34_medium_numbers_preview.png new file mode 100644 index 00000000..7b7fd4e7 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/bitham_34_medium_numbers_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_bold_preview.png new file mode 100644 index 00000000..fae898dd Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_light_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_light_preview.png new file mode 100644 index 00000000..dc033905 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_light_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_medium_numbers_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_medium_numbers_preview.png new file mode 100644 index 00000000..1f2646c3 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/bitham_42_medium_numbers_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/droid_28_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/droid_28_bold_preview.png new file mode 100644 index 00000000..fef7a5c2 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/droid_28_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_bold_preview.png new file mode 100644 index 00000000..6d88513f Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_preview.png new file mode 100644 index 00000000..97fc6df1 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_14_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_bold_preview.png new file mode 100644 index 00000000..323eaf2f Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_preview.png new file mode 100644 index 00000000..cf84b07b Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_18_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_bold_preview.png new file mode 100644 index 00000000..02d185b0 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_preview.png new file mode 100644 index 00000000..31385557 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_24_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_bold_preview.png new file mode 100644 index 00000000..bd503eb0 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_preview.png new file mode 100644 index 00000000..83652349 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/gothic_28_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_20_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_20_bold_preview.png new file mode 100644 index 00000000..881d75c6 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_20_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_26_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_26_bold_preview.png new file mode 100644 index 00000000..76b357b7 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_26_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_28_light_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_28_light_preview.png new file mode 100644 index 00000000..9966ccb8 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_28_light_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_32_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_32_bold_preview.png new file mode 100644 index 00000000..fe01036a Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_32_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_36_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_36_bold_preview.png new file mode 100644 index 00000000..11317093 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_36_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_38_bold_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_38_bold_preview.png new file mode 100644 index 00000000..3ca0e8c4 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_38_bold_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/leco_42_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/leco_42_preview.png new file mode 100644 index 00000000..bb54fe5a Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/leco_42_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/roboto_21_condensed_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/roboto_21_condensed_preview.png new file mode 100644 index 00000000..7bd31b2f Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/roboto_21_condensed_preview.png differ diff --git a/devsite/source/assets/images/guides/app-resources/fonts/roboto_49_bold_subset_preview.png b/devsite/source/assets/images/guides/app-resources/fonts/roboto_49_bold_subset_preview.png new file mode 100644 index 00000000..67d43292 Binary files /dev/null and b/devsite/source/assets/images/guides/app-resources/fonts/roboto_49_bold_subset_preview.png differ diff --git a/devsite/source/assets/images/guides/appglance-c/hello-world-app-glance.png b/devsite/source/assets/images/guides/appglance-c/hello-world-app-glance.png new file mode 100644 index 00000000..d1d96a68 Binary files /dev/null and b/devsite/source/assets/images/guides/appglance-c/hello-world-app-glance.png differ diff --git a/devsite/source/assets/images/guides/appglance-c/virtual-pet-app-glance.png b/devsite/source/assets/images/guides/appglance-c/virtual-pet-app-glance.png new file mode 100644 index 00000000..e86275a9 Binary files /dev/null and b/devsite/source/assets/images/guides/appglance-c/virtual-pet-app-glance.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-assets.png b/devsite/source/assets/images/guides/appstore-publishing/app-assets.png new file mode 100644 index 00000000..36c519c2 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-assets.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-category.png b/devsite/source/assets/images/guides/appstore-publishing/app-category.png new file mode 100644 index 00000000..31bd16f4 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-category.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-icons.png b/devsite/source/assets/images/guides/appstore-publishing/app-icons.png new file mode 100644 index 00000000..05506482 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-icons.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-listing.png b/devsite/source/assets/images/guides/appstore-publishing/app-listing.png new file mode 100644 index 00000000..d5426693 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-listing.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-release.png b/devsite/source/assets/images/guides/appstore-publishing/app-release.png new file mode 100644 index 00000000..8661a432 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-release.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/app-title.png b/devsite/source/assets/images/guides/appstore-publishing/app-title.png new file mode 100644 index 00000000..9b985a6b Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/app-title.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/buttons-pressed-example.png b/devsite/source/assets/images/guides/appstore-publishing/buttons-pressed-example.png new file mode 100644 index 00000000..5e14658e Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/buttons-pressed-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/companion-category.png b/devsite/source/assets/images/guides/appstore-publishing/companion-category.png new file mode 100644 index 00000000..2bdc62e9 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/companion-category.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/companion-icons.png b/devsite/source/assets/images/guides/appstore-publishing/companion-icons.png new file mode 100644 index 00000000..4d59aa95 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/companion-icons.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/companion-link.png b/devsite/source/assets/images/guides/appstore-publishing/companion-link.png new file mode 100644 index 00000000..c9bc134e Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/companion-link.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/companion-title.png b/devsite/source/assets/images/guides/appstore-publishing/companion-title.png new file mode 100644 index 00000000..657871bd Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/companion-title.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/crash-count-example.png b/devsite/source/assets/images/guides/appstore-publishing/crash-count-example.png new file mode 100644 index 00000000..6c8f6ba0 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/crash-count-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/face-assets.png b/devsite/source/assets/images/guides/appstore-publishing/face-assets.png new file mode 100644 index 00000000..3b22d2ff Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/face-assets.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/face-listing.png b/devsite/source/assets/images/guides/appstore-publishing/face-listing.png new file mode 100644 index 00000000..74a0186a Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/face-listing.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/face-release-publish.png b/devsite/source/assets/images/guides/appstore-publishing/face-release-publish.png new file mode 100644 index 00000000..f51a8bf6 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/face-release-publish.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/face-release.png b/devsite/source/assets/images/guides/appstore-publishing/face-release.png new file mode 100644 index 00000000..cf2eaada Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/face-release.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/face-title.png b/devsite/source/assets/images/guides/appstore-publishing/face-title.png new file mode 100644 index 00000000..889fd0a3 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/face-title.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/grade-versions.png b/devsite/source/assets/images/guides/appstore-publishing/grade-versions.png new file mode 100644 index 00000000..72994e35 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/grade-versions.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/grade.png b/devsite/source/assets/images/guides/appstore-publishing/grade.png new file mode 100644 index 00000000..ea6b5658 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/grade.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/installations-example.png b/devsite/source/assets/images/guides/appstore-publishing/installations-example.png new file mode 100644 index 00000000..ecb8968b Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/installations-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/launches-example.png b/devsite/source/assets/images/guides/appstore-publishing/launches-example.png new file mode 100644 index 00000000..330ed608 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/launches-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/launching-app-from-pin-example.png b/devsite/source/assets/images/guides/appstore-publishing/launching-app-from-pin-example.png new file mode 100644 index 00000000..429e98fc Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/launching-app-from-pin-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/opening-pin-example.png b/devsite/source/assets/images/guides/appstore-publishing/opening-pin-example.png new file mode 100644 index 00000000..43a358d9 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/opening-pin-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/perspective-right.png b/devsite/source/assets/images/guides/appstore-publishing/perspective-right.png new file mode 100644 index 00000000..e040b639 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/perspective-right.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/pins-opened-example.png b/devsite/source/assets/images/guides/appstore-publishing/pins-opened-example.png new file mode 100644 index 00000000..87410ece Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/pins-opened-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/run-time-example.png b/devsite/source/assets/images/guides/appstore-publishing/run-time-example.png new file mode 100644 index 00000000..aafcb97d Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/run-time-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/run-time-per-launch-example.png b/devsite/source/assets/images/guides/appstore-publishing/run-time-per-launch-example.png new file mode 100644 index 00000000..d32abc51 Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/run-time-per-launch-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/times-launched-example.png b/devsite/source/assets/images/guides/appstore-publishing/times-launched-example.png new file mode 100644 index 00000000..a2bacd8b Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/times-launched-example.png differ diff --git a/devsite/source/assets/images/guides/appstore-publishing/unique-users-example.png b/devsite/source/assets/images/guides/appstore-publishing/unique-users-example.png new file mode 100644 index 00000000..93b3280c Binary files /dev/null and b/devsite/source/assets/images/guides/appstore-publishing/unique-users-example.png differ diff --git a/devsite/source/assets/images/guides/best-practices/select.png b/devsite/source/assets/images/guides/best-practices/select.png new file mode 100644 index 00000000..d639f054 Binary files /dev/null and b/devsite/source/assets/images/guides/best-practices/select.png differ diff --git a/devsite/source/assets/images/guides/best-practices/ux-design-guide.old b/devsite/source/assets/images/guides/best-practices/ux-design-guide.old new file mode 100644 index 00000000..8e5c94f4 Binary files /dev/null and b/devsite/source/assets/images/guides/best-practices/ux-design-guide.old differ diff --git a/devsite/source/assets/images/guides/best-practices/ux-design-guide.png b/devsite/source/assets/images/guides/best-practices/ux-design-guide.png new file mode 100644 index 00000000..8f529901 Binary files /dev/null and b/devsite/source/assets/images/guides/best-practices/ux-design-guide.png differ diff --git a/devsite/source/assets/images/guides/content-size/anim.gif b/devsite/source/assets/images/guides/content-size/anim.gif new file mode 100644 index 00000000..5d8dc816 Binary files /dev/null and b/devsite/source/assets/images/guides/content-size/anim.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/abl-icons.png b/devsite/source/assets/images/guides/design-and-interaction/abl-icons.png new file mode 100644 index 00000000..51ab5132 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/abl-icons.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/action-required.png b/devsite/source/assets/images/guides/design-and-interaction/action-required.png new file mode 100644 index 00000000..5ff8644f Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/action-required.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/actionbar~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/actionbar~aplite.png new file mode 100644 index 00000000..066c374e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/actionbar~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/actionbar~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/actionbar~basalt.png new file mode 100644 index 00000000..933c0b06 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/actionbar~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/actionbar~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/actionbar~chalk.png new file mode 100644 index 00000000..8c563696 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/actionbar~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/actionmenu.png b/devsite/source/assets/images/guides/design-and-interaction/actionmenu.png new file mode 100644 index 00000000..339ae8eb Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/actionmenu.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/actions.png b/devsite/source/assets/images/guides/design-and-interaction/actions.png new file mode 100644 index 00000000..1fc0fa79 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/actions.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/alarm-list-config.png b/devsite/source/assets/images/guides/design-and-interaction/alarm-list-config.png new file mode 100644 index 00000000..5382b1bc Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/alarm-list-config.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/alarm-list~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~aplite.png new file mode 100644 index 00000000..60e67bff Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/alarm-list~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~basalt.png new file mode 100644 index 00000000..bde953d4 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/alarm-list~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~chalk.png new file mode 100644 index 00000000..6294fe7d Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/alarm-list~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/alarms.png b/devsite/source/assets/images/guides/design-and-interaction/alarms.png new file mode 100644 index 00000000..c18813e9 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/alarms.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/calendar-layout.png b/devsite/source/assets/images/guides/design-and-interaction/calendar-layout.png new file mode 100644 index 00000000..05a6106e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/calendar-layout.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~basalt.png new file mode 100644 index 00000000..20341b34 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~chalk.png new file mode 100644 index 00000000..1e11a86e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/caltrain-stops~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/card.gif b/devsite/source/assets/images/guides/design-and-interaction/card.gif new file mode 100644 index 00000000..0236675e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/card.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/center-layout~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/center-layout~aplite.png new file mode 100644 index 00000000..df925b37 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/center-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/center-layout~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/center-layout~basalt.png new file mode 100644 index 00000000..8c7ee913 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/center-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/center-layout~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/center-layout~chalk.png new file mode 100644 index 00000000..563053fa Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/center-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/checkbox-list.png b/devsite/source/assets/images/guides/design-and-interaction/checkbox-list.png new file mode 100644 index 00000000..b916b04e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/checkbox-list.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/content-indicator.png b/devsite/source/assets/images/guides/design-and-interaction/content-indicator.png new file mode 100644 index 00000000..e0d8d011 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/content-indicator.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-patterns.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-patterns.png new file mode 100644 index 00000000..4aafba2c Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-patterns.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~aplite.png new file mode 100644 index 00000000..ee5ec2b3 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~basalt.png new file mode 100644 index 00000000..4aafba2c Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~chalk.png new file mode 100644 index 00000000..43bd7de1 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice-window~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~basalt.png new file mode 100644 index 00000000..187f168f Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~chalk.png new file mode 100644 index 00000000..15059e02 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-choice~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-message.gif b/devsite/source/assets/images/guides/design-and-interaction/dialog-message.gif new file mode 100644 index 00000000..6019336e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-message.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/dialog-message.png b/devsite/source/assets/images/guides/design-and-interaction/dialog-message.png new file mode 100644 index 00000000..5338555a Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/dialog-message.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/filtermenu.png b/devsite/source/assets/images/guides/design-and-interaction/filtermenu.png new file mode 100644 index 00000000..75bb6587 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/filtermenu.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/filtermenulist.gif b/devsite/source/assets/images/guides/design-and-interaction/filtermenulist.gif new file mode 100644 index 00000000..9c20e415 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/filtermenulist.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/golf~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/golf~aplite.png new file mode 100644 index 00000000..d5a58fda Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/golf~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/golf~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/golf~basalt.png new file mode 100644 index 00000000..e4277157 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/golf~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/golf~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/golf~chalk.png new file mode 100644 index 00000000..7c97466d Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/golf~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_check.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_check.png new file mode 100644 index 00000000..c2a3e2f3 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_check.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_delete.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_delete.png new file mode 100644 index 00000000..a6bf6728 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_delete.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_dismiss.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_dismiss.png new file mode 100644 index 00000000..e0b4216e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_dismiss.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_down.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_down.png new file mode 100644 index 00000000..e78edaa5 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_down.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_edit.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_edit.png new file mode 100644 index 00000000..2c1aab8b Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_edit.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_snooze.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_snooze.png new file mode 100644 index 00000000..b58b3358 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_snooze.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_up.png b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_up.png new file mode 100644 index 00000000..f4bd07c2 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/action_bar_icon_up.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_ellipsis.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_ellipsis.png new file mode 100644 index 00000000..7349dbd1 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_ellipsis.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_pause.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_pause.png new file mode 100644 index 00000000..1f2c34b1 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_pause.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_play.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_play.png new file mode 100644 index 00000000..b83ef16c Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_play.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_backward.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_backward.png new file mode 100644 index 00000000..c991c22a Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_backward.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_forward.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_forward.png new file mode 100644 index 00000000..be6e5394 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_skip_forward.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_down.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_down.png new file mode 100644 index 00000000..abbf7860 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_down.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_up.png b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_up.png new file mode 100644 index 00000000..17cd9090 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/icons/music_icon_volume_up.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/list-message.png b/devsite/source/assets/images/guides/design-and-interaction/list-message.png new file mode 100644 index 00000000..577bf51b Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/list-message.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/list~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/list~aplite.png new file mode 100644 index 00000000..f37a0c17 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/list~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/list~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/list~basalt.png new file mode 100644 index 00000000..81e36f37 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/list~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/list~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/list~chalk.png new file mode 100644 index 00000000..f2e76ef6 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/list~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/lockitron.png b/devsite/source/assets/images/guides/design-and-interaction/lockitron.png new file mode 100644 index 00000000..0abc6623 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/lockitron.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/menulayer.png b/devsite/source/assets/images/guides/design-and-interaction/menulayer.png new file mode 100644 index 00000000..5bc241c2 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/menulayer.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/music-actions.gif b/devsite/source/assets/images/guides/design-and-interaction/music-actions.gif new file mode 100644 index 00000000..816f27d0 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/music-actions.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/music.png b/devsite/source/assets/images/guides/design-and-interaction/music.png new file mode 100644 index 00000000..8d4e770b Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/music.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~aplite.png new file mode 100644 index 00000000..4a1bb437 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~basalt.png new file mode 100644 index 00000000..9458771b Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~chalk.png new file mode 100644 index 00000000..667eaa7b Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-bt-connection~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~aplite.png new file mode 100644 index 00000000..9de143f7 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~basalt.png new file mode 100644 index 00000000..a73ec52f Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~chalk.png new file mode 100644 index 00000000..15538d50 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/no-inet-connection~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/notifications.png b/devsite/source/assets/images/guides/design-and-interaction/notifications.png new file mode 100644 index 00000000..7f6cce17 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/notifications.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/pin.png b/devsite/source/assets/images/guides/design-and-interaction/pin.png new file mode 100644 index 00000000..5a768f8c Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/pin.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/progress-bar.gif b/devsite/source/assets/images/guides/design-and-interaction/progress-bar.gif new file mode 100644 index 00000000..5f4f63ca Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/progress-bar.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/progresslayer.gif b/devsite/source/assets/images/guides/design-and-interaction/progresslayer.gif new file mode 100644 index 00000000..c35b7238 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/progresslayer.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/radio-button.png b/devsite/source/assets/images/guides/design-and-interaction/radio-button.png new file mode 100644 index 00000000..dd54e3f4 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/radio-button.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/scrolling-with-text-flow.gif b/devsite/source/assets/images/guides/design-and-interaction/scrolling-with-text-flow.gif new file mode 100644 index 00000000..1440ca00 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/scrolling-with-text-flow.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/settings.png b/devsite/source/assets/images/guides/design-and-interaction/settings.png new file mode 100644 index 00000000..1de705ab Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/settings.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/slate-config-page.png b/devsite/source/assets/images/guides/design-and-interaction/slate-config-page.png new file mode 100644 index 00000000..31eb84d9 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/slate-config-page.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/slate-watch.png b/devsite/source/assets/images/guides/design-and-interaction/slate-watch.png new file mode 100644 index 00000000..9bc6fbfd Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/slate-watch.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/sports~aplite.png b/devsite/source/assets/images/guides/design-and-interaction/sports~aplite.png new file mode 100644 index 00000000..9ba8fa74 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/sports~aplite.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/sports~basalt.png b/devsite/source/assets/images/guides/design-and-interaction/sports~basalt.png new file mode 100644 index 00000000..c7a8fc18 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/sports~basalt.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/sports~chalk.png b/devsite/source/assets/images/guides/design-and-interaction/sports~chalk.png new file mode 100644 index 00000000..6e939333 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/sports~chalk.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/system-navigation.png b/devsite/source/assets/images/guides/design-and-interaction/system-navigation.png new file mode 100644 index 00000000..c6a337f3 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/system-navigation.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/text-change-anim.gif b/devsite/source/assets/images/guides/design-and-interaction/text-change-anim.gif new file mode 100644 index 00000000..1a41ee51 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/text-change-anim.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/timeline-future.png b/devsite/source/assets/images/guides/design-and-interaction/timeline-future.png new file mode 100644 index 00000000..c861b08d Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/timeline-future.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/timeline.png b/devsite/source/assets/images/guides/design-and-interaction/timeline.png new file mode 100644 index 00000000..d612809a Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/timeline.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/watchfaces.png b/devsite/source/assets/images/guides/design-and-interaction/watchfaces.png new file mode 100644 index 00000000..1e3b3d99 Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/watchfaces.png differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/weather.gif b/devsite/source/assets/images/guides/design-and-interaction/weather.gif new file mode 100644 index 00000000..0236675e Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/weather.gif differ diff --git a/devsite/source/assets/images/guides/design-and-interaction/weather.png b/devsite/source/assets/images/guides/design-and-interaction/weather.png new file mode 100644 index 00000000..32fca9bb Binary files /dev/null and b/devsite/source/assets/images/guides/design-and-interaction/weather.png differ diff --git a/devsite/source/assets/images/guides/distribute/app-screenshot.png b/devsite/source/assets/images/guides/distribute/app-screenshot.png new file mode 100644 index 00000000..7656e4b7 Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/app-screenshot.png differ diff --git a/devsite/source/assets/images/guides/distribute/appstore-screenshot.png b/devsite/source/assets/images/guides/distribute/appstore-screenshot.png new file mode 100644 index 00000000..b840f2ca Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/appstore-screenshot.png differ diff --git a/devsite/source/assets/images/guides/distribute/featured.png b/devsite/source/assets/images/guides/distribute/featured.png new file mode 100644 index 00000000..14ae8cc8 Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/featured.png differ diff --git a/devsite/source/assets/images/guides/distribute/header.png b/devsite/source/assets/images/guides/distribute/header.png new file mode 100644 index 00000000..14ae8cc8 Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/header.png differ diff --git a/devsite/source/assets/images/guides/distribute/icon-144x144.png b/devsite/source/assets/images/guides/distribute/icon-144x144.png new file mode 100644 index 00000000..ce431f65 Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/icon-144x144.png differ diff --git a/devsite/source/assets/images/guides/distribute/icon-48x48.png b/devsite/source/assets/images/guides/distribute/icon-48x48.png new file mode 100644 index 00000000..0df505aa Binary files /dev/null and b/devsite/source/assets/images/guides/distribute/icon-48x48.png differ diff --git a/devsite/source/assets/images/guides/hardware/buffer.png b/devsite/source/assets/images/guides/hardware/buffer.png new file mode 100644 index 00000000..1c6e18b0 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/buffer.png differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step1.jpg b/devsite/source/assets/images/guides/hardware/cable-step1.jpg new file mode 100644 index 00000000..0d8e46c0 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step1.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step2.jpg b/devsite/source/assets/images/guides/hardware/cable-step2.jpg new file mode 100644 index 00000000..cfaed188 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step2.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step3.jpg b/devsite/source/assets/images/guides/hardware/cable-step3.jpg new file mode 100644 index 00000000..dc0e536c Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step3.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step4.jpg b/devsite/source/assets/images/guides/hardware/cable-step4.jpg new file mode 100644 index 00000000..19536026 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step4.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step5.jpg b/devsite/source/assets/images/guides/hardware/cable-step5.jpg new file mode 100644 index 00000000..5c3fecdc Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step5.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/cable-step6.jpg b/devsite/source/assets/images/guides/hardware/cable-step6.jpg new file mode 100644 index 00000000..1d76d894 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/cable-step6.jpg differ diff --git a/devsite/source/assets/images/guides/hardware/example-frames.png b/devsite/source/assets/images/guides/hardware/example-frames.png new file mode 100644 index 00000000..6c2aac31 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/example-frames.png differ diff --git a/devsite/source/assets/images/guides/hardware/raw-read.png b/devsite/source/assets/images/guides/hardware/raw-read.png new file mode 100644 index 00000000..0d1977f5 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/raw-read.png differ diff --git a/devsite/source/assets/images/guides/hardware/raw-response.png b/devsite/source/assets/images/guides/hardware/raw-response.png new file mode 100644 index 00000000..f9a474d4 Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/raw-response.png differ diff --git a/devsite/source/assets/images/guides/hardware/software-serial.png b/devsite/source/assets/images/guides/hardware/software-serial.png new file mode 100644 index 00000000..4d988fca Binary files /dev/null and b/devsite/source/assets/images/guides/hardware/software-serial.png differ diff --git a/devsite/source/assets/images/guides/migration/3x-aplite-system.png b/devsite/source/assets/images/guides/migration/3x-aplite-system.png new file mode 100644 index 00000000..ea6901b8 Binary files /dev/null and b/devsite/source/assets/images/guides/migration/3x-aplite-system.png differ diff --git a/devsite/source/assets/images/guides/migration/companion-checkbox.png b/devsite/source/assets/images/guides/migration/companion-checkbox.png new file mode 100644 index 00000000..ec0988fa Binary files /dev/null and b/devsite/source/assets/images/guides/migration/companion-checkbox.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/activity.png b/devsite/source/assets/images/guides/pebble-apps/activity.png new file mode 100644 index 00000000..8fa586b6 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/activity.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/app-sync.png b/devsite/source/assets/images/guides/pebble-apps/communications/app-sync.png new file mode 100644 index 00000000..b70b73c9 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/app-sync.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/app_message.png b/devsite/source/assets/images/guides/pebble-apps/communications/app_message.png new file mode 100644 index 00000000..81e1f244 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/app_message.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/appmessage-example.png b/devsite/source/assets/images/guides/pebble-apps/communications/appmessage-example.png new file mode 100644 index 00000000..6bfc82c3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/appmessage-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/sports-apps.png b/devsite/source/assets/images/guides/pebble-apps/communications/sports-apps.png new file mode 100644 index 00000000..b822cd7e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/sports-apps.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/sync-example.png b/devsite/source/assets/images/guides/pebble-apps/communications/sync-example.png new file mode 100644 index 00000000..a7614f82 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/sync-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/communications/tuplet.png b/devsite/source/assets/images/guides/pebble-apps/communications/tuplet.png new file mode 100644 index 00000000..661b5b08 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/communications/tuplet.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/aa-comparison.gif b/devsite/source/assets/images/guides/pebble-apps/display-animations/aa-comparison.gif new file mode 100644 index 00000000..5af1155a Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/aa-comparison.gif differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/aa.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/aa.png new file mode 100644 index 00000000..6a796bab Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/aa.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~aplite.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~aplite.png new file mode 100644 index 00000000..a80d1fb3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~aplite.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~basalt.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~basalt.png new file mode 100644 index 00000000..595ad4e3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~basalt.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~chalk.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~chalk.png new file mode 100644 index 00000000..0acf3c1d Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/actions~chalk.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/animations.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/animations.png new file mode 100644 index 00000000..9f40f2b6 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/animations.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/bg-blue.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/bg-blue.png new file mode 100644 index 00000000..7c7be10f Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/bg-blue.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/centered.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/centered.png new file mode 100644 index 00000000..63f70a9b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/centered.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/color-picker.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/color-picker.png new file mode 100644 index 00000000..cac5802f Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/color-picker.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/complex-anims.gif b/devsite/source/assets/images/guides/pebble-apps/display-animations/complex-anims.gif new file mode 100644 index 00000000..30e7f508 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/complex-anims.gif differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/cut-corners.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/cut-corners.png new file mode 100644 index 00000000..f1668af9 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/cut-corners.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/drawing-example.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/drawing-example.png new file mode 100644 index 00000000..7d8e62d4 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/drawing-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_abc.png new file mode 100644 index 00000000..d3be5951 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_digits.png new file mode 100644 index 00000000..5eb993a7 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_30_black_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_34_medium_numbers_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_34_medium_numbers_digits.png new file mode 100644 index 00000000..7e5a0d0e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_34_medium_numbers_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_abc.png new file mode 100644 index 00000000..523ed815 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_digits.png new file mode 100644 index 00000000..fc78ea92 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_abc.png new file mode 100644 index 00000000..2251b0e3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_digits.png new file mode 100644 index 00000000..09f60ceb Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_light_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_medium_numbers_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_medium_numbers_digits.png new file mode 100644 index 00000000..26bd838c Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/bitham_42_medium_numbers_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_abc.png new file mode 100644 index 00000000..75b681b6 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_digits.png new file mode 100644 index 00000000..a26053e1 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/droid_28_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_abc.png new file mode 100644 index 00000000..253fd3bb Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_abc.png new file mode 100644 index 00000000..83ea8a85 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_digits.png new file mode 100644 index 00000000..232ae995 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_digits.png new file mode 100644 index 00000000..7cbe54e7 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_14_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_abc.png new file mode 100644 index 00000000..b8aa959f Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_abc.png new file mode 100644 index 00000000..bb303eb9 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_digits.png new file mode 100644 index 00000000..7480342e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_digits.png new file mode 100644 index 00000000..914bdab2 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_18_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_abc.png new file mode 100644 index 00000000..b0380996 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_abc.png new file mode 100644 index 00000000..4ec29ac7 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_digits.png new file mode 100644 index 00000000..d9663081 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_digits.png new file mode 100644 index 00000000..19f6f27b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_24_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_abc.png new file mode 100644 index 00000000..5eba0022 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_abc.png new file mode 100644 index 00000000..64d3cce1 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_digits.png new file mode 100644 index 00000000..5121eb93 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_bold_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_digits.png new file mode 100644 index 00000000..8d19375b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/gothic_28_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-20-bold-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-20-bold-numbers.png new file mode 100644 index 00000000..c1b08e99 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-20-bold-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-26-bold-numbers-am-pm.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-26-bold-numbers-am-pm.png new file mode 100644 index 00000000..26e5cfa8 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-26-bold-numbers-am-pm.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-28-light-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-28-light-numbers.png new file mode 100644 index 00000000..6b092678 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-28-light-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-32-bold-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-32-bold-numbers.png new file mode 100644 index 00000000..fbee18a2 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-32-bold-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-36-bold-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-36-bold-numbers.png new file mode 100644 index 00000000..94082248 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-36-bold-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-38-bold-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-38-bold-numbers.png new file mode 100644 index 00000000..1c5cc8e3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-38-bold-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-42-bold-numbers.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-42-bold-numbers.png new file mode 100644 index 00000000..6efab061 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/leco-42-bold-numbers.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_abc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_abc.png new file mode 100644 index 00000000..e21f4a8b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_abc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_digits.png new file mode 100644 index 00000000..18cfc5e4 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_21_condensed_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_49_bold_subset_digits.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_49_bold_subset_digits.png new file mode 100644 index 00000000..9ba6616e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/fonts/roboto_49_bold_subset_digits.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/frame_bounds.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/frame_bounds.png new file mode 100644 index 00000000..7a907b42 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/frame_bounds.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/layers-example.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/layers-example.png new file mode 100644 index 00000000..563a5aa8 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/layers-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/letter-c.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/letter-c.png new file mode 100644 index 00000000..844bc2d0 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/letter-c.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/mask.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/mask.png new file mode 100644 index 00000000..409122c9 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/mask.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd b/devsite/source/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd new file mode 100644 index 00000000..cfede2e3 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/stroke-width.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/stroke-width.png new file mode 100644 index 00000000..94d3fa7d Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/stroke-width.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~aplite.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~aplite.png new file mode 100644 index 00000000..03e22df1 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~aplite.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~basalt.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~basalt.png new file mode 100644 index 00000000..efd09a89 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~basalt.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~chalk.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~chalk.png new file mode 100644 index 00000000..8d73627e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/submenu~chalk.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/text-flow.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/text-flow.png new file mode 100644 index 00000000..f5881b3e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/text-flow.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/time-dots.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/time-dots.png new file mode 100644 index 00000000..8fa680e9 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/time-dots.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/display-animations/update_proc.png b/devsite/source/assets/images/guides/pebble-apps/display-animations/update_proc.png new file mode 100644 index 00000000..37e91483 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/display-animations/update_proc.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/alpha.png b/devsite/source/assets/images/guides/pebble-apps/resources/alpha.png new file mode 100644 index 00000000..14df529e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/alpha.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/emoji-screenshot.png b/devsite/source/assets/images/guides/pebble-apps/resources/emoji-screenshot.png new file mode 100644 index 00000000..7f98cddc Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/emoji-screenshot.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/emoji-unsupported.png b/devsite/source/assets/images/guides/pebble-apps/resources/emoji-unsupported.png new file mode 100644 index 00000000..dc298322 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/emoji-unsupported.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/emoji1.png b/devsite/source/assets/images/guides/pebble-apps/resources/emoji1.png new file mode 100644 index 00000000..9058cbd8 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/emoji1.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/fonts.png b/devsite/source/assets/images/guides/pebble-apps/resources/fonts.png new file mode 100644 index 00000000..02cb43c1 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/fonts.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/globe-aplite.png b/devsite/source/assets/images/guides/pebble-apps/resources/globe-aplite.png new file mode 100644 index 00000000..e278e81b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/globe-aplite.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/globe-basalt.png b/devsite/source/assets/images/guides/pebble-apps/resources/globe-basalt.png new file mode 100644 index 00000000..c0483000 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/globe-basalt.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-fit.png b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-fit.png new file mode 100644 index 00000000..90fc8b7a Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-fit.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-open.png b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-open.png new file mode 100644 index 00000000..d33662cf Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-open.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-resize.png b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-resize.png new file mode 100644 index 00000000..1306613b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-resize.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-settings.png b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-settings.png new file mode 100644 index 00000000..6ed82acd Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-settings.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-ungroup.png b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-ungroup.png new file mode 100644 index 00000000..673c0bd5 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/illustrator-ungroup.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-open.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-open.png new file mode 100644 index 00000000..c5c75f34 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-open.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-plain.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-plain.png new file mode 100644 index 00000000..f135be6b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-plain.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-relative.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-relative.png new file mode 100644 index 00000000..0357cf47 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-relative.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-page.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-page.png new file mode 100644 index 00000000..b50b2bd8 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-page.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-pebble.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-pebble.png new file mode 100644 index 00000000..67892037 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-resize-pebble.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-ungroup.png b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-ungroup.png new file mode 100644 index 00000000..01dbee71 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/inkscape-ungroup.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/pencil.svg b/devsite/source/assets/images/guides/pebble-apps/resources/pencil.svg new file mode 100644 index 00000000..5b03554c --- /dev/null +++ b/devsite/source/assets/images/guides/pebble-apps/resources/pencil.svg @@ -0,0 +1,20 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + \ No newline at end of file diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/raw-example.png b/devsite/source/assets/images/guides/pebble-apps/resources/raw-example.png new file mode 100644 index 00000000..7fdabe3b Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/raw-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/resources-example.png b/devsite/source/assets/images/guides/pebble-apps/resources/resources-example.png new file mode 100644 index 00000000..9cf0614e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/resources-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/resources/svg-output.png b/devsite/source/assets/images/guides/pebble-apps/resources/svg-output.png new file mode 100644 index 00000000..3329fd3c Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/resources/svg-output.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/accel-data-example.png b/devsite/source/assets/images/guides/pebble-apps/sensors/accel-data-example.png new file mode 100644 index 00000000..b6e411ef Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/accel-data-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/accel-tap-example.png b/devsite/source/assets/images/guides/pebble-apps/sensors/accel-tap-example.png new file mode 100644 index 00000000..595ebbcd Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/accel-tap-example.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/accel.png b/devsite/source/assets/images/guides/pebble-apps/sensors/accel.png new file mode 100644 index 00000000..08475d31 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/accel.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-1.png b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-1.png new file mode 100644 index 00000000..0446ed6f Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-1.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-2.png b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-2.png new file mode 100644 index 00000000..a8346523 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-example-2.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/compass-orientation.png b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-orientation.png new file mode 100644 index 00000000..5edf3f89 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/compass-orientation.png differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/declination.gif b/devsite/source/assets/images/guides/pebble-apps/sensors/declination.gif new file mode 100644 index 00000000..7d785727 Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/declination.gif differ diff --git a/devsite/source/assets/images/guides/pebble-apps/sensors/listening.png b/devsite/source/assets/images/guides/pebble-apps/sensors/listening.png new file mode 100644 index 00000000..4fa6922e Binary files /dev/null and b/devsite/source/assets/images/guides/pebble-apps/sensors/listening.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/accel-ui.png b/devsite/source/assets/images/guides/publishing-tools/accel-ui.png new file mode 100644 index 00000000..ff076c0e Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/accel-ui.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/appstore-create.png b/devsite/source/assets/images/guides/publishing-tools/appstore-create.png new file mode 100644 index 00000000..711ec430 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/appstore-create.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/appstore-feedback.png b/devsite/source/assets/images/guides/publishing-tools/appstore-feedback.png new file mode 100644 index 00000000..3a75a4e3 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/appstore-feedback.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/asset-button.png b/devsite/source/assets/images/guides/publishing-tools/asset-button.png new file mode 100644 index 00000000..57d2cc5d Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/asset-button.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/asset-collection.png b/devsite/source/assets/images/guides/publishing-tools/asset-collection.png new file mode 100644 index 00000000..93aee481 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/asset-collection.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/asset-items.png b/devsite/source/assets/images/guides/publishing-tools/asset-items.png new file mode 100644 index 00000000..20eecd5e Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/asset-items.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/emulator.png b/devsite/source/assets/images/guides/publishing-tools/emulator.png new file mode 100644 index 00000000..94fc725d Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/emulator.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-1.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-1.png new file mode 100644 index 00000000..251de942 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-1.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-2.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-2.png new file mode 100644 index 00000000..941a88e8 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-2.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-4.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-4.png new file mode 100644 index 00000000..fb5cba6c Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-android-4.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-1.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-1.png new file mode 100644 index 00000000..6833532b Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-1.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-2.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-2.png new file mode 100644 index 00000000..202fb6d1 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-2.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-3.png b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-3.png new file mode 100644 index 00000000..0097c4a9 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/enable-dev-ios-3.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/qr-code.png b/devsite/source/assets/images/guides/publishing-tools/qr-code.png new file mode 100644 index 00000000..249cb0fc Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/qr-code.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/timeline-future.png b/devsite/source/assets/images/guides/publishing-tools/timeline-future.png new file mode 100644 index 00000000..b422207b Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/timeline-future.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/timeline-past.png b/devsite/source/assets/images/guides/publishing-tools/timeline-past.png new file mode 100644 index 00000000..67c9acdd Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/timeline-past.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/ui-editor-drag.gif b/devsite/source/assets/images/guides/publishing-tools/ui-editor-drag.gif new file mode 100644 index 00000000..d565a2f9 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/ui-editor-drag.gif differ diff --git a/devsite/source/assets/images/guides/publishing-tools/ui-editor-preview.png b/devsite/source/assets/images/guides/publishing-tools/ui-editor-preview.png new file mode 100644 index 00000000..9020d753 Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/ui-editor-preview.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/ui-editor-textlayer.png b/devsite/source/assets/images/guides/publishing-tools/ui-editor-textlayer.png new file mode 100644 index 00000000..6d9b3bbb Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/ui-editor-textlayer.png differ diff --git a/devsite/source/assets/images/guides/publishing-tools/ui-editor.png b/devsite/source/assets/images/guides/publishing-tools/ui-editor.png new file mode 100644 index 00000000..66929d7f Binary files /dev/null and b/devsite/source/assets/images/guides/publishing-tools/ui-editor.png differ diff --git a/devsite/source/assets/images/guides/sensors-and-input/button-layout.png b/devsite/source/assets/images/guides/sensors-and-input/button-layout.png new file mode 100644 index 00000000..1fffa7c7 Binary files /dev/null and b/devsite/source/assets/images/guides/sensors-and-input/button-layout.png differ diff --git a/devsite/source/assets/images/guides/timeline/ALARM_CLOCK.svg b/devsite/source/assets/images/guides/timeline/ALARM_CLOCK.svg new file mode 100644 index 00000000..dc49c32a --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/ALARM_CLOCK.svg @@ -0,0 +1,3359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/AMERICAN_FOOTBALL.svg b/devsite/source/assets/images/guides/timeline/AMERICAN_FOOTBALL.svg new file mode 100644 index 00000000..06589df2 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/AMERICAN_FOOTBALL.svg @@ -0,0 +1,3358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/AUDIO_CASSETTE.svg b/devsite/source/assets/images/guides/timeline/AUDIO_CASSETTE.svg new file mode 100644 index 00000000..c73e8bd1 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/AUDIO_CASSETTE.svg @@ -0,0 +1,3361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/BASKETBALL.svg b/devsite/source/assets/images/guides/timeline/BASKETBALL.svg new file mode 100644 index 00000000..04b88847 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/BASKETBALL.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/BIRTHDAY_EVENT.svg b/devsite/source/assets/images/guides/timeline/BIRTHDAY_EVENT.svg new file mode 100644 index 00000000..56c8c937 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/BIRTHDAY_EVENT.svg @@ -0,0 +1,3355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/CAR_RENTAL.svg b/devsite/source/assets/images/guides/timeline/CAR_RENTAL.svg new file mode 100644 index 00000000..56e5b96d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/CAR_RENTAL.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/CHECK_INTERNET_CONNECTION.svg b/devsite/source/assets/images/guides/timeline/CHECK_INTERNET_CONNECTION.svg new file mode 100644 index 00000000..683a2ecb --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/CHECK_INTERNET_CONNECTION.svg @@ -0,0 +1,3370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/CLOUDY_DAY.svg b/devsite/source/assets/images/guides/timeline/CLOUDY_DAY.svg new file mode 100644 index 00000000..a970faaf --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/CLOUDY_DAY.svg @@ -0,0 +1,3355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/CRICKET_GAME.svg b/devsite/source/assets/images/guides/timeline/CRICKET_GAME.svg new file mode 100644 index 00000000..07e7285c --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/CRICKET_GAME.svg @@ -0,0 +1,3353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/DAY_SEPARATOR.svg b/devsite/source/assets/images/guides/timeline/DAY_SEPARATOR.svg new file mode 100644 index 00000000..8ff9839e --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/DAY_SEPARATOR.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/DINNER_RESERVATION.svg b/devsite/source/assets/images/guides/timeline/DINNER_RESERVATION.svg new file mode 100644 index 00000000..b14223f4 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/DINNER_RESERVATION.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/DISMISSED_PHONE_CALL.svg b/devsite/source/assets/images/guides/timeline/DISMISSED_PHONE_CALL.svg new file mode 100644 index 00000000..4c6e0fb2 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/DISMISSED_PHONE_CALL.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL.svg b/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL.svg new file mode 100644 index 00000000..f11e91c4 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL.svg @@ -0,0 +1,3380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL_CENTERED.svg b/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL_CENTERED.svg new file mode 100644 index 00000000..70542103 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/DURING_PHONE_CALL_CENTERED.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GENERIC_CONFIRMATION.svg b/devsite/source/assets/images/guides/timeline/GENERIC_CONFIRMATION.svg new file mode 100644 index 00000000..113e2e83 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GENERIC_CONFIRMATION.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GENERIC_EMAIL.svg b/devsite/source/assets/images/guides/timeline/GENERIC_EMAIL.svg new file mode 100644 index 00000000..b36abd21 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GENERIC_EMAIL.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GENERIC_QUESTION.svg b/devsite/source/assets/images/guides/timeline/GENERIC_QUESTION.svg new file mode 100644 index 00000000..cc1cb07a --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GENERIC_QUESTION.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GENERIC_SMS.svg b/devsite/source/assets/images/guides/timeline/GENERIC_SMS.svg new file mode 100644 index 00000000..53cd5c14 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GENERIC_SMS.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GENERIC_WARNING.svg b/devsite/source/assets/images/guides/timeline/GENERIC_WARNING.svg new file mode 100644 index 00000000..55b23b88 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GENERIC_WARNING.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/GLUCOSE_MONITOR.svg b/devsite/source/assets/images/guides/timeline/GLUCOSE_MONITOR.svg new file mode 100644 index 00000000..a8ec2360 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/GLUCOSE_MONITOR.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/HEAVY_RAIN.svg b/devsite/source/assets/images/guides/timeline/HEAVY_RAIN.svg new file mode 100644 index 00000000..c6dc22a3 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/HEAVY_RAIN.svg @@ -0,0 +1,3359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/HEAVY_SNOW.svg b/devsite/source/assets/images/guides/timeline/HEAVY_SNOW.svg new file mode 100644 index 00000000..9924603a --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/HEAVY_SNOW.svg @@ -0,0 +1,3362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/HOCKEY_GAME.svg b/devsite/source/assets/images/guides/timeline/HOCKEY_GAME.svg new file mode 100644 index 00000000..adaf92d3 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/HOCKEY_GAME.svg @@ -0,0 +1,3359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/HOTEL_RESERVATION.svg b/devsite/source/assets/images/guides/timeline/HOTEL_RESERVATION.svg new file mode 100644 index 00000000..e927db22 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/HOTEL_RESERVATION.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/INCOMING_PHONE_CALL.svg b/devsite/source/assets/images/guides/timeline/INCOMING_PHONE_CALL.svg new file mode 100644 index 00000000..97b84d48 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/INCOMING_PHONE_CALL.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/LIGHT_RAIN.svg b/devsite/source/assets/images/guides/timeline/LIGHT_RAIN.svg new file mode 100644 index 00000000..abd33f36 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/LIGHT_RAIN.svg @@ -0,0 +1,3358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/LIGHT_SNOW.svg b/devsite/source/assets/images/guides/timeline/LIGHT_SNOW.svg new file mode 100644 index 00000000..b1e49bd3 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/LIGHT_SNOW.svg @@ -0,0 +1,3358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/LOCATION.svg b/devsite/source/assets/images/guides/timeline/LOCATION.svg new file mode 100644 index 00000000..797926e0 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/LOCATION.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/MOVIE_EVENT.svg b/devsite/source/assets/images/guides/timeline/MOVIE_EVENT.svg new file mode 100644 index 00000000..c428fd26 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/MOVIE_EVENT.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/MUSIC_EVENT.svg b/devsite/source/assets/images/guides/timeline/MUSIC_EVENT.svg new file mode 100644 index 00000000..226298df --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/MUSIC_EVENT.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NEWS_EVENT.svg b/devsite/source/assets/images/guides/timeline/NEWS_EVENT.svg new file mode 100644 index 00000000..39eef8bc --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NEWS_EVENT.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_BLACKBERRY_MESSENGER.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_BLACKBERRY_MESSENGER.svg new file mode 100644 index 00000000..9caa489a --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_BLACKBERRY_MESSENGER.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK.svg new file mode 100644 index 00000000..34f839dc --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK.svg @@ -0,0 +1,3353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK_MESSENGER.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK_MESSENGER.svg new file mode 100644 index 00000000..611e86b1 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FACEBOOK_MESSENGER.svg @@ -0,0 +1,3348 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_FLAG.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FLAG.svg new file mode 100644 index 00000000..ed34e95e --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_FLAG.svg @@ -0,0 +1,3355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_GENERIC.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GENERIC.svg new file mode 100644 index 00000000..9100b8fd --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GENERIC.svg @@ -0,0 +1,3354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_GMAIL.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GMAIL.svg new file mode 100644 index 00000000..41e76f0d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GMAIL.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_HANGOUTS.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_HANGOUTS.svg new file mode 100644 index 00000000..d14e62f0 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_HANGOUTS.svg @@ -0,0 +1,3351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_INBOX.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_INBOX.svg new file mode 100644 index 00000000..3b7aadd7 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_GOOGLE_INBOX.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_INSTAGRAM.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_INSTAGRAM.svg new file mode 100644 index 00000000..956273b9 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_INSTAGRAM.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_LIGHTHOUSE.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_LIGHTHOUSE.svg new file mode 100644 index 00000000..59850e6b --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_LIGHTHOUSE.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_LINE.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_LINE.svg new file mode 100644 index 00000000..e87bf3d6 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_LINE.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_MAILBOX.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_MAILBOX.svg new file mode 100644 index 00000000..e35a4426 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_MAILBOX.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_OUTLOOK.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_OUTLOOK.svg new file mode 100644 index 00000000..b40e3164 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_OUTLOOK.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_REMINDER.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_REMINDER.svg new file mode 100644 index 00000000..47cfd8af --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_REMINDER.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_SKYPE.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_SKYPE.svg new file mode 100644 index 00000000..029331cb --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_SKYPE.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_SNAPCHAT.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_SNAPCHAT.svg new file mode 100644 index 00000000..b4a7a9a1 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_SNAPCHAT.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_TELEGRAM.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_TELEGRAM.svg new file mode 100644 index 00000000..78d1595f --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_TELEGRAM.svg @@ -0,0 +1,3351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_TWITTER.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_TWITTER.svg new file mode 100644 index 00000000..9b6e4426 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_TWITTER.svg @@ -0,0 +1,3348 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_VIBER.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_VIBER.svg new file mode 100644 index 00000000..f6683d36 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_VIBER.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_WECHAT.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_WECHAT.svg new file mode 100644 index 00000000..64d4ca2d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_WECHAT.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_WHATSAPP.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_WHATSAPP.svg new file mode 100644 index 00000000..c86766dd --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_WHATSAPP.svg @@ -0,0 +1,3351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NOTIFICATION_YAHOO_MAIL.svg b/devsite/source/assets/images/guides/timeline/NOTIFICATION_YAHOO_MAIL.svg new file mode 100644 index 00000000..a2a44579 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NOTIFICATION_YAHOO_MAIL.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/NO_EVENTS.svg b/devsite/source/assets/images/guides/timeline/NO_EVENTS.svg new file mode 100644 index 00000000..1fdfe970 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/NO_EVENTS.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/PARTLY_CLOUDY.svg b/devsite/source/assets/images/guides/timeline/PARTLY_CLOUDY.svg new file mode 100644 index 00000000..0232dd08 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/PARTLY_CLOUDY.svg @@ -0,0 +1,3361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/PAY_BILL.svg b/devsite/source/assets/images/guides/timeline/PAY_BILL.svg new file mode 100644 index 00000000..f160168e --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/PAY_BILL.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RADIO_SHOW.svg b/devsite/source/assets/images/guides/timeline/RADIO_SHOW.svg new file mode 100644 index 00000000..cd4d082b --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RADIO_SHOW.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RAINING_AND_SNOWING.svg b/devsite/source/assets/images/guides/timeline/RAINING_AND_SNOWING.svg new file mode 100644 index 00000000..09198fd1 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RAINING_AND_SNOWING.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/REACHED_FITNESS_GOAL.svg b/devsite/source/assets/images/guides/timeline/REACHED_FITNESS_GOAL.svg new file mode 100644 index 00000000..87f8e003 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/REACHED_FITNESS_GOAL.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RESULT_DELETED.svg b/devsite/source/assets/images/guides/timeline/RESULT_DELETED.svg new file mode 100644 index 00000000..c500250d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RESULT_DELETED.svg @@ -0,0 +1,3359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RESULT_DISMISSED.svg b/devsite/source/assets/images/guides/timeline/RESULT_DISMISSED.svg new file mode 100644 index 00000000..e474ad2f --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RESULT_DISMISSED.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RESULT_FAILED.svg b/devsite/source/assets/images/guides/timeline/RESULT_FAILED.svg new file mode 100644 index 00000000..5b4b63c1 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RESULT_FAILED.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RESULT_MUTE.svg b/devsite/source/assets/images/guides/timeline/RESULT_MUTE.svg new file mode 100644 index 00000000..b0a31649 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RESULT_MUTE.svg @@ -0,0 +1,3377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/RESULT_SENT.svg b/devsite/source/assets/images/guides/timeline/RESULT_SENT.svg new file mode 100644 index 00000000..6cb2dd00 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/RESULT_SENT.svg @@ -0,0 +1,3365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SCHEDULED_EVENT.svg b/devsite/source/assets/images/guides/timeline/SCHEDULED_EVENT.svg new file mode 100644 index 00000000..3d7a76d5 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SCHEDULED_EVENT.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SCHEDULED_FLIGHT.svg b/devsite/source/assets/images/guides/timeline/SCHEDULED_FLIGHT.svg new file mode 100644 index 00000000..67731605 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SCHEDULED_FLIGHT.svg @@ -0,0 +1,3353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SETTINGS.svg b/devsite/source/assets/images/guides/timeline/SETTINGS.svg new file mode 100644 index 00000000..4e10bffd --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SETTINGS.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SOCCER_GAME.svg b/devsite/source/assets/images/guides/timeline/SOCCER_GAME.svg new file mode 100644 index 00000000..7f52fe0f --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SOCCER_GAME.svg @@ -0,0 +1,3362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/STOCKS_EVENT.svg b/devsite/source/assets/images/guides/timeline/STOCKS_EVENT.svg new file mode 100644 index 00000000..100d83ab --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/STOCKS_EVENT.svg @@ -0,0 +1,3353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SUNRISE.svg b/devsite/source/assets/images/guides/timeline/SUNRISE.svg new file mode 100644 index 00000000..086eb5b0 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SUNRISE.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/SUNSET.svg b/devsite/source/assets/images/guides/timeline/SUNSET.svg new file mode 100644 index 00000000..a36c2fce --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/SUNSET.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIDE_IS_HIGH.svg b/devsite/source/assets/images/guides/timeline/TIDE_IS_HIGH.svg new file mode 100644 index 00000000..5374514a --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIDE_IS_HIGH.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_BASEBALL.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_BASEBALL.svg new file mode 100644 index 00000000..be359376 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_BASEBALL.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_CALENDAR.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_CALENDAR.svg new file mode 100644 index 00000000..adb6780d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_CALENDAR.svg @@ -0,0 +1,3359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_MISSED_CALL.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_MISSED_CALL.svg new file mode 100644 index 00000000..b9997778 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_MISSED_CALL.svg @@ -0,0 +1,3368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_SPORTS.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_SPORTS.svg new file mode 100644 index 00000000..aced2d26 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_SPORTS.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_SUN.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_SUN.svg new file mode 100644 index 00000000..fe2625f7 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_SUN.svg @@ -0,0 +1,3364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TIMELINE_WEATHER.svg b/devsite/source/assets/images/guides/timeline/TIMELINE_WEATHER.svg new file mode 100644 index 00000000..ac408a98 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TIMELINE_WEATHER.svg @@ -0,0 +1,3363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/TV_SHOW.svg b/devsite/source/assets/images/guides/timeline/TV_SHOW.svg new file mode 100644 index 00000000..306786d7 --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/TV_SHOW.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/WATCH_DISCONNECTED.svg b/devsite/source/assets/images/guides/timeline/WATCH_DISCONNECTED.svg new file mode 100644 index 00000000..4464875d --- /dev/null +++ b/devsite/source/assets/images/guides/timeline/WATCH_DISCONNECTED.svg @@ -0,0 +1,3374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/guides/timeline/all-users.png b/devsite/source/assets/images/guides/timeline/all-users.png new file mode 100644 index 00000000..3d818600 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/all-users.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-layout~aplite.png b/devsite/source/assets/images/guides/timeline/calendar-layout~aplite.png new file mode 100644 index 00000000..ca598eee Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-layout~basalt.png b/devsite/source/assets/images/guides/timeline/calendar-layout~basalt.png new file mode 100644 index 00000000..62c79de1 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-layout~chalk.png b/devsite/source/assets/images/guides/timeline/calendar-layout~chalk.png new file mode 100644 index 00000000..997956f2 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-pin~aplite.png b/devsite/source/assets/images/guides/timeline/calendar-pin~aplite.png new file mode 100644 index 00000000..3325de9f Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-pin~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-pin~basalt.png b/devsite/source/assets/images/guides/timeline/calendar-pin~basalt.png new file mode 100644 index 00000000..874b0bc0 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-pin~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/calendar-pin~chalk.png b/devsite/source/assets/images/guides/timeline/calendar-pin~chalk.png new file mode 100644 index 00000000..1b6d87c1 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/calendar-pin~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/cloudpebble-ui.png b/devsite/source/assets/images/guides/timeline/cloudpebble-ui.png new file mode 100644 index 00000000..83a42690 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/cloudpebble-ui.png differ diff --git a/devsite/source/assets/images/guides/timeline/dev-portal-enable.png b/devsite/source/assets/images/guides/timeline/dev-portal-enable.png new file mode 100644 index 00000000..7815b7e5 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/dev-portal-enable.png differ diff --git a/devsite/source/assets/images/guides/timeline/dev-portal-keys.png b/devsite/source/assets/images/guides/timeline/dev-portal-keys.png new file mode 100644 index 00000000..ab99d5ab Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/dev-portal-keys.png differ diff --git a/devsite/source/assets/images/guides/timeline/dev-portal-manage.png b/devsite/source/assets/images/guides/timeline/dev-portal-manage.png new file mode 100644 index 00000000..d62730db Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/dev-portal-manage.png differ diff --git a/devsite/source/assets/images/guides/timeline/dev-portal-users.png b/devsite/source/assets/images/guides/timeline/dev-portal-users.png new file mode 100644 index 00000000..8b535a47 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/dev-portal-users.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-layout~aplite.png b/devsite/source/assets/images/guides/timeline/generic-layout~aplite.png new file mode 100644 index 00000000..02d322d6 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-layout~basalt.png b/devsite/source/assets/images/guides/timeline/generic-layout~basalt.png new file mode 100644 index 00000000..27ba818a Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-layout~chalk.png b/devsite/source/assets/images/guides/timeline/generic-layout~chalk.png new file mode 100644 index 00000000..ec7361a4 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-notification-layout~aplite.png b/devsite/source/assets/images/guides/timeline/generic-notification-layout~aplite.png new file mode 100644 index 00000000..b0882c12 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-notification-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-notification-layout~basalt.png b/devsite/source/assets/images/guides/timeline/generic-notification-layout~basalt.png new file mode 100644 index 00000000..78683d40 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-notification-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-notification-layout~chalk.png b/devsite/source/assets/images/guides/timeline/generic-notification-layout~chalk.png new file mode 100644 index 00000000..17b97268 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-notification-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-pin~aplite.png b/devsite/source/assets/images/guides/timeline/generic-pin~aplite.png new file mode 100644 index 00000000..cce50362 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-pin~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-pin~basalt.png b/devsite/source/assets/images/guides/timeline/generic-pin~basalt.png new file mode 100644 index 00000000..07a2d34c Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-pin~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-pin~chalk.png b/devsite/source/assets/images/guides/timeline/generic-pin~chalk.png new file mode 100644 index 00000000..d9eb7410 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-pin~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder-layout~aplite.png b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~aplite.png new file mode 100644 index 00000000..1ac7a283 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder-layout~basalt.png b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~basalt.png new file mode 100644 index 00000000..522f116e Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder-layout~chalk.png b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~chalk.png new file mode 100644 index 00000000..763fc7e5 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder.png b/devsite/source/assets/images/guides/timeline/generic-reminder.png new file mode 100644 index 00000000..9a3a83d3 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder~aplite.png b/devsite/source/assets/images/guides/timeline/generic-reminder~aplite.png new file mode 100644 index 00000000..5d41ce7e Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder~basalt.png b/devsite/source/assets/images/guides/timeline/generic-reminder~basalt.png new file mode 100644 index 00000000..9a8e5e7c Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/generic-reminder~chalk.png b/devsite/source/assets/images/guides/timeline/generic-reminder~chalk.png new file mode 100644 index 00000000..16e10f80 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/generic-reminder~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/individual.png b/devsite/source/assets/images/guides/timeline/individual.png new file mode 100644 index 00000000..a83caa03 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/individual.png differ diff --git a/devsite/source/assets/images/guides/timeline/notification-layout~basalt.png b/devsite/source/assets/images/guides/timeline/notification-layout~basalt.png new file mode 100644 index 00000000..1ae1c10f Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/notification-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/notification-layout~chalk.png b/devsite/source/assets/images/guides/timeline/notification-layout~chalk.png new file mode 100644 index 00000000..fa585e09 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/notification-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/preview.gif b/devsite/source/assets/images/guides/timeline/preview.gif new file mode 100644 index 00000000..260d2243 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/preview.gif differ diff --git a/devsite/source/assets/images/guides/timeline/reminder-future-day.png b/devsite/source/assets/images/guides/timeline/reminder-future-day.png new file mode 100644 index 00000000..a74b49fb Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/reminder-future-day.png differ diff --git a/devsite/source/assets/images/guides/timeline/reminder-one-hour.png b/devsite/source/assets/images/guides/timeline/reminder-one-hour.png new file mode 100644 index 00000000..a38657db Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/reminder-one-hour.png differ diff --git a/devsite/source/assets/images/guides/timeline/some-users.png b/devsite/source/assets/images/guides/timeline/some-users.png new file mode 100644 index 00000000..f4e95b57 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/some-users.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-layout~aplite.png b/devsite/source/assets/images/guides/timeline/sport-layout~aplite.png new file mode 100644 index 00000000..fb8e73ae Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-layout~basalt.png b/devsite/source/assets/images/guides/timeline/sport-layout~basalt.png new file mode 100644 index 00000000..0a2f0607 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-layout~chalk.png b/devsite/source/assets/images/guides/timeline/sport-layout~chalk.png new file mode 100644 index 00000000..7987faf2 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-pin~aplite.png b/devsite/source/assets/images/guides/timeline/sport-pin~aplite.png new file mode 100644 index 00000000..173744cd Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-pin~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-pin~basalt.png b/devsite/source/assets/images/guides/timeline/sport-pin~basalt.png new file mode 100644 index 00000000..530cc550 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-pin~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/sport-pin~chalk.png b/devsite/source/assets/images/guides/timeline/sport-pin~chalk.png new file mode 100644 index 00000000..765f96c5 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/sport-pin~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/timeline-compressed.png b/devsite/source/assets/images/guides/timeline/timeline-compressed.png new file mode 100644 index 00000000..e6d57829 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/timeline-compressed.png differ diff --git a/devsite/source/assets/images/guides/timeline/timeline-one-line.png b/devsite/source/assets/images/guides/timeline/timeline-one-line.png new file mode 100644 index 00000000..9499a4db Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/timeline-one-line.png differ diff --git a/devsite/source/assets/images/guides/timeline/timeline-opened.png b/devsite/source/assets/images/guides/timeline/timeline-opened.png new file mode 100644 index 00000000..8a6601ab Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/timeline-opened.png differ diff --git a/devsite/source/assets/images/guides/timeline/timeline-selected.png b/devsite/source/assets/images/guides/timeline/timeline-selected.png new file mode 100644 index 00000000..a59cce40 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/timeline-selected.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-layout~aplite.png b/devsite/source/assets/images/guides/timeline/weather-layout~aplite.png new file mode 100644 index 00000000..c77cf2dd Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-layout~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-layout~basalt.png b/devsite/source/assets/images/guides/timeline/weather-layout~basalt.png new file mode 100644 index 00000000..13e15baf Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-layout~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-layout~chalk.png b/devsite/source/assets/images/guides/timeline/weather-layout~chalk.png new file mode 100644 index 00000000..a2a6da8d Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-layout~chalk.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-pin~aplite.png b/devsite/source/assets/images/guides/timeline/weather-pin~aplite.png new file mode 100644 index 00000000..067313e3 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-pin~aplite.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-pin~basalt.png b/devsite/source/assets/images/guides/timeline/weather-pin~basalt.png new file mode 100644 index 00000000..f34ea248 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-pin~basalt.png differ diff --git a/devsite/source/assets/images/guides/timeline/weather-pin~chalk.png b/devsite/source/assets/images/guides/timeline/weather-pin~chalk.png new file mode 100644 index 00000000..dc9f6777 Binary files /dev/null and b/devsite/source/assets/images/guides/timeline/weather-pin~chalk.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/create-project.png b/devsite/source/assets/images/guides/tools-and-resources/create-project.png new file mode 100644 index 00000000..305312fa Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/create-project.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/empty-project.png b/devsite/source/assets/images/guides/tools-and-resources/empty-project.png new file mode 100644 index 00000000..66d9d406 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/empty-project.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/gear-options.png b/devsite/source/assets/images/guides/tools-and-resources/gear-options.png new file mode 100644 index 00000000..e58f0f67 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/gear-options.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/icon-delete.png b/devsite/source/assets/images/guides/tools-and-resources/icon-delete.png new file mode 100644 index 00000000..4d878197 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/icon-delete.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/icon-play.png b/devsite/source/assets/images/guides/tools-and-resources/icon-play.png new file mode 100644 index 00000000..85daf252 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/icon-play.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/icon-reload.png b/devsite/source/assets/images/guides/tools-and-resources/icon-reload.png new file mode 100644 index 00000000..7b6a1110 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/icon-reload.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/icon-rename.png b/devsite/source/assets/images/guides/tools-and-resources/icon-rename.png new file mode 100644 index 00000000..1cbf956c Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/icon-rename.png differ diff --git a/devsite/source/assets/images/guides/tools-and-resources/icon-save.png b/devsite/source/assets/images/guides/tools-and-resources/icon-save.png new file mode 100644 index 00000000..c9be0033 Binary files /dev/null and b/devsite/source/assets/images/guides/tools-and-resources/icon-save.png differ diff --git a/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-actual.png b/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-actual.png new file mode 100644 index 00000000..bc376092 Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-actual.png differ diff --git a/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-sample.png b/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-sample.png new file mode 100644 index 00000000..f1efd952 Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/app-configuration/clay-sample.png differ diff --git a/devsite/source/assets/images/guides/user-interfaces/app-configuration/message-keys.png b/devsite/source/assets/images/guides/user-interfaces/app-configuration/message-keys.png new file mode 100644 index 00000000..2e03b366 Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/app-configuration/message-keys.png differ diff --git a/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg new file mode 100644 index 00000000..036e4379 Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg differ diff --git a/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg new file mode 100644 index 00000000..d7a5f4dd Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg differ diff --git a/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif new file mode 100644 index 00000000..1805937f Binary files /dev/null and b/devsite/source/assets/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif differ diff --git a/devsite/source/assets/images/landing-page/back_for_more_bg.png b/devsite/source/assets/images/landing-page/back_for_more_bg.png new file mode 100755 index 00000000..71011b85 Binary files /dev/null and b/devsite/source/assets/images/landing-page/back_for_more_bg.png differ diff --git a/devsite/source/assets/images/landing-page/devblog.jpg b/devsite/source/assets/images/landing-page/devblog.jpg new file mode 100644 index 00000000..e28f61f5 Binary files /dev/null and b/devsite/source/assets/images/landing-page/devblog.jpg differ diff --git a/devsite/source/assets/images/landing-page/first_time_bg.png b/devsite/source/assets/images/landing-page/first_time_bg.png new file mode 100755 index 00000000..fc55153a Binary files /dev/null and b/devsite/source/assets/images/landing-page/first_time_bg.png differ diff --git a/devsite/source/assets/images/landing-page/interactive_bg.png b/devsite/source/assets/images/landing-page/interactive_bg.png new file mode 100644 index 00000000..3c7a401e Binary files /dev/null and b/devsite/source/assets/images/landing-page/interactive_bg.png differ diff --git a/devsite/source/assets/images/landing-page/meta_bg.png b/devsite/source/assets/images/landing-page/meta_bg.png new file mode 100644 index 00000000..298c5438 Binary files /dev/null and b/devsite/source/assets/images/landing-page/meta_bg.png differ diff --git a/devsite/source/assets/images/menu/blog.svg b/devsite/source/assets/images/menu/blog.svg new file mode 100644 index 00000000..6bc7b54e --- /dev/null +++ b/devsite/source/assets/images/menu/blog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/devsite/source/assets/images/menu/community.svg b/devsite/source/assets/images/menu/community.svg new file mode 100644 index 00000000..95d5f10c --- /dev/null +++ b/devsite/source/assets/images/menu/community.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/docs.svg b/devsite/source/assets/images/menu/docs.svg new file mode 100644 index 00000000..c0742b2d --- /dev/null +++ b/devsite/source/assets/images/menu/docs.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/devsite/source/assets/images/menu/examples.svg b/devsite/source/assets/images/menu/examples.svg new file mode 100644 index 00000000..70ad47bf --- /dev/null +++ b/devsite/source/assets/images/menu/examples.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/getting-started.svg b/devsite/source/assets/images/menu/getting-started.svg new file mode 100644 index 00000000..4279df21 --- /dev/null +++ b/devsite/source/assets/images/menu/getting-started.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/guides.svg b/devsite/source/assets/images/menu/guides.svg new file mode 100644 index 00000000..8fc416f3 --- /dev/null +++ b/devsite/source/assets/images/menu/guides.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/more.svg b/devsite/source/assets/images/menu/more.svg new file mode 100644 index 00000000..42c612ae --- /dev/null +++ b/devsite/source/assets/images/menu/more.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/overview.svg b/devsite/source/assets/images/menu/overview.svg new file mode 100644 index 00000000..bd241343 --- /dev/null +++ b/devsite/source/assets/images/menu/overview.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/menu/sdk.svg b/devsite/source/assets/images/menu/sdk.svg new file mode 100644 index 00000000..d8837e58 --- /dev/null +++ b/devsite/source/assets/images/menu/sdk.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/more/strap-adapter.png b/devsite/source/assets/images/more/strap-adapter.png new file mode 100644 index 00000000..6e08417b Binary files /dev/null and b/devsite/source/assets/images/more/strap-adapter.png differ diff --git a/devsite/source/assets/images/more/strap-components.png b/devsite/source/assets/images/more/strap-components.png new file mode 100644 index 00000000..462e9b29 Binary files /dev/null and b/devsite/source/assets/images/more/strap-components.png differ diff --git a/devsite/source/assets/images/more/strap-insert-pin.png b/devsite/source/assets/images/more/strap-insert-pin.png new file mode 100755 index 00000000..d9d8c029 Binary files /dev/null and b/devsite/source/assets/images/more/strap-insert-pin.png differ diff --git a/devsite/source/assets/images/more/strap-insert-pogo-pins.png b/devsite/source/assets/images/more/strap-insert-pogo-pins.png new file mode 100755 index 00000000..ce6337e9 Binary files /dev/null and b/devsite/source/assets/images/more/strap-insert-pogo-pins.png differ diff --git a/devsite/source/assets/images/more/strap-insert-wires.png b/devsite/source/assets/images/more/strap-insert-wires.png new file mode 100755 index 00000000..c57fd327 Binary files /dev/null and b/devsite/source/assets/images/more/strap-insert-wires.png differ diff --git a/devsite/source/assets/images/more/strap-into-adapter.png b/devsite/source/assets/images/more/strap-into-adapter.png new file mode 100755 index 00000000..b2f74940 Binary files /dev/null and b/devsite/source/assets/images/more/strap-into-adapter.png differ diff --git a/devsite/source/assets/images/more/strap-measurements.png b/devsite/source/assets/images/more/strap-measurements.png new file mode 100755 index 00000000..7df166ed Binary files /dev/null and b/devsite/source/assets/images/more/strap-measurements.png differ diff --git a/devsite/source/assets/images/pebbles/black.png b/devsite/source/assets/images/pebbles/black.png new file mode 100644 index 00000000..e3e73d8e Binary files /dev/null and b/devsite/source/assets/images/pebbles/black.png differ diff --git a/devsite/source/assets/images/pebbles/device_pebble2_black.png b/devsite/source/assets/images/pebbles/device_pebble2_black.png new file mode 100644 index 00000000..97a9373e Binary files /dev/null and b/devsite/source/assets/images/pebbles/device_pebble2_black.png differ diff --git a/devsite/source/assets/images/pebbles/device_pebble2_blacklime.png b/devsite/source/assets/images/pebbles/device_pebble2_blacklime.png new file mode 100644 index 00000000..7429d207 Binary files /dev/null and b/devsite/source/assets/images/pebbles/device_pebble2_blacklime.png differ diff --git a/devsite/source/assets/images/pebbles/device_pebble2_blackred.png b/devsite/source/assets/images/pebbles/device_pebble2_blackred.png new file mode 100644 index 00000000..0d3d822a Binary files /dev/null and b/devsite/source/assets/images/pebbles/device_pebble2_blackred.png differ diff --git a/devsite/source/assets/images/pebbles/device_pebble2_white.png b/devsite/source/assets/images/pebbles/device_pebble2_white.png new file mode 100644 index 00000000..3ae0029b Binary files /dev/null and b/devsite/source/assets/images/pebbles/device_pebble2_white.png differ diff --git a/devsite/source/assets/images/pebbles/device_pebble2_whiteteal.png b/devsite/source/assets/images/pebbles/device_pebble2_whiteteal.png new file mode 100644 index 00000000..f6a1df8e Binary files /dev/null and b/devsite/source/assets/images/pebbles/device_pebble2_whiteteal.png differ diff --git a/devsite/source/assets/images/pebbles/grey.png b/devsite/source/assets/images/pebbles/grey.png new file mode 100644 index 00000000..95ccf498 Binary files /dev/null and b/devsite/source/assets/images/pebbles/grey.png differ diff --git a/devsite/source/assets/images/pebbles/orange.png b/devsite/source/assets/images/pebbles/orange.png new file mode 100644 index 00000000..e0acba7d Binary files /dev/null and b/devsite/source/assets/images/pebbles/orange.png differ diff --git a/devsite/source/assets/images/pebbles/red.png b/devsite/source/assets/images/pebbles/red.png new file mode 100644 index 00000000..af78bde4 Binary files /dev/null and b/devsite/source/assets/images/pebbles/red.png differ diff --git a/devsite/source/assets/images/pebbles/snowy-black.png b/devsite/source/assets/images/pebbles/snowy-black.png new file mode 100644 index 00000000..9fdc5632 Binary files /dev/null and b/devsite/source/assets/images/pebbles/snowy-black.png differ diff --git a/devsite/source/assets/images/pebbles/snowy-red.png b/devsite/source/assets/images/pebbles/snowy-red.png new file mode 100644 index 00000000..07df8292 Binary files /dev/null and b/devsite/source/assets/images/pebbles/snowy-red.png differ diff --git a/devsite/source/assets/images/pebbles/snowy-white.png b/devsite/source/assets/images/pebbles/snowy-white.png new file mode 100644 index 00000000..48bd3922 Binary files /dev/null and b/devsite/source/assets/images/pebbles/snowy-white.png differ diff --git a/devsite/source/assets/images/pebbles/steel_black.png b/devsite/source/assets/images/pebbles/steel_black.png new file mode 100644 index 00000000..e0de193d Binary files /dev/null and b/devsite/source/assets/images/pebbles/steel_black.png differ diff --git a/devsite/source/assets/images/pebbles/steel_stainless.png b/devsite/source/assets/images/pebbles/steel_stainless.png new file mode 100644 index 00000000..7c213ade Binary files /dev/null and b/devsite/source/assets/images/pebbles/steel_stainless.png differ diff --git a/devsite/source/assets/images/pebbles/time-round-black-20.png b/devsite/source/assets/images/pebbles/time-round-black-20.png new file mode 100644 index 00000000..ed18b367 Binary files /dev/null and b/devsite/source/assets/images/pebbles/time-round-black-20.png differ diff --git a/devsite/source/assets/images/pebbles/time-round-red-14.png b/devsite/source/assets/images/pebbles/time-round-red-14.png new file mode 100644 index 00000000..3234409b Binary files /dev/null and b/devsite/source/assets/images/pebbles/time-round-red-14.png differ diff --git a/devsite/source/assets/images/pebbles/white.png b/devsite/source/assets/images/pebbles/white.png new file mode 100644 index 00000000..f7c64728 Binary files /dev/null and b/devsite/source/assets/images/pebbles/white.png differ diff --git a/devsite/source/assets/images/pebbles/white_black.png b/devsite/source/assets/images/pebbles/white_black.png new file mode 100644 index 00000000..f1ee365d Binary files /dev/null and b/devsite/source/assets/images/pebbles/white_black.png differ diff --git a/devsite/source/assets/images/round/splash.jpg b/devsite/source/assets/images/round/splash.jpg new file mode 100644 index 00000000..19b4614d Binary files /dev/null and b/devsite/source/assets/images/round/splash.jpg differ diff --git a/devsite/source/assets/images/sdk/action-menu.png b/devsite/source/assets/images/sdk/action-menu.png new file mode 100644 index 00000000..55726b82 Binary files /dev/null and b/devsite/source/assets/images/sdk/action-menu.png differ diff --git a/devsite/source/assets/images/sdk/build-process-3.png b/devsite/source/assets/images/sdk/build-process-3.png new file mode 100644 index 00000000..3cadc93d Binary files /dev/null and b/devsite/source/assets/images/sdk/build-process-3.png differ diff --git a/devsite/source/assets/images/sdk/cloud.svg b/devsite/source/assets/images/sdk/cloud.svg new file mode 100644 index 00000000..6cdbee4d --- /dev/null +++ b/devsite/source/assets/images/sdk/cloud.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/devsite/source/assets/images/sdk/concentricity~aplite.png b/devsite/source/assets/images/sdk/concentricity~aplite.png new file mode 100644 index 00000000..0aaed9f9 Binary files /dev/null and b/devsite/source/assets/images/sdk/concentricity~aplite.png differ diff --git a/devsite/source/assets/images/sdk/concentricity~basalt.png b/devsite/source/assets/images/sdk/concentricity~basalt.png new file mode 100644 index 00000000..d9564d26 Binary files /dev/null and b/devsite/source/assets/images/sdk/concentricity~basalt.png differ diff --git a/devsite/source/assets/images/sdk/concentricity~chalk.png b/devsite/source/assets/images/sdk/concentricity~chalk.png new file mode 100644 index 00000000..7a141a0f Binary files /dev/null and b/devsite/source/assets/images/sdk/concentricity~chalk.png differ diff --git a/devsite/source/assets/images/sdk/content-indicator-demo.png b/devsite/source/assets/images/sdk/content-indicator-demo.png new file mode 100644 index 00000000..d8abfda2 Binary files /dev/null and b/devsite/source/assets/images/sdk/content-indicator-demo.png differ diff --git a/devsite/source/assets/images/sdk/pebblekit.svg b/devsite/source/assets/images/sdk/pebblekit.svg new file mode 100644 index 00000000..84a52b7a --- /dev/null +++ b/devsite/source/assets/images/sdk/pebblekit.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/devsite/source/assets/images/sdk/sdk-box.svg b/devsite/source/assets/images/sdk/sdk-box.svg new file mode 100644 index 00000000..d295197e --- /dev/null +++ b/devsite/source/assets/images/sdk/sdk-box.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/devsite/source/assets/images/sdk/status-bar-color.png b/devsite/source/assets/images/sdk/status-bar-color.png new file mode 100644 index 00000000..ea2e3bae Binary files /dev/null and b/devsite/source/assets/images/sdk/status-bar-color.png differ diff --git a/devsite/source/assets/images/sdk/status-bar-default.png b/devsite/source/assets/images/sdk/status-bar-default.png new file mode 100644 index 00000000..1c581d2f Binary files /dev/null and b/devsite/source/assets/images/sdk/status-bar-default.png differ diff --git a/devsite/source/assets/images/sdk/time-dots.png b/devsite/source/assets/images/sdk/time-dots.png new file mode 100644 index 00000000..68941e9d Binary files /dev/null and b/devsite/source/assets/images/sdk/time-dots.png differ diff --git a/devsite/source/assets/images/tutorials/advanced/action-completed.gif b/devsite/source/assets/images/tutorials/advanced/action-completed.gif new file mode 100644 index 00000000..8f8bf40e Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/action-completed.gif differ diff --git a/devsite/source/assets/images/tutorials/advanced/action-completed.png b/devsite/source/assets/images/tutorials/advanced/action-completed.png new file mode 100644 index 00000000..421c5f55 Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/action-completed.png differ diff --git a/devsite/source/assets/images/tutorials/advanced/clock-anim.gif b/devsite/source/assets/images/tutorials/advanced/clock-anim.gif new file mode 100644 index 00000000..011832fe Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/clock-anim.gif differ diff --git a/devsite/source/assets/images/tutorials/advanced/pdcs-example.gif b/devsite/source/assets/images/tutorials/advanced/pdcs-example.gif new file mode 100644 index 00000000..edd0d36e Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/pdcs-example.gif differ diff --git a/devsite/source/assets/images/tutorials/advanced/weather-image.png b/devsite/source/assets/images/tutorials/advanced/weather-image.png new file mode 100644 index 00000000..a76dfe5e Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/weather-image.png differ diff --git a/devsite/source/assets/images/tutorials/advanced/weather.png b/devsite/source/assets/images/tutorials/advanced/weather.png new file mode 100644 index 00000000..2b2fde1f Binary files /dev/null and b/devsite/source/assets/images/tutorials/advanced/weather.png differ diff --git a/devsite/source/assets/images/tutorials/beginner/hello-pebble-blank-window.png b/devsite/source/assets/images/tutorials/beginner/hello-pebble-blank-window.png new file mode 100644 index 00000000..da6cf9b7 Binary files /dev/null and b/devsite/source/assets/images/tutorials/beginner/hello-pebble-blank-window.png differ diff --git a/devsite/source/assets/images/tutorials/beginner/hello-pebble-text-layer.png b/devsite/source/assets/images/tutorials/beginner/hello-pebble-text-layer.png new file mode 100644 index 00000000..7d4742d5 Binary files /dev/null and b/devsite/source/assets/images/tutorials/beginner/hello-pebble-text-layer.png differ diff --git a/devsite/source/assets/images/tutorials/intermediate/battery-level.png b/devsite/source/assets/images/tutorials/intermediate/battery-level.png new file mode 100644 index 00000000..f844607e Binary files /dev/null and b/devsite/source/assets/images/tutorials/intermediate/battery-level.png differ diff --git a/devsite/source/assets/images/tutorials/intermediate/bt-icon.png b/devsite/source/assets/images/tutorials/intermediate/bt-icon.png new file mode 100644 index 00000000..41db3dce Binary files /dev/null and b/devsite/source/assets/images/tutorials/intermediate/bt-icon.png differ diff --git a/devsite/source/assets/images/tutorials/intermediate/bt.png b/devsite/source/assets/images/tutorials/intermediate/bt.png new file mode 100644 index 00000000..f5725509 Binary files /dev/null and b/devsite/source/assets/images/tutorials/intermediate/bt.png differ diff --git a/devsite/source/assets/images/tutorials/intermediate/date.png b/devsite/source/assets/images/tutorials/intermediate/date.png new file mode 100644 index 00000000..95633b4d Binary files /dev/null and b/devsite/source/assets/images/tutorials/intermediate/date.png differ diff --git a/devsite/source/assets/images/tutorials/js-watchface-tutorial/rocky-time.png b/devsite/source/assets/images/tutorials/js-watchface-tutorial/rocky-time.png new file mode 100644 index 00000000..63f36cf1 Binary files /dev/null and b/devsite/source/assets/images/tutorials/js-watchface-tutorial/rocky-time.png differ diff --git a/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc-weather.png b/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc-weather.png new file mode 100644 index 00000000..a58ea89f Binary files /dev/null and b/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc-weather.png differ diff --git a/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc.png b/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc.png new file mode 100644 index 00000000..152de77b Binary files /dev/null and b/devsite/source/assets/images/tutorials/js-watchface-tutorial/tictoc.png differ diff --git a/devsite/source/assets/js/404.js b/devsite/source/assets/js/404.js new file mode 100644 index 00000000..fbf6c25a --- /dev/null +++ b/devsite/source/assets/js/404.js @@ -0,0 +1,58 @@ +--- +--- + +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var $results = $('#js-404-search'); + +var search = new Search({ + appId: '{{ site.algolia_app_id }}', + apiKey: '{{ site.algolia_api_key }}', + prefix: '{{ site.algolia_prefix }}', + options: { + hitsPerPage: 2, + getRankingInfo: true, + distinct: true, + removeWordsIfNoResults: 'firstWords', + analytics: false + } +}); + +search.on('results', showResults); +search.on('error', function (err) { + Rollbar.error('404 search error', err); +}); + +search.search(window.location.pathname.split('/').join(' ')); + +function showResults(results) { + Object.keys(results).forEach(function (index) { + var type = Search.indexes[index]; + var typeResults = results[index]; + $results.append('
  • ' + type.title + '
  • '); + typeResults.hits.forEach(function (result) { + $results.append(buildResult(result)); + }); + }); + $('#js-404-search-intro').show() +} + +function buildResult(result) { + var $result = $('
  • '); + $result.append($('').text(result.title).attr('href', result.url)); + return $result; +} diff --git a/devsite/source/assets/js/app.js b/devsite/source/assets/js/app.js new file mode 100644 index 00000000..07836cfe --- /dev/null +++ b/devsite/source/assets/js/app.js @@ -0,0 +1,208 @@ +--- +--- + +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function () { + + $.get('/mobilenav.html', function (response) { + $('body').append(response); + $(".js-mobile-nav a").each(function (index, elem) { + if ($(elem).attr('href') == window.location.pathname) { + $(elem).parent().addClass('active'); + } + }); + $(".js-mobile-nav").mmenu({ + footer: { + add: true, + content: 'Privacy Policy · Cookie Policy' + } + }, { + classNames: { + selected: 'active' + } + }); + $('body').trigger('pbl-menu-loaded'); + }); + + $('.js-toc-select').on('change', function () { + var url = $(this).val(); + if (! url) { + return; + } + if (url[0] == '/') { + window.location = url; + } + else { + window.location = '#' + url; + } + }); + + $('.js-mobile-nav-toggle').on('click', function () { + $('.js-mobile-nav').trigger("open.mm"); + }); + + // Handles affixing the gray-box to the right hand side of the page + // so it will stay in position when you scroll. + $('.gray-box--fixed').each(function (index, elem) { + var $graybox = $(elem); + var $parent = $graybox.parent(); + $graybox.css('position', 'fixed'); + $(window).on('resize', function () { + updateGraybox(); + }); + updateGraybox(); + + function updateGraybox() { + $graybox.css('height', 'auto'); + $graybox.css('left', $parent.offset().left); + $graybox.css('width', $parent.width()); + var maxHeight = $(window).height() - $graybox.position().top - 10; + if ($graybox.height() > maxHeight) { + $graybox.css('height', maxHeight); + } + } + }); + + if ($('.gray-box--scrollspy').length) { + var triggers = []; + $('.toc__item').each(function (index, item) { + var url = $(item).find('a').attr('href'); + var id = url.substr(url.indexOf('#') + 1); + var $header = $('[id="' + id + '"]'); + if ($header && $header.length) { + triggers.push({ + from: $header.position().top, + item: $(item) + }); + } + }); + $(window).on('scroll', function () { + var pos = $(window).scrollTop(); + pos += $(window).height() / 8; + triggers.forEach(function (trigger) { + if (pos >= trigger.from) { + $('.toc__item--active').removeClass('toc__item--active'); + trigger.item.addClass('toc__item--active'); + } + }); + }); + } + + // OSS: disabled because PrettyEmbed has no clear license and cannot be included. + // $().prettyEmbed({ + // useFitVids: true + // }); + + $('body').on('mouseover', '.code-copy-link', function () { + $(this).prepend('COPY'); + }); + + $('body').on('mouseout', '.code-copy-link', function () { + $('span', this).remove(); + }); + + $('.js-doc-menu-toggle').click(function (e) { + e.preventDefault(); + $('.js-doc-menu').toggleClass('documentation-menu--visible'); + }); + + /* SDK Platform */ + + if (Cookies.get('sdk-platform')) { + showPlatformSpecifics(Cookies.get('sdk-platform')); + } + else if (queryString.parse(location.search).sdk === 'local') { + setPlatform('local'); + } + else if (queryString.parse(location.search).sdk === 'cloudpebble') { + setPlatform('cloudpebble'); + } + else { + $('.platform-choice--large').show(); + } + + $('.js-platform-choice').on('click', function (event) { + event.preventDefault(); + setPlatform($(this).data('sdk-platform')); + }); + + function setPlatform(platform) { + Cookies.set('sdk-platform', platform); + showPlatformSpecifics(platform); + } + + function showPlatformSpecifics(platform) { + $('.platform-choice--large').hide(); + $('.platform-specific').hide(); + $('.platform-specific[data-sdk-platform="' + platform + '"]').show(); + $('.platform-choice--small').show(); + } + + $('a[data-modal-target]').on('show.r.modal', function(event) { + // TODO: Track this in the analytics. + }); + + $('body').on('pbl-menu-loaded', function () { + $('.video--autoplay').each(function () { + this.play(); + }); + }); + + // SCREENSHOT VIEWER + + $('.js-screenshot-tabs h4').on('click', function () { + var $viewer = $(this).parents('.screenshot-viewer'); + if (!$viewer.hasClass('screenshot-viewer--tabbed')) { + return; + } + + var platform = $(this).data('platform'); + + $('.js-screenshot-tabs h4').removeClass('selected'); + $('.js-screenshot-tabs h4[data-platform="' + platform + '"]').addClass('selected'); + + $('.screenshot-viewer__platform').hide(); + $('.screenshot-viewer__platform[data-platform="' + platform + '"]').show(); + }); + + function updateScreenshotViewers() { + $('.screenshot-viewer').each(function (index, elem) { + var $viewer = $(elem); + if ($viewer.hasClass('screenshot-viewer--tabbed')) { + return; + } + + var $screenshots = $viewer.find('.screenshot-viewer__screenshots'); + var width = $screenshots.find('.screenshot-viewer__platform').map(function (index, elem) { + return $(elem).width(); + }).toArray().reduce(function (previous, current) { + return previous + current; + }); + if (width > $viewer.width()) { + $viewer.addClass('screenshot-viewer--tabbed'); + $('.js-screenshot-tabs h4:last-child').click(); + } + }); + } + + $(window).on('resize', function () { + updateScreenshotViewers(); + }); + updateScreenshotViewers(); + +}()); diff --git a/devsite/source/assets/js/disqus.js b/devsite/source/assets/js/disqus.js new file mode 100644 index 00000000..a964ee1d --- /dev/null +++ b/devsite/source/assets/js/disqus.js @@ -0,0 +1,36 @@ +--- +--- + +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$(function () { + + var $thread = $('#disqus_thread'); + if (! $thread || $thread.length === 0) { + return; + } + + var disqus_shortname = '{{ site.disqus.short_name }}'; + window.disqus_identifier = $thread.data('post-url'); + window.disqus_url = $thread.data('post-url'); + (function () { + var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; + dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); + }()); + +}); diff --git a/devsite/source/assets/js/docs/c.js b/devsite/source/assets/js/docs/c.js new file mode 100644 index 00000000..8900d6c0 --- /dev/null +++ b/devsite/source/assets/js/docs/c.js @@ -0,0 +1,42 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$(function () { + + $('.docs__item__tabs a[data-platform]').on('click', function () { + var offset = $(this).position().top - $('body').scrollTop(); + var platform = $(this).data('platform'); + showPlatform(platform); + savePlatform(platform); + var newOffset = $(this).position().top - $('body').scrollTop(); + $('body').scrollTop($('body').scrollTop() + (newOffset - offset)); + }); + + function showPlatform(platform) { + $('.docs__item__tabs a[data-platform]').removeClass('active'); + $('.docs__item__tabs a[data-platform="' + platform + '"]').addClass('active'); + $('section[data-platform]').hide(); + $('section[data-platform="' + platform + '"]').show(); + } + + function savePlatform(platform) { + Cookies.set('docs-platform', platform); + } + + var defaultPlatform = $('[data-basalt-only]').length ? 'basalt' : 'aplite'; + showPlatform(Cookies.get('docs-platform') || defaultPlatform); + +}); diff --git a/devsite/source/assets/js/docs/pebblejs.js b/devsite/source/assets/js/docs/pebblejs.js new file mode 100644 index 00000000..df023c4b --- /dev/null +++ b/devsite/source/assets/js/docs/pebblejs.js @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$(function () { + var hash = window.location.hash; + if (hash.length) { + var $link = $('.section-menu a[href$="' + hash + '"]'); + if ($link && $link.length) { + $('.section-menu').scrollTo($link); + } + } +}); \ No newline at end of file diff --git a/devsite/source/assets/js/examples.js b/devsite/source/assets/js/examples.js new file mode 100644 index 00000000..b3829da7 --- /dev/null +++ b/devsite/source/assets/js/examples.js @@ -0,0 +1,76 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var exampleFilters = {}; + +$('.js-examples-tag').on('click', function (event) { + event.preventDefault(); + + var $tag = $(this); + var $filters = $tag.parents('.examples__filters'); + + if ($tag.hasClass('js-selected-tag')) { + $tag.removeClass('js-selected-tag'); + $tag.removeClass('selected'); + $filters.removeClass('examples__filters--selected'); + + exampleFilters[$filters.data('filter-type')] = null; + updateExamples(); + } + else { + var $selectedTag = $filters.find('.js-selected-tag'); + $selectedTag.removeClass('js-selected-tag'); + $selectedTag.removeClass('selected'); + + $tag.addClass('js-selected-tag'); + $tag.addClass('selected'); + $filters.addClass('examples__filters--selected'); + + exampleFilters[$filters.data('filter-type')] = $tag.text(); + updateExamples(); + } +}); + +function updateExamples() { + $('.js-example').each(function (index, elem) { + var $example = $(elem); + var tags = $example.data('tags').split('|'); + var languages = $example.data('languages').split('|'); + var hardwarePlatforms = $example.data('hardware-platforms').split('|'); + + if (exampleFilters['tags'] && tags.indexOf(exampleFilters['tags']) === -1) { + $example.hide(); + return; + } + if (exampleFilters['languages'] && languages.indexOf(exampleFilters['languages']) === -1) { + $example.hide(); + return; + } + if (exampleFilters['hardware-platforms'] && hardwarePlatforms.indexOf(exampleFilters['hardware-platforms']) === -1) { + $example.hide(); + return; + } + + $example.show(); + }); + + if ($('.js-example:visible').length > 0) { + $('.js-no-examples').hide(); + } + else { + $('.js-no-examples').show(); + } +} diff --git a/devsite/source/assets/js/landing-page.js b/devsite/source/assets/js/landing-page.js new file mode 100644 index 00000000..34b9f869 --- /dev/null +++ b/devsite/source/assets/js/landing-page.js @@ -0,0 +1,105 @@ +--- +--- + +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function willPlayVideo() { + return ! (/iPad|iPhone|iPod/i.test(navigator.userAgent) || /Android/i.test(navigator.userAgent)); +} + +$('body').on('pbl-menu-loaded', function () { + if (! willPlayVideo()) { + return; + } + $('.js-video-slide').each(function (index, elem) { + $(elem).vide({ + mp4: $(elem).data('video'), + ogv: $(elem).data('video').replace(/\.mp4$/, '.ogv'), + webm: $(elem).data('video').replace(/\.mp4$/, '.webm') + }, { + loop: false, + posterType: 'none' + }); + $(elem).data('vide').getVideoObject().onended = function (event) { + $(elem).trigger('video-ended'); + }; + }); +}); + +var slideTimeout = null; +var previousSlide = null; +var indexSlick = $('.js-slider').slick({ + autoplay: false, + arrows: false, + dots: true, + onBeforeChange: function (event, from, to) { + if (slideTimeout) { + clearTimeout(slideTimeout); + slideTimeout = null; + } + var toSlide = findSlideByIndex(to); + if (isVideoSlide(toSlide) && willPlayVideo()) { + var video = toSlide.data('vide').getVideoObject(); + if (video) { + video.currentTime = 0; + video.play(); + } + } + previousSlide = findSlideByIndex(from); + }, + onAfterChange: function (event) { + if (previousSlide && isVideoSlide(previousSlide) && willPlayVideo()) { + var video = previousSlide.data('vide').getVideoObject(); + if (video) { + video.pause(); + video.currentTime = 0; + } + } + previousSlide = null; + } +}); + +function setupSlideTransition() { + var slide = $('.js-slide[data-slide-index="' + indexSlick.slickCurrentSlide() + '"]:not(.slick-cloned)'); + var isVideoSlide = slide.hasClass('js-video-slide'); + if (isVideoSlide && willPlayVideo()) { + slide.on('video-ended', function () { + if (slide.data('slide-index') === indexSlick.slickCurrentSlide()) { + indexSlick.slickNext(); + setupSlideTransition(); + } + }); + } + else { + var duration = slide.data('duration'); + slideTimeout = setTimeout(function () { + indexSlick.slickNext(); + setupSlideTransition(); + slideTimeout = null; + }, duration); + } +} + +function findSlideByIndex(index) { + return $('.js-slide[data-slide-index="' + index + '"]:not(.slick-cloned)'); +} + +function isVideoSlide(slide) { + return slide.hasClass('js-video-slide'); +} + +setupSlideTransition(); diff --git a/devsite/source/assets/js/menu.js b/devsite/source/assets/js/menu.js new file mode 100644 index 00000000..58d3ae97 --- /dev/null +++ b/devsite/source/assets/js/menu.js @@ -0,0 +1,24 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$(function () { + + $('.sidebar__menu a span').on('click', function () { + $(this).parent().parent().toggleClass('expanded'); + $(this).parent().parent().toggleClass('collapsed'); + }); + +}); \ No newline at end of file diff --git a/devsite/source/assets/js/quicksearch.js b/devsite/source/assets/js/quicksearch.js new file mode 100644 index 00000000..45296f64 --- /dev/null +++ b/devsite/source/assets/js/quicksearch.js @@ -0,0 +1,177 @@ +--- +--- + +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function () { + + var searchLayouts = { + 1: [ 1 ], + 2: [ 1, 1 ], + 3: [ 1, 1, 1 ], + 4: [ 1, 1, 2 ], + 5: [ 1, 2, 2 ], + 6: [ 2, 2, 2 ], + }; + + var search = new Search({ + appId: '{{ site.algolia_app_id }}', + apiKey: '{{ site.algolia_api_key }}', + prefix: '{{ site.algolia_prefix }}', + options: { + hitsPerPage: 8, + getRankingInfo: true, + distinct: true + } + }); + search.on('results', showResults); + search.on('clear', clearResults); + search.on('error', function (err) { + Rollbar.error('Quicksearch error', err); + }); + + + var $input = $('#quicksearch'); + var $results = $('#quicksearch__results'); + var $blackout = $('#search__blackout'); + var selectedResult = [-1, -1]; + + $input.on('keyup', function (event) { + switch (event.keyCode) { + case 40: // DOWN + updateSelectedResult(1, 0); + break; + case 38: // UP + updateSelectedResult(-1, 0); + break; + case 39: // RIGHT + updateSelectedResult(0, 1); + break; + case 37: // LEFT + updateSelectedResult(0, -1); + break; + case 13: // ENTER + if (selectedResult[0] >= 0 && selectedResult[1] >= 0) { + gotoSelectedResult(); + } + break; + case 27: // ESCAPE + clearResults(); + $input.blur(); + break; + } + search.search($input.val()); + }); + + $blackout.on('click', function () { + hideResults(); + }); + + function getOrderedIndexNames(results) { + return Object.keys(results); + } + + function isEmpty(obj) { + var hasOwn = object.prototype.hasOwnProperty; + for(var x in obj) { + if (hasOwn.call(obj, x)) { + return false; + } + } + return true; + } + + function showResults(results) { + $results.html(''); + if (isEmpty(results)) { + showEmptyResults(); + return; + } + var indexes = getOrderedIndexNames(results); + var numResults = Object.keys(results).length; + var layout = searchLayouts[numResults]; + var $row = $('
    ').addClass('row'); + buildLayout($row, layout); + indexes.forEach(function (index, pos) { + var $container = getContainer($row, pos); + $container.addClass('quicksearch__block--' + Search.indexes[index].cssClass); + var searchResults = new SearchResults(results[index], Search.indexes[index], $container.data('result-limit')); + searchResults.render($container); + }); + $results.append($row).show(); + $blackout.fadeIn(100); + $('body').addClass('stop-scrolling'); + } + + function showEmptyResults() { + $results.html(Handlebars.templates['quicksearch-no-results']()); + } + + /** + * buildLayout + * Takes a layout array and builds the DOM elements inside the given row + * param $row The DOM element to build the search layout inside of + * param layout The search layout array to build the layout from. + **/ + function buildLayout($row, layout) { + var colWidth = Math.min(12 / layout.length, 12); + var index = 0; + layout.forEach(function (col, c) { + var $col = $('
    ').addClass('col-l-' + colWidth); + for (var r = 0; r < col; r += 1) { + var $block = $('
    ').addClass('quicksearch__block') + .attr('data-result-container', index) + .attr('data-result-limit', col == 2 ? 3 : 6); + $col.append($block); + index += 1; + } + $row.append($col); + }); + } + + /** + * getContainer + * Finds a search result container that was built using buildLayout + * @param $row The DOM element that contains all of the result containers + * @param pos The index of the container + * @returns The results container for the given index as a DOM element + **/ + function getContainer($row, index) { + return $row.find('[data-result-container="' + index + '"]'); + } + + function clearResults() { + hideResults(); + $results.find('ul').html(''); + } + + function hideResults() { + $blackout.fadeOut(100, function () { + $results.hide(); + $('body').removeClass('stop-scrolling'); + }); + } + + function updateSelectedResult(dx, dy) { + + } + + function gotoSelectedResult() { + + } + +}()); diff --git a/devsite/source/assets/js/sdk/index.js b/devsite/source/assets/js/sdk/index.js new file mode 100644 index 00000000..fb9860c7 --- /dev/null +++ b/devsite/source/assets/js/sdk/index.js @@ -0,0 +1,38 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$sdkLink = $('.js-sdk-link'); +$('.instructions-other').hide(); +if ((navigator.userAgent.match(/MSIE/i)) || (navigator.userAgent.match(/Windows/i))) { + $sdkLink.attr('href', '/sdk/install/windows/'); + $('.instructions-windows').show(); +} +else if (navigator.userAgent.match(/Macintosh/i)) { + $sdkLink.attr('href', '/sdk/install/mac/'); + $('.instructions-mac').show(); + if (document.location.hash === '#homebrew') { + setTimeout(function () { + $('a[data-modal-target="#modal-mac-auto"]').click(); + }, 100); + } +} +else if (navigator.userAgent.match(/Linux/i) && ! navigator.userAgent.match(/Android/i)) { + $sdkLink.attr('href', '/sdk/install/linux/'); + $('.instructions-linux').show(); +} +else { + $('.instructions-other').show(); +} diff --git a/devsite/source/assets/js/search.js b/devsite/source/assets/js/search.js new file mode 100644 index 00000000..a10e2500 --- /dev/null +++ b/devsite/source/assets/js/search.js @@ -0,0 +1,148 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* export SearchResults */ +/* export Search */ + +function SearchResults(results, type, limit) { + this.results = results; + this.type = type; + this.limit = limit; +}; + +SearchResults.prototype.render = function($container) { + this.results.hits.splice(this.limit, this.results.hits.length); + this.results.hits.forEach(processResult.bind(this)); + var $resultGroup = $(Handlebars.templates['quicksearch-result-group']({ + title: this.type.title, + results: this.results.hits + })); + $container.append($resultGroup); +}; + +function processResult(result) { + if (result._snippetResult && result._snippetResult.content) { + result.summary = result._snippetResult.content.value; + } + else if (result._snippetResult && result._snippetResult.summary) { + result.summary = result._snippetResult.summary.value; + } + if (this.type.section) { + result.section = this.type.section(result).join(' · '); + } + else { + result.section = ''; + } +} + +function Search(config) { + this.client = new AlgoliaSearch(config.appId, config.apiKey); + this.prefix = config.prefix; + this.searchNumber = 0; + this.lastQuery = null; + this.searchOptions = config.options; +} + +heir.inherit(Search, EventEmitter); + +Search.indexes = { + 'guides': { + title: 'Guides', + cssClass: 'guides', + section: function (hit) { + var section = [ hit.group ]; + if (hit.subgroup && hit.subgroup.length) { + section.push(hit.subgroup); + } + return section; + } + }, + 'documentation': { + title: 'Documentation', + cssClass: 'docs', + section: function (hit) { + return [ hit.language ] + } + }, + 'blog-posts': { + title: 'Blog Posts', + cssClass: 'more', + section: function (hit) { + var dateString = hit.posted.split(" ")[0]; + if (!dateString) return "Invalid Date"; + + return [ moment(dateString).format('MMM DD YYYY')]; + } + }, + 'examples': { + title: 'Examples', + cssClass: 'more', + }, + 'community-resources': { + title: 'Community Resources', + cssClass: 'community', + section: function (hit) { + switch(hit.resourceType) { + case 'community_tools': + return ['Tool']; + case 'community_apps': + return ['App']; + case 'community_libraries': + return ['Library'] + } + return []; + } + }, + 'other': { + title: 'Other', + cssClass: 'other' + } +}; + +Search.prototype.search = function (query) { + if (query === this.lastQuery) { + return; + } + if (query.length <= 2) { + this.emitEvent('clear'); + return; + } + this.searchNumber += 1; + var thisNumber = this.searchNumber; + this.client.startQueriesBatch(); + Object.keys(Search.indexes).forEach(function (type) { + this.client.addQueryInBatch(this.prefix + type, query, this.searchOptions); + }.bind(this)); + this.client.sendQueriesBatch(function (success, content) { + if (thisNumber !== this.searchNumber) { + return; + } + if (! success) { + this.emitEvent('error', new Error('Algolia responded with a failure event.')); + } + if (! content.results) { + this.emitEvent('error', new Error('No results returned from Algolia.')); + } + var results = {}; + content.results.forEach(function (result) { + if (result.hits.length > 0) { + var index = result.index.replace(this.prefix, ''); + results[index] = result; + } + }.bind(this)); + this.emitEvent('results', [ results ]); + }.bind(this)); +}; diff --git a/devsite/source/assets/js/search/page.js b/devsite/source/assets/js/search/page.js new file mode 100644 index 00000000..795c21e5 --- /dev/null +++ b/devsite/source/assets/js/search/page.js @@ -0,0 +1,20 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var query = queryString.parse(location.search).query; +if (query) { + $('#quicksearch').val(query).trigger('keyup'); +} diff --git a/devsite/source/assets/js/templates.js b/devsite/source/assets/js/templates.js new file mode 100644 index 00000000..6dc2063c --- /dev/null +++ b/devsite/source/assets/js/templates.js @@ -0,0 +1,76 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['color-picker-sample-window'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var helper; + + return "
    Window *window = window_create();\nwindow_set_background_color(window, "
    +    + container.escapeExpression(((helper = (helper = helpers.color_name || (depth0 != null ? depth0.color_name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"color_name","hash":{},"data":data}) : helper)))
    +    + ");\nwindow_stack_push(window, true);\n
    \n"; +},"useData":true}); +templates['events-info-none'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "
    \n

    There are currenly no events scheduled for " + + alias4(((helper = (helper = helpers.month || (depth0 != null ? depth0.month : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"month","hash":{},"data":data}) : helper))) + + ".

    \n

    Try changing the month using the calendar controls on the map above.

    \n

    If you know about an event happening in " + + alias4(((helper = (helper = helpers.month || (depth0 != null ? depth0.month : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"month","hash":{},"data":data}) : helper))) + + ", use the submission form to let us know!

    \n
    \n"; +},"useData":true}); +templates['events-info'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "
    \n
    \n

    " + + alias4(((helper = (helper = helpers.dateString || (depth0 != null ? depth0.dateString : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dateString","hash":{},"data":data}) : helper))) + + " - " + + alias4(((helper = (helper = helpers.location || (depth0 != null ? depth0.location : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"location","hash":{},"data":data}) : helper))) + + "

    \n

    " + + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper))) + + " | More

    \n \n
    \n
    \n"; +},"useData":true}); +templates['quicksearch-no-results'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + return "
    \n
    \n

    There were no search results.

    \n
    \n
    \n"; +},"useData":true}); +templates['quicksearch-result-group'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "
  • \n " + + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper))) + + "\n

    " + + ((stack1 = ((helper = (helper = helpers.section || (depth0 != null ? depth0.section : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"section","hash":{},"data":data}) : helper))) != null ? stack1 : "") + + "

    \n

    " + + ((stack1 = ((helper = (helper = helpers.summary || (depth0 != null ? depth0.summary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"summary","hash":{},"data":data}) : helper))) != null ? stack1 : "") + + "

    \n
  • \n"; +},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : {}; + + return "

    " + + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper))) + + "

    \n
      \n" + + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.results : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "
    \n"; +},"useData":true}); +})(); \ No newline at end of file diff --git a/devsite/source/assets/js/tools/color-dict.js b/devsite/source/assets/js/tools/color-dict.js new file mode 100644 index 00000000..daa4bd6b --- /dev/null +++ b/devsite/source/assets/js/tools/color-dict.js @@ -0,0 +1,3346 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var color_picker_colors = { + "#AAFFAA": { + "dist": 25.45584412271571, + "closest": { + "url": "http://en.wikipedia.org/wiki/Spring_green_(color)#Mint_green", + "r": 152, + "b": 152, + "name": "Mint green", + "g": 255 + }, + "c_value_identifier": "GColorMintGreenARGB8", + "binary": "0b11101110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorMintGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 255, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAFFAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAFFAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b11, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Mint Green", + "g": 255, + "c_identifier": "GColorMintGreen", + "url": "http://en.wikipedia.org/wiki/Spring_green_(color)#Mint_green", + "html": "#AAFFAA", + "r": 170, + "identifier": "MintGreen" + }, + "#FFAAAA": { + "dist": 20.688160865577203, + "closest": { + "url": "http://en.wikipedia.org/wiki/Variations_of_orange#Melon", + "r": 253, + "b": 180, + "name": "Melon", + "g": 188 + }, + "c_value_identifier": "GColorMelonARGB8", + "binary": "0b11111010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorMelon", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 170, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFAAAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFAAAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b10, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Melon", + "g": 170, + "c_identifier": "GColorMelon", + "url": "http://en.wikipedia.org/wiki/Variations_of_orange#Melon", + "html": "#FFAAAA", + "r": 255, + "identifier": "Melon" + }, + "#FF55FF": { + "dist": 26.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Variations_of_magenta#Ultra_pink", + "r": 255, + "b": 255, + "name": "Shocking pink (Crayola)", + "g": 111 + }, + "c_value_identifier": "GColorShockingPinkARGB8", + "binary": "0b11110111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorShockingPink", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 85, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF55FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF55FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b01, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Shocking Pink (Crayola)", + "g": 85, + "c_identifier": "GColorShockingPink", + "url": "http://en.wikipedia.org/wiki/Variations_of_magenta#Ultra_pink", + "html": "#FF55FF", + "r": 255, + "identifier": "ShockingPink" + }, + "#FF0055": { + "dist": 6.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Crimson#Folly", + "r": 255, + "b": 79, + "name": "Folly", + "g": 0 + }, + "c_value_identifier": "GColorFollyARGB8", + "binary": "0b11110001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorFolly", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 0, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF0055)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF0055", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b00, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Folly", + "g": 0, + "c_identifier": "GColorFolly", + "url": "http://en.wikipedia.org/wiki/Crimson#Folly", + "html": "#FF0055", + "r": 255, + "identifier": "Folly" + }, + "#FF5555": { + "dist": 9.433981132056603, + "closest": { + "url": "http://en.wikipedia.org/wiki/Sunset_(color)#Sunset_orange", + "r": 253, + "b": 83, + "name": "Sunset orange", + "g": 94 + }, + "c_value_identifier": "GColorSunsetOrangeARGB8", + "binary": "0b11110101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorSunsetOrange", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 85, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF5555)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF5555", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b01, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Sunset Orange", + "g": 85, + "c_identifier": "GColorSunsetOrange", + "url": "http://en.wikipedia.org/wiki/Sunset_(color)#Sunset_orange", + "html": "#FF5555", + "r": 255, + "identifier": "SunsetOrange" + }, + "#555500": { + "dist": 33.58571124749333, + "closest": { + "url": "http://en.wikipedia.org/wiki/Army_green", + "r": 75, + "b": 32, + "name": "Army green", + "g": 83 + }, + "c_value_identifier": "GColorArmyGreenARGB8", + "binary": "0b11010100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorArmyGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 85, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x555500)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#555500", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b01, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Army Green", + "g": 85, + "c_identifier": "GColorArmyGreen", + "url": "http://en.wikipedia.org/wiki/Army_green", + "html": "#555500", + "r": 85, + "identifier": "ArmyGreen" + }, + "#0000AA": { + "dist": 14.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Duke_blue", + "r": 0, + "b": 156, + "name": "Duke blue", + "g": 0 + }, + "c_value_identifier": "GColorDukeBlueARGB8", + "binary": "0b11000010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorDukeBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 0, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x0000AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#0000AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b00, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Duke Blue", + "g": 0, + "c_identifier": "GColorDukeBlue", + "url": "http://en.wikipedia.org/wiki/Duke_blue", + "html": "#0000AA", + "r": 0, + "identifier": "DukeBlue" + }, + "#00AAAA": { + "dist": 21.840329667841555, + "closest": { + "url": "http://en.wikipedia.org/wiki/Tiffany_Blue#Tiffany_Blue", + "r": 10, + "b": 181, + "name": "Tiffany Blue", + "g": 186 + }, + "c_value_identifier": "GColorTiffanyBlueARGB8", + "binary": "0b11001010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorTiffanyBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 170, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00AAAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00AAAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b10, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Tiffany Blue", + "g": 170, + "c_identifier": "GColorTiffanyBlue", + "url": "http://en.wikipedia.org/wiki/Tiffany_Blue#Tiffany_Blue", + "html": "#00AAAA", + "r": 0, + "identifier": "TiffanyBlue" + }, + "#55FF55": { + "dist": 49.57822102496216, + "closest": { + "url": "http://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors#Standard_colors", + "r": 118, + "b": 122, + "name": "Screamin' Green", + "g": 255 + }, + "c_value_identifier": "GColorScreaminGreenARGB8", + "binary": "0b11011101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorScreaminGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 255, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55FF55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55FF55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b11, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Screamin' Green", + "g": 255, + "c_identifier": "GColorScreaminGreen", + "url": "http://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors#Standard_colors", + "html": "#55FF55", + "r": 85, + "identifier": "ScreaminGreen" + }, + "#FFFFAA": { + "dist": 20.199009876724155, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_yellow#Pastel_yellow", + "r": 253, + "b": 150, + "name": "Pastel yellow", + "g": 253 + }, + "c_value_identifier": "GColorPastelYellowARGB8", + "binary": "0b11111110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorPastelYellow", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 255, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFFFAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFFFAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b11, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Pastel Yellow", + "g": 255, + "c_identifier": "GColorPastelYellow", + "url": "http://en.wikipedia.org/wiki/Shades_of_yellow#Pastel_yellow", + "html": "#FFFFAA", + "r": 255, + "identifier": "PastelYellow" + }, + "#FFAAFF": { + "dist": 14.352700094407323, + "closest": { + "url": "http://en.wikipedia.org/wiki/Lavender_(color)#Rich_brilliant_lavender", + "r": 241, + "b": 254, + "name": "Rich brilliant lavender", + "g": 167 + }, + "c_value_identifier": "GColorRichBrilliantLavenderARGB8", + "binary": "0b11111011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorRichBrilliantLavender", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 170, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFAAFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFAAFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b10, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Rich Brilliant Lavender", + "g": 170, + "c_identifier": "GColorRichBrilliantLavender", + "url": "http://en.wikipedia.org/wiki/Lavender_(color)#Rich_brilliant_lavender", + "html": "#FFAAFF", + "r": 255, + "identifier": "RichBrilliantLavender" + }, + "#55FF00": { + "dist": 17.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Bright_green", + "r": 102, + "b": 0, + "name": "Bright green", + "g": 255 + }, + "c_value_identifier": "GColorBrightGreenARGB8", + "binary": "0b11011100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorBrightGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 255, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55FF00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55FF00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b11, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Bright Green", + "g": 255, + "c_identifier": "GColorBrightGreen", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Bright_green", + "html": "#55FF00", + "r": 85, + "identifier": "BrightGreen" + }, + "#FF55AA": { + "dist": 7.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Brilliant_rose", + "r": 255, + "b": 163, + "name": "Brilliant rose", + "g": 85 + }, + "c_value_identifier": "GColorBrilliantRoseARGB8", + "binary": "0b11110110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorBrilliantRose", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 85, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF55AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF55AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b01, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Brilliant Rose", + "g": 85, + "c_identifier": "GColorBrilliantRose", + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Brilliant_rose", + "html": "#FF55AA", + "r": 255, + "identifier": "BrilliantRose" + }, + "#55AAAA": { + "dist": 18.547236990991408, + "closest": { + "url": "http://en.wikipedia.org/wiki/Cadet_grey#Cadet_blue", + "r": 95, + "b": 160, + "name": "Cadet blue", + "g": 158 + }, + "c_value_identifier": "GColorCadetBlueARGB8", + "binary": "0b11011010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorCadetBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 170, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55AAAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55AAAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b10, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Cadet Blue", + "g": 170, + "c_identifier": "GColorCadetBlue", + "url": "http://en.wikipedia.org/wiki/Cadet_grey#Cadet_blue", + "html": "#55AAAA", + "r": 85, + "identifier": "CadetBlue" + }, + "#AA5555": { + "dist": 7.681145747868608, + "closest": { + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Rose_vale", + "r": 171, + "b": 82, + "name": "Rose vale", + "g": 78 + }, + "c_value_identifier": "GColorRoseValeARGB8", + "binary": "0b11100101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorRoseVale", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 85, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA5555)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA5555", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b01, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Rose Vale", + "g": 85, + "c_identifier": "GColorRoseVale", + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Rose_vale", + "html": "#AA5555", + "r": 170, + "identifier": "RoseVale" + }, + "#FF00AA": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Fuchsia_(color)#Fashion_fuchsia", + "r": 255, + "b": 170, + "name": "Fashion Magenta", + "g": 0 + }, + "c_value_identifier": "GColorFashionMagentaARGB8", + "binary": "0b11110010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorFashionMagenta", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 0, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF00AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF00AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b00, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Fashion Magenta", + "g": 0, + "c_identifier": "GColorFashionMagenta", + "url": "http://en.wikipedia.org/wiki/Fuchsia_(color)#Fashion_fuchsia", + "html": "#FF00AA", + "r": 255, + "identifier": "FashionMagenta" + }, + "#00AA55": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/J\u00e4germeister", + "r": 0, + "b": 85, + "name": "Jaeger Green", + "g": 170 + }, + "c_value_identifier": "GColorJaegerGreenARGB8", + "binary": "0b11001001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorJaegerGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 170, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00AA55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00AA55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b10, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Jaeger Green", + "g": 170, + "c_identifier": "GColorJaegerGreen", + "url": "http://en.wikipedia.org/wiki/J\u00e4germeister", + "html": "#00AA55", + "r": 0, + "identifier": "JaegerGreen" + }, + "#AAAAFF": { + "dist": 36.069377593742864, + "closest": { + "url": "http://en.wikipedia.org/wiki/Baby_blue#Baby_blue_eyes", + "r": 161, + "b": 241, + "name": "Baby blue eyes", + "g": 202 + }, + "c_value_identifier": "GColorBabyBlueEyesARGB8", + "binary": "0b11101011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorBabyBlueEyes", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 170, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAAAFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAAAFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b10, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Baby Blue Eyes", + "g": 170, + "c_identifier": "GColorBabyBlueEyes", + "url": "http://en.wikipedia.org/wiki/Baby_blue#Baby_blue_eyes", + "html": "#AAAAFF", + "r": 170, + "identifier": "BabyBlueEyes" + }, + "#AA55AA": { + "dist": 17.916472867168917, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_purple#Purpureus", + "r": 154, + "b": 174, + "name": "Purpureus", + "g": 78 + }, + "c_value_identifier": "GColorPurpureusARGB8", + "binary": "0b11100110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorPurpureus", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 85, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA55AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA55AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b01, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Purpureus", + "g": 85, + "c_identifier": "GColorPurpureus", + "url": "http://en.wikipedia.org/wiki/Shades_of_purple#Purpureus", + "html": "#AA55AA", + "r": 170, + "identifier": "Purpureus" + }, + "#FFAA00": { + "dist": 3.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Chrome_yellow", + "r": 255, + "b": 0, + "name": "Chrome yellow", + "g": 167 + }, + "c_value_identifier": "GColorChromeYellowARGB8", + "binary": "0b11111000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorChromeYellow", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 170, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFAA00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFAA00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b10, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Chrome Yellow", + "g": 170, + "c_identifier": "GColorChromeYellow", + "url": "http://en.wikipedia.org/wiki/Chrome_yellow", + "html": "#FFAA00", + "r": 255, + "identifier": "ChromeYellow" + }, + "#005500": { + "dist": 15.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Dark_green_.28X11.29", + "r": 0, + "b": 0, + "name": "Dark green (X11)", + "g": 100 + }, + "c_value_identifier": "GColorDarkGreenARGB8", + "binary": "0b11000100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorDarkGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 85, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x005500)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#005500", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b01, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Dark Green (X11)", + "g": 85, + "c_identifier": "GColorDarkGreen", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Dark_green_.28X11.29", + "html": "#005500", + "r": 0, + "identifier": "DarkGreen" + }, + "#FF0000": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Red", + "r": 255, + "b": 0, + "name": "Red", + "g": 0 + }, + "c_value_identifier": "GColorRedARGB8", + "binary": "0b11110000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorRed", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 0, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF0000)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF0000", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b00, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Red", + "g": 0, + "c_identifier": "GColorRed", + "url": "http://en.wikipedia.org/wiki/Red", + "html": "#FF0000", + "r": 255, + "identifier": "Red" + }, + "#5555AA": { + "dist": 5.916079783099616, + "closest": { + "url": "http://en.wikipedia.org/wiki/Variations_of_blue#Liberty", + "r": 84, + "b": 167, + "name": "Liberty", + "g": 90 + }, + "c_value_identifier": "GColorLibertyARGB8", + "binary": "0b11010110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorLiberty", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 85, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x5555AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#5555AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b01, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Liberty", + "g": 85, + "c_identifier": "GColorLiberty", + "url": "http://en.wikipedia.org/wiki/Variations_of_blue#Liberty", + "html": "#5555AA", + "r": 85, + "identifier": "Liberty" + }, + "#AAAAAA": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_gray#Light_gray", + "r": 170, + "b": 170, + "name": "Light Gray", + "g": 170 + }, + "c_value_identifier": "GColorLightGrayARGB8", + "binary": "0b11101010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorLightGray", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 170, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAAAAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAAAAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b10, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Light Gray", + "g": 170, + "c_identifier": "GColorLightGray", + "url": "http://en.wikipedia.org/wiki/Shades_of_gray#Light_gray", + "html": "#AAAAAA", + "r": 170, + "identifier": "LightGray" + }, + "#AA00FF": { + "dist": 11.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_violet#Vivid_violet", + "r": 159, + "b": 255, + "name": "Vivid violet", + "g": 0 + }, + "c_value_identifier": "GColorVividVioletARGB8", + "binary": "0b11100011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorVividViolet", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 0, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA00FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA00FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b00, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Vivid Violet", + "g": 0, + "c_identifier": "GColorVividViolet", + "url": "http://en.wikipedia.org/wiki/Shades_of_violet#Vivid_violet", + "html": "#AA00FF", + "r": 170, + "identifier": "VividViolet" + }, + "#FFAA55": { + "dist": 11.74734012447073, + "closest": { + "url": "http://en.wikipedia.org/wiki/Saffron_(color)#Rajah", + "r": 251, + "b": 96, + "name": "Rajah", + "g": 171 + }, + "c_value_identifier": "GColorRajahARGB8", + "binary": "0b11111001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorRajah", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 170, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFAA55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFAA55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b10, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Rajah", + "g": 170, + "c_identifier": "GColorRajah", + "url": "http://en.wikipedia.org/wiki/Saffron_(color)#Rajah", + "html": "#FFAA55", + "r": 255, + "identifier": "Rajah" + }, + "#5500AA": { + "dist": 41.23105625617661, + "closest": { + "url": "http://en.wikipedia.org/wiki/Indigo#Pigment_indigo_.28web_color_indigo.29", + "r": 75, + "b": 130, + "name": "Indigo (web)", + "g": 0 + }, + "c_value_identifier": "GColorIndigoARGB8", + "binary": "0b11010010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorIndigo", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 0, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x5500AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#5500AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b00, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Indigo (Web)", + "g": 0, + "c_identifier": "GColorIndigo", + "url": "http://en.wikipedia.org/wiki/Indigo#Pigment_indigo_.28web_color_indigo.29", + "html": "#5500AA", + "r": 85, + "identifier": "Indigo" + }, + "#55AA55": { + "dist": 33.25657829663178, + "closest": { + "url": "http://en.wikipedia.org/wiki/Spring_bud#May_green", + "r": 76, + "b": 65, + "name": "May green", + "g": 145 + }, + "c_value_identifier": "GColorMayGreenARGB8", + "binary": "0b11011001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorMayGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 170, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55AA55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55AA55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b10, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "May Green", + "g": 170, + "c_identifier": "GColorMayGreen", + "url": "http://en.wikipedia.org/wiki/Spring_bud#May_green", + "html": "#55AA55", + "r": 85, + "identifier": "MayGreen" + }, + "#FFFF55": { + "dist": 12.409673645990857, + "closest": { + "url": "http://en.wikipedia.org/wiki/Icterine", + "r": 252, + "b": 94, + "name": "Icterine", + "g": 247 + }, + "c_value_identifier": "GColorIcterineARGB8", + "binary": "0b11111101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorIcterine", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 255, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFFF55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFFF55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b11, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Icterine", + "g": 255, + "c_identifier": "GColorIcterine", + "url": "http://en.wikipedia.org/wiki/Icterine", + "html": "#FFFF55", + "r": 255, + "identifier": "Icterine" + }, + "#550000": { + "dist": 15.937377450509228, + "closest": { + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Bulgarian_Rose", + "r": 72, + "b": 7, + "name": "Bulgarian rose", + "g": 6 + }, + "c_value_identifier": "GColorBulgarianRoseARGB8", + "binary": "0b11010000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorBulgarianRose", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 0, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x550000)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#550000", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b00, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Bulgarian Rose", + "g": 0, + "c_identifier": "GColorBulgarianRose", + "url": "http://en.wikipedia.org/wiki/Rose_(color)#Bulgarian_Rose", + "html": "#550000", + "r": 85, + "identifier": "BulgarianRose" + }, + "#FF5500": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Orange_(colour)", + "r": 255, + "b": 0, + "name": "Orange", + "g": 85 + }, + "c_value_identifier": "GColorOrangeARGB8", + "binary": "0b11110100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorOrange", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 85, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF5500)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF5500", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b01, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Orange", + "g": 85, + "c_identifier": "GColorOrange", + "url": "http://en.wikipedia.org/wiki/Orange_(colour)", + "html": "#FF5500", + "r": 255, + "identifier": "Orange" + }, + "#00FF00": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Green", + "r": 0, + "b": 0, + "name": "Green", + "g": 255 + }, + "c_value_identifier": "GColorGreenARGB8", + "binary": "0b11001100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 255, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00FF00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00FF00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b11, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Green", + "g": 255, + "c_identifier": "GColorGreen", + "url": "http://en.wikipedia.org/wiki/Green", + "html": "#00FF00", + "r": 0, + "identifier": "Green" + }, + "#AA5500": { + "dist": 3.605551275463989, + "closest": { + "url": "http://en.wikipedia.org/wiki/Tan_(color)#Windsor_tan", + "r": 167, + "b": 2, + "name": "Windsor tan", + "g": 85 + }, + "c_value_identifier": "GColorWindsorTanARGB8", + "binary": "0b11100100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorWindsorTan", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 85, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA5500)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA5500", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b01, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Windsor Tan", + "g": 85, + "c_identifier": "GColorWindsorTan", + "url": "http://en.wikipedia.org/wiki/Tan_(color)#Windsor_tan", + "html": "#AA5500", + "r": 170, + "identifier": "WindsorTan" + }, + "#AA55FF": { + "dist": 29.79932885150268, + "closest": { + "url": "http://en.wikipedia.org/wiki/Lavender_(color)#Lavender_indigo", + "r": 148, + "b": 235, + "name": "Lavender indigo", + "g": 87 + }, + "c_value_identifier": "GColorLavenderIndigoARGB8", + "binary": "0b11100111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorLavenderIndigo", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 85, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA55FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA55FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b01, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Lavender Indigo", + "g": 85, + "c_identifier": "GColorLavenderIndigo", + "url": "http://en.wikipedia.org/wiki/Lavender_(color)#Lavender_indigo", + "html": "#AA55FF", + "r": 170, + "identifier": "LavenderIndigo" + }, + "#555555": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_gray#Dark_medium_gray_.28dark_gray_.28X11.29.29", + "r": 85, + "b": 85, + "name": "Dark Gray", + "g": 85 + }, + "c_value_identifier": "GColorDarkGrayARGB8", + "binary": "0b11010101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorDarkGray", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 85, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x555555)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#555555", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b01, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Dark Gray", + "g": 85, + "c_identifier": "GColorDarkGray", + "url": "http://en.wikipedia.org/wiki/Shades_of_gray#Dark_medium_gray_.28dark_gray_.28X11.29.29", + "html": "#555555", + "r": 85, + "identifier": "DarkGray" + }, + "#55FFFF": { + "dist": 40.44749683231337, + "closest": { + "url": "http://en.wikipedia.org/wiki/Electric_blue_(color)", + "r": 125, + "b": 255, + "name": "Electric blue", + "g": 249 + }, + "c_value_identifier": "GColorElectricBlueARGB8", + "binary": "0b11011111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorElectricBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 255, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55FFFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55FFFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b11, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Electric Blue", + "g": 255, + "c_identifier": "GColorElectricBlue", + "url": "http://en.wikipedia.org/wiki/Electric_blue_(color)", + "html": "#55FFFF", + "r": 85, + "identifier": "ElectricBlue" + }, + "#0055FF": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Blue_Moon_(beer)", + "r": 0, + "b": 255, + "name": "Blue Moon", + "g": 85 + }, + "c_value_identifier": "GColorBlueMoonARGB8", + "binary": "0b11000111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorBlueMoon", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 85, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x0055FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#0055FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b01, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Blue Moon", + "g": 85, + "c_identifier": "GColorBlueMoon", + "url": "http://en.wikipedia.org/wiki/Blue_Moon_(beer)", + "html": "#0055FF", + "r": 0, + "identifier": "BlueMoon" + }, + "#00FFFF": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Cyan", + "r": 0, + "b": 255, + "name": "Cyan", + "g": 255 + }, + "c_value_identifier": "GColorCyanARGB8", + "binary": "0b11001111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorCyan", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 255, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00FFFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00FFFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b11, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Cyan", + "g": 255, + "c_identifier": "GColorCyan", + "url": "http://en.wikipedia.org/wiki/Cyan", + "html": "#00FFFF", + "r": 0, + "identifier": "Cyan" + }, + "#000000": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Black", + "r": 0, + "b": 0, + "name": "Black", + "g": 0 + }, + "c_value_identifier": "GColorBlackARGB8", + "binary": "0b11000000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorBlack", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 0, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x000000)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#000000", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b00, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Black", + "g": 0, + "c_identifier": "GColorBlack", + "url": "http://en.wikipedia.org/wiki/Black", + "html": "#000000", + "r": 0, + "identifier": "Black" + }, + "#55FFAA": { + "dist": 38.01315561749642, + "closest": { + "url": "http://en.wikipedia.org/wiki/Aquamarine_(color)#Medium_aquamarine", + "r": 102, + "b": 170, + "name": "Medium aquamarine", + "g": 221 + }, + "c_value_identifier": "GColorMediumAquamarineARGB8", + "binary": "0b11011110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorMediumAquamarine", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 255, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55FFAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55FFAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b11, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Medium Aquamarine", + "g": 255, + "c_identifier": "GColorMediumAquamarine", + "url": "http://en.wikipedia.org/wiki/Aquamarine_(color)#Medium_aquamarine", + "html": "#55FFAA", + "r": 85, + "identifier": "MediumAquamarine" + }, + "#AA0000": { + "dist": 6.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Candy_apple_red_(color)#Dark_candy_apple_red", + "r": 164, + "b": 0, + "name": "Dark candy apple red", + "g": 0 + }, + "c_value_identifier": "GColorDarkCandyAppleRedARGB8", + "binary": "0b11100000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorDarkCandyAppleRed", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 0, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA0000)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA0000", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b00, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Dark Candy Apple Red", + "g": 0, + "c_identifier": "GColorDarkCandyAppleRed", + "url": "http://en.wikipedia.org/wiki/Candy_apple_red_(color)#Dark_candy_apple_red", + "html": "#AA0000", + "r": 170, + "identifier": "DarkCandyAppleRed" + }, + "#AAAA00": { + "dist": 28.74021572639983, + "closest": { + "url": "http://en.wikipedia.org/wiki/Chartreuse_(color)#Limerick", + "r": 157, + "b": 9, + "name": "Limerick", + "g": 194 + }, + "c_value_identifier": "GColorLimerickARGB8", + "binary": "0b11101000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorLimerick", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 170, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAAA00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAAA00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b10, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Limerick", + "g": 170, + "c_identifier": "GColorLimerick", + "url": "http://en.wikipedia.org/wiki/Chartreuse_(color)#Limerick", + "html": "#AAAA00", + "r": 170, + "identifier": "Limerick" + }, + "#0055AA": { + "dist": 14.035668847618199, + "closest": { + "url": "http://en.wikipedia.org/wiki/Cobalt_blue", + "r": 0, + "b": 171, + "name": "Cobalt Blue", + "g": 71 + }, + "c_value_identifier": "GColorCobaltBlueARGB8", + "binary": "0b11000110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorCobaltBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 85, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x0055AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#0055AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b01, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Cobalt Blue", + "g": 85, + "c_identifier": "GColorCobaltBlue", + "url": "http://en.wikipedia.org/wiki/Cobalt_blue", + "html": "#0055AA", + "r": 0, + "identifier": "CobaltBlue" + }, + "#AAFFFF": { + "dist": 8.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Sky_Blue#Celeste", + "r": 178, + "b": 255, + "name": "Celeste", + "g": 255 + }, + "c_value_identifier": "GColorCelesteARGB8", + "binary": "0b11101111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorCeleste", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 255, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAFFFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAFFFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b11, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Celeste", + "g": 255, + "c_identifier": "GColorCeleste", + "url": "http://en.wikipedia.org/wiki/Sky_Blue#Celeste", + "html": "#AAFFFF", + "r": 170, + "identifier": "Celeste" + }, + "#5500FF": { + "dist": 22.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Ultramarine#Electric_ultramarine", + "r": 63, + "b": 255, + "name": "Electric ultramarine", + "g": 0 + }, + "c_value_identifier": "GColorElectricUltramarineARGB8", + "binary": "0b11010011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorElectricUltramarine", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 0, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x5500FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#5500FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b00, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Electric Ultramarine", + "g": 0, + "c_identifier": "GColorElectricUltramarine", + "url": "http://en.wikipedia.org/wiki/Ultramarine#Electric_ultramarine", + "html": "#5500FF", + "r": 85, + "identifier": "ElectricUltramarine" + }, + "#55AAFF": { + "dist": 28.879058156387302, + "closest": { + "url": "http://en.wikipedia.org/wiki/Azure_(color)#Picton_blue", + "r": 69, + "b": 232, + "name": "Picton blue", + "g": 177 + }, + "c_value_identifier": "GColorPictonBlueARGB8", + "binary": "0b11011011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorPictonBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 170, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55AAFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55AAFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b10, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Picton Blue", + "g": 170, + "c_identifier": "GColorPictonBlue", + "url": "http://en.wikipedia.org/wiki/Azure_(color)#Picton_blue", + "html": "#55AAFF", + "r": 85, + "identifier": "PictonBlue" + }, + "#AAFF55": { + "dist": 22.11334438749598, + "closest": { + "url": "http://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors#Standard_colors", + "r": 178, + "b": 93, + "name": "Inchworm", + "g": 236 + }, + "c_value_identifier": "GColorInchwormARGB8", + "binary": "0b11101101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorInchworm", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 255, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAFF55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAFF55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b11, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Inchworm", + "g": 255, + "c_identifier": "GColorInchworm", + "url": "http://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors#Standard_colors", + "html": "#AAFF55", + "r": 170, + "identifier": "Inchworm" + }, + "#0000FF": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Blue", + "r": 0, + "b": 255, + "name": "Blue", + "g": 0 + }, + "c_value_identifier": "GColorBlueARGB8", + "binary": "0b11000011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 0, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x0000FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#0000FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b00, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Blue", + "g": 0, + "c_identifier": "GColorBlue", + "url": "http://en.wikipedia.org/wiki/Blue", + "html": "#0000FF", + "r": 0, + "identifier": "Blue" + }, + "#00AAFF": { + "dist": 17.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Cerulean", + "r": 0, + "b": 238, + "name": "Vivid cerulean", + "g": 170 + }, + "c_value_identifier": "GColorVividCeruleanARGB8", + "binary": "0b11001011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorVividCerulean", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 170, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00AAFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00AAFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b10, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Vivid Cerulean", + "g": 170, + "c_identifier": "GColorVividCerulean", + "url": "http://en.wikipedia.org/wiki/Cerulean", + "html": "#00AAFF", + "r": 0, + "identifier": "VividCerulean" + }, + "#AA00AA": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Purple", + "r": 170, + "b": 170, + "name": "Purple", + "g": 0 + }, + "c_value_identifier": "GColorPurpleARGB8", + "binary": "0b11100010", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorPurple", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 0, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA00AA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA00AA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100010}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b00, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Purple", + "g": 0, + "c_identifier": "GColorPurple", + "url": "http://en.wikipedia.org/wiki/Purple", + "html": "#AA00AA", + "r": 170, + "identifier": "Purple" + }, + "#55AA00": { + "dist": 29.9833287011299, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Kelly_green", + "r": 76, + "b": 23, + "name": "Kelly green", + "g": 187 + }, + "c_value_identifier": "GColorKellyGreenARGB8", + "binary": "0b11011000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorKellyGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 170, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x55AA00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#55AA00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11011000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b10, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Kelly Green", + "g": 170, + "c_identifier": "GColorKellyGreen", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Kelly_green", + "html": "#55AA00", + "r": 85, + "identifier": "KellyGreen" + }, + "#00FF55": { + "dist": 38.80721582386451, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Malachite", + "r": 11, + "b": 81, + "name": "Malachite", + "g": 218 + }, + "c_value_identifier": "GColorMalachiteARGB8", + "binary": "0b11001101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorMalachite", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 255, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00FF55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00FF55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b11, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Malachite", + "g": 255, + "c_identifier": "GColorMalachite", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Malachite", + "html": "#00FF55", + "r": 0, + "identifier": "Malachite" + }, + "#005555": { + "dist": 12.165525060596439, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Midnight_green", + "r": 0, + "b": 83, + "name": "Midnight green (eagle green)", + "g": 73 + }, + "c_value_identifier": "GColorMidnightGreenARGB8", + "binary": "0b11000101", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorMidnightGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 85, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x005555)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#005555", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000101}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b01, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Midnight Green (Eagle Green)", + "g": 85, + "c_identifier": "GColorMidnightGreen", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Midnight_green", + "html": "#005555", + "r": 0, + "identifier": "MidnightGreen" + }, + "#FFFF00": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Yellow", + "r": 255, + "b": 0, + "name": "Yellow", + "g": 255 + }, + "c_value_identifier": "GColorYellowARGB8", + "binary": "0b11111100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorYellow", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 255, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFFF00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFFF00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b11, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Yellow", + "g": 255, + "c_identifier": "GColorYellow", + "url": "http://en.wikipedia.org/wiki/Yellow", + "html": "#FFFF00", + "r": 255, + "identifier": "Yellow" + }, + "#FF00FF": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Magenta", + "r": 255, + "b": 255, + "name": "Magenta", + "g": 0 + }, + "c_value_identifier": "GColorMagentaARGB8", + "binary": "0b11110011", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorMagenta", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 0, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFF00FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FF00FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11110011}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b00, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Magenta", + "g": 0, + "c_identifier": "GColorMagenta", + "url": "http://en.wikipedia.org/wiki/Magenta", + "html": "#FF00FF", + "r": 255, + "identifier": "Magenta" + }, + "#AAFF00": { + "dist": 4.242640687119285, + "closest": { + "url": "http://en.wikipedia.org/wiki/Spring_bud", + "r": 167, + "b": 0, + "name": "Spring bud", + "g": 252 + }, + "c_value_identifier": "GColorSpringBudARGB8", + "binary": "0b11101100", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorSpringBud", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 255, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAFF00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAFF00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101100}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b11, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Spring Bud", + "g": 255, + "c_identifier": "GColorSpringBud", + "url": "http://en.wikipedia.org/wiki/Spring_bud", + "html": "#AAFF00", + "r": 170, + "identifier": "SpringBud" + }, + "#AA0055": { + "dist": 15.066519173319364, + "closest": { + "url": "http://en.wikipedia.org/wiki/Red-violet#Jazzberry_jam", + "r": 165, + "b": 94, + "name": "Jazzberry jam", + "g": 11 + }, + "c_value_identifier": "GColorJazzberryJamARGB8", + "binary": "0b11100001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorJazzberryJam", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 0, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAA0055)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AA0055", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11100001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b00, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Jazzberry Jam", + "g": 0, + "c_identifier": "GColorJazzberryJam", + "url": "http://en.wikipedia.org/wiki/Red-violet#Jazzberry_jam", + "html": "#AA0055", + "r": 170, + "identifier": "JazzberryJam" + }, + "#5555FF": { + "dist": 24.041630560342615, + "closest": { + "url": "http://en.wikipedia.org/wiki/Light_blue", + "r": 102, + "b": 255, + "name": "Very light blue", + "g": 102 + }, + "c_value_identifier": "GColorVeryLightBlueARGB8", + "binary": "0b11010111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorVeryLightBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 85, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x5555FF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#5555FF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b01, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "Very Light Blue", + "g": 85, + "c_identifier": "GColorVeryLightBlue", + "url": "http://en.wikipedia.org/wiki/Light_blue", + "html": "#5555FF", + "r": 85, + "identifier": "VeryLightBlue" + }, + "#FFFFFF": { + "dist": 0.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/White", + "r": 255, + "b": 255, + "name": "White", + "g": 255 + }, + "c_value_identifier": "GColorWhiteARGB8", + "binary": "0b11111111", + "b": 255, + "literals": [ + { + "id": "define", + "value": "GColorWhite", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(255, 255, 255)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xFFFFFF)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#FFFFFF", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11111111}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b11, .g=0b11, .b=0b11}", + "description": "GColor (components)" + } + ], + "name": "White", + "g": 255, + "c_identifier": "GColorWhite", + "url": "http://en.wikipedia.org/wiki/White", + "html": "#FFFFFF", + "r": 255, + "identifier": "White" + }, + "#00AA00": { + "dist": 26.0, + "closest": { + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Islamic_green", + "r": 0, + "b": 0, + "name": "Islamic green", + "g": 144 + }, + "c_value_identifier": "GColorIslamicGreenARGB8", + "binary": "0b11001000", + "b": 0, + "literals": [ + { + "id": "define", + "value": "GColorIslamicGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 170, 0)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00AA00)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00AA00", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001000}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b10, .b=0b00}", + "description": "GColor (components)" + } + ], + "name": "Islamic Green", + "g": 170, + "c_identifier": "GColorIslamicGreen", + "url": "http://en.wikipedia.org/wiki/Shades_of_green#Islamic_green", + "html": "#00AA00", + "r": 0, + "identifier": "IslamicGreen" + }, + "#000055": { + "dist": 35.84689665786984, + "closest": { + "url": "http://en.wikipedia.org/wiki/Oxford_University", + "r": 0, + "b": 71, + "name": "Oxford Blue", + "g": 33 + }, + "c_value_identifier": "GColorOxfordBlueARGB8", + "binary": "0b11000001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorOxfordBlue", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 0, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x000055)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#000055", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11000001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b00, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Oxford Blue", + "g": 0, + "c_identifier": "GColorOxfordBlue", + "url": "http://en.wikipedia.org/wiki/Oxford_University", + "html": "#000055", + "r": 0, + "identifier": "OxfordBlue" + }, + "#550055": { + "dist": 30.298514815086232, + "closest": { + "url": "http://en.wikipedia.org/wiki/Purple#Tyrian_purple:_Classical_antiquity", + "r": 102, + "b": 60, + "name": "Imperial purple", + "g": 2 + }, + "c_value_identifier": "GColorImperialPurpleARGB8", + "binary": "0b11010001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorImperialPurple", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(85, 0, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x550055)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#550055", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11010001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b01, .g=0b00, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Imperial Purple", + "g": 0, + "c_identifier": "GColorImperialPurple", + "url": "http://en.wikipedia.org/wiki/Purple#Tyrian_purple:_Classical_antiquity", + "html": "#550055", + "r": 85, + "identifier": "ImperialPurple" + }, + "#AAAA55": { + "dist": 22.315913604421397, + "closest": { + "url": "http://en.wikipedia.org/wiki/Brass", + "r": 181, + "b": 66, + "name": "Brass", + "g": 166 + }, + "c_value_identifier": "GColorBrassARGB8", + "binary": "0b11101001", + "b": 85, + "literals": [ + { + "id": "define", + "value": "GColorBrass", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(170, 170, 85)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0xAAAA55)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#AAAA55", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11101001}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b10, .g=0b10, .b=0b01}", + "description": "GColor (components)" + } + ], + "name": "Brass", + "g": 170, + "c_identifier": "GColorBrass", + "url": "http://en.wikipedia.org/wiki/Brass", + "html": "#AAAA55", + "r": 170, + "identifier": "Brass" + }, + "#00FFAA": { + "dist": 16.76305461424021, + "closest": { + "url": "http://en.wikipedia.org/wiki/Spring_green_(color)#Medium_spring_green", + "r": 0, + "b": 154, + "name": "Medium spring green", + "g": 250 + }, + "c_value_identifier": "GColorMediumSpringGreenARGB8", + "binary": "0b11001110", + "b": 170, + "literals": [ + { + "id": "define", + "value": "GColorMediumSpringGreen", + "description": "SDK Constant" + }, + { + "id": "rgb", + "value": "GColorFromRGB(0, 255, 170)", + "description": "Code (RGB)" + }, + { + "id": "hex", + "value": "GColorFromHEX(0x00FFAA)", + "description": "Code (Hex)" + }, + { + "id": "html", + "value": "#00FFAA", + "description": "HTML code" + }, + { + "id": "gcolor_argb", + "value": "(GColor){.argb=0b11001110}", + "description": "GColor (argb)" + }, + { + "id": "gcolor_fields", + "value": "(GColor){.a=0b11, .r=0b00, .g=0b11, .b=0b10}", + "description": "GColor (components)" + } + ], + "name": "Medium Spring Green", + "g": 255, + "c_identifier": "GColorMediumSpringGreen", + "url": "http://en.wikipedia.org/wiki/Spring_green_(color)#Medium_spring_green", + "html": "#00FFAA", + "r": 0, + "identifier": "MediumSpringGreen" + } +}; diff --git a/devsite/source/assets/js/tools/color-mapping-sunlight.js b/devsite/source/assets/js/tools/color-mapping-sunlight.js new file mode 100644 index 00000000..14e51238 --- /dev/null +++ b/devsite/source/assets/js/tools/color-mapping-sunlight.js @@ -0,0 +1,82 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var colorMappingSunlight = { + "#000000": "#000000", + "#000055": "#001e41", + "#0000aa": "#004387", + "#0000ff": "#0068ca", + "#005500": "#2b4a2c", + "#005555": "#27514f", + "#0055aa": "#16638d", + "#0055ff": "#007dce", + "#00aa00": "#5e9860", + "#00aa55": "#5c9b72", + "#00aaaa": "#57a5a2", + "#00aaff": "#4cb4db", + "#00ff00": "#8ee391", + "#00ff55": "#8ee69e", + "#00ffaa": "#8aebc0", + "#00ffff": "#84f5f1", + "#550000": "#4a161b", + "#550055": "#482748", + "#5500aa": "#40488a", + "#5500ff": "#2f6bcc", + "#555500": "#564e36", + "#555555": "#545454", + "#5555aa": "#4f6790", + "#5555ff": "#4180d0", + "#55aa00": "#759a64", + "#55aa55": "#759d76", + "#55aaaa": "#71a6a4", + "#55aaff": "#69b5dd", + "#55ff00": "#9ee594", + "#55ff55": "#9de7a0", + "#55ffaa": "#9becc2", + "#55ffff": "#95f6f2", + "#aa0000": "#99353f", + "#aa0055": "#983e5a", + "#aa00aa": "#955694", + "#aa00ff": "#8f74d2", + "#aa5500": "#9d5b4d", + "#aa5555": "#9d6064", + "#aa55aa": "#9a7099", + "#aa55ff": "#9587d5", + "#aaaa00": "#afa072", + "#aaaa55": "#aea382", + "#aaaaaa": "#ababab", + "#ffffff": "#ffffff", + "#aaaaff": "#a7bae2", + "#aaff00": "#c9e89d", + "#aaff55": "#c9eaa7", + "#aaffaa": "#c7f0c8", + "#aaffff": "#c3f9f7", + "#ff0000": "#e35462", + "#ff0055": "#e25874", + "#ff00aa": "#e16aa3", + "#ff00ff": "#de83dc", + "#ff5500": "#e66e6b", + "#ff5555": "#e6727c", + "#ff55aa": "#e37fa7", + "#ff55ff": "#e194df", + "#ffaa00": "#f1aa86", + "#ffaa55": "#f1ad93", + "#ffaaaa": "#efb5b8", + "#ffaaff": "#ecc3eb", + "#ffff00": "#ffeeab", + "#ffff55": "#fff1b5", + "#ffffaa": "#fff6d3" +}; diff --git a/devsite/source/assets/js/tools/color-picker.js b/devsite/source/assets/js/tools/color-picker.js new file mode 100644 index 00000000..c783833f --- /dev/null +++ b/devsite/source/assets/js/tools/color-picker.js @@ -0,0 +1,122 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var titleBackup = document.title; + +function updateFavicon(hex) { + var canvas = document.createElement('canvas'); + if (! canvas.getContext) { + return; + } + var ctx = canvas.getContext('2d'); + var img = document.createElement('img'); + img.onload = function () { + canvas.height = this.height; + canvas.width = this.width; + ctx.drawImage(this, 0, 0); + ctx.fillStyle = hex; + ctx.fillRect(2, 4, 9, 8); + $('#favicon').attr('href', canvas.toDataURL('image/png')); + }; + img.src = '/assets/favicon.png'; +} + +function selectColor($polygon) { + var hexValue = $polygon.data('hex'); + var correctedHexValue = $polygon.attr('fill'); + var name = color_picker_colors[hexValue].name; + $('#color-picker polygon').attr('stroke', '#fff').attr('stroke-width', '1.5'); + $polygon.attr('stroke', '#000').attr('stroke-width', '3'); + $polygon.parent().append($polygon); + + if (color_picker_colors[hexValue].url) { + $('#js-picker-name-no-url').hide(); + $('#js-picker-name').text(name).attr('href', color_picker_colors[hexValue].url).show(); + } + else { + $('#js-picker-name-no-url').show().text(name); + $('#js-picker-name').hide(); + } + $('#js-picker-block').css('background-color', correctedHexValue); + if (hexValue === '#FFFFFF') { + $('#js-picker-block').css('border', '1px solid #000'); + } + else { + $('#js-picker-block').css('border', '0'); + } + $('#js-picker-constant').text(color_picker_colors[hexValue].c_identifier); + $('#js-picker-html').text(correctedHexValue.toUpperCase()); + $('#js-picker-html-uncorrected').text(hexValue.toUpperCase()); + $('#js-picker-rgb').text(color_picker_colors[hexValue].literals[1].value); + $('#js-picker-hex').text(color_picker_colors[hexValue].literals[2].value); + $('#js-picker-sample').html(Handlebars.templates['color-picker-sample-window']({ color_name: color_picker_colors[hexValue].c_identifier })); + document.title = titleBackup.replace('Color Picker Tool', name + ' // Color Picker Tool'); + updateFavicon(hexValue); + + if (window.location.hash !== hexValue) { + window.location = hexValue; + } +} + +function selectUrlColor() { + if (! document.location.hash) { + return; + } + var polygon = $('#color-picker polygon[data-hex="' + document.location.hash + '"]'); + if (polygon && polygon.length) { + selectColor(polygon); + } +} + +function applyColorMap(map) { + $('#color-picker polygon').each(function (index, elem) { + if (map) { + $(elem).attr('fill', map[$(elem).data('hex').toLowerCase()]); + } + else { + $(elem).attr('fill', $(elem).data('hex')); + } + }); +} + +$(function () { + $('#color-picker polygon').on('click', function (event) { + selectColor($(this)); + }); + + $('#color-picker polygon').each(function (index, elem) { + $(elem).attr('data-hex', $(elem).attr('fill')); + }); + + $('.js-btn-colormap').on('click', function (event) { + var type = $(this).data('colormap'); + switch (type) { + case 'none': + applyColorMap(null); + break; + case 'sunlight': + applyColorMap(colorMappingSunlight); + break; + } + selectUrlColor(); + }); + + window.onpopstate = function(event) { + selectUrlColor(); + }; + + selectUrlColor(); +}); diff --git a/devsite/source/assets/js/videos.js b/devsite/source/assets/js/videos.js new file mode 100644 index 00000000..abdd94f6 --- /dev/null +++ b/devsite/source/assets/js/videos.js @@ -0,0 +1,18 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// OSS: removed because PrettyEmbed has no clear license and cannot be included. +// $().prettyEmbed({ useFitVids: true }); \ No newline at end of file diff --git a/devsite/source/assets/other/UX-Design-Guide-v1.pdf b/devsite/source/assets/other/UX-Design-Guide-v1.pdf new file mode 100644 index 00000000..ab884a25 Binary files /dev/null and b/devsite/source/assets/other/UX-Design-Guide-v1.pdf differ diff --git a/devsite/source/assets/other/clock_sequence.pdc b/devsite/source/assets/other/clock_sequence.pdc new file mode 100644 index 00000000..00e5a781 Binary files /dev/null and b/devsite/source/assets/other/clock_sequence.pdc differ diff --git a/devsite/source/assets/other/pdc/pencil-illustrator.pdc b/devsite/source/assets/other/pdc/pencil-illustrator.pdc new file mode 100644 index 00000000..b6b440ba Binary files /dev/null and b/devsite/source/assets/other/pdc/pencil-illustrator.pdc differ diff --git a/devsite/source/assets/other/pdc/pencil-illustrator.svg b/devsite/source/assets/other/pdc/pencil-illustrator.svg new file mode 100644 index 00000000..76fbfa08 --- /dev/null +++ b/devsite/source/assets/other/pdc/pencil-illustrator.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/devsite/source/assets/other/pdc/pencil-inkscape.pdc b/devsite/source/assets/other/pdc/pencil-inkscape.pdc new file mode 100644 index 00000000..84b10550 Binary files /dev/null and b/devsite/source/assets/other/pdc/pencil-inkscape.pdc differ diff --git a/devsite/source/assets/other/pdc/pencil-inkscape.svg b/devsite/source/assets/other/pdc/pencil-inkscape.svg new file mode 100644 index 00000000..ffef07d7 --- /dev/null +++ b/devsite/source/assets/other/pdc/pencil-inkscape.svg @@ -0,0 +1,42 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/devsite/source/assets/other/pebble_colors_64.act b/devsite/source/assets/other/pebble_colors_64.act new file mode 100644 index 00000000..59d7271d Binary files /dev/null and b/devsite/source/assets/other/pebble_colors_64.act differ diff --git a/devsite/source/assets/other/pebble_colors_64.ai b/devsite/source/assets/other/pebble_colors_64.ai new file mode 100644 index 00000000..66c54cfa --- /dev/null +++ b/devsite/source/assets/other/pebble_colors_64.ai @@ -0,0 +1,818 @@ +%PDF-1.5 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + application/vnd.adobe.illustrator + + + pebble_colors_64 + + + Adobe Illustrator CC 2015 (Macintosh) + 2015-08-14T12:08:32-07:00 + 2015-08-14T12:08:32-07:00 + 2015-08-14T12:08:32-07:00 + + + + 256 + 156 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAnAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A6ZqmuTWUb6o/EoAIyKEn cgdNs5TUdl5JHiPDxfF80x9oZZT44VyrdZ5O87atNtfGEW3JmdIEateIA+0fHNDnwnHiIh9N9e99 J7CxTyQo1xWf0IT8or382YddFj5hNkNJmubiR1gCl+Hon06Eb15qK53+m1UMg9LnZtFkxjiPJmnm fzcmn3SadqEwtImlAku2BYFSOSLwjBbcjr9+abtLPkl+6vh33l5dNhu8fqe0pDONOdt9z5dNkfH5 20SSyjMN4Li4VOeyOnKgB25KOvIZRr+0jjxYwDxZLFy5AedPV6HActDvSTQJvM0+v/pCx1my/wAK alcpNBYmGR7wMsRW6iaVvhoZgCpAG349Hg1EckBIdW3UaHJinKMh9P3dHoeXuG7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXzR+bvnFLHVHuFZVuI4YoyCjFeDM TXbvU52Om7JGTFxefe+f9nzlqJcYrlX2vJ7fzhpl5fSanez1ZYvSrEjgUDAjYjxOcL2h2SYHf6Pf vb6J7P6OUJeIee4+567+TWtzah5/08qU9ASXKEBWDVFtIe+3hlPZ2g4YGY5CX6n0rt2MD2dIi7qP +6i9N86+UhrF02oySfVLK1LySXJHqUVU+P4OSsS9ABm2ho8c9jvM9P2vz9n02WWrOaI4cG9y58h3 c9+VPHfLXnTyL5X0XzPFfia48wXsVxbWelATmNUHArH9YHJaySTcq0qu4y7W+zvh8o2JB6vsGc8g iQethmX/ADjlFDdXGoX1wWhulUG3s25N8D0Dy89gd148SPfNbj0JwgExp772kz5fy2KJG3WW3TkK /T1e65a8S7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXzp5r /JnztfavLeWlnLKrxIgH1m3Vag77M4Nad87rD7RYseERBjxcX82XJwNNpfBOynp/5FeaIFa1e0nF s3xGQ3NsXDbdKN7eGYmXtjCTwAjg76N/j4PY6PXYYECR29x/Uzn8rfyx1Ly3qH1u+SVGWeVkDSxO vBouC7IWPUnNFq/AArHIy/X8m/tbtTFkxHHjNggdDzv4PR9d0mLVtJutPdvT9eNljmpyMclPgkC1 FeDUND1zE02c4sgmN6Pz8vi8jnwjJAxPV47df846RJHKsFz6rQR+pbsURRNL148S/wAG5bck9R4Z 08/aTxZAzG3Ud3n5t/Z0jp5DqB+LZ55I/LxfL6W9y89boRr6kYQDgSPjQMDuOgrTtmj1muGXarrY Hy6O21na88+PgkOXX7ma5rXTuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kux V2KuxV2KuxV2KrDPCJhCW/ekVC+2Q4xfD1YeJHi4b3QGn65bXl1JAhWikiNgSeVK17bZq9J2vjzZ pYuvTzaMGqjkJARc92kKyF9hGjPU16KCT+rMoa2Iz+EdiRY8/wAbuZw7WxLyF+a/lPzhFGmn38U1 9L6jLbxrKPgjNCeTqBmaxZjLPDEUEjcS54r7nISmI1fVhPJGNWeaAl1y0j1IWXIEiglbf4WYVUUp 3zWZ+1sePOMR+J7j0aDq4+JwDpz8kfJKEZQ2wbavvmVqdZHDKIl9Muvm5kY2xH/lanlG11nUdF1b ULew1XT5Y1axaTlMY56ek/ACvxBgxp0BBOxGZjFmOKuxV2KuxV2KuxV2KuxVAa/cfVtC1G5/3xaz Sbb/AGI2bv8ALFWDfkB5pPmf8vItVII5XU8e6hPsEDoC2KvSMVdirsVdirsVdirsVdirsVdirsVd irsVdirFfPzLDp6XKO8dwnIqU2JUCrCo3r4Zru0NP4gFGpPNe0kuHHExPDO9vx9zyG3/ADf1Dy76 AngieB3kEdwsDPIBXb1GLih+PNXg0k8BBjCNjrXq+a9iZRGEYmvf1+P45J/rf5g6pc+X9P1meJYo NUZxp5APF1jYK/qKHYiu+a7Lg1Hj+NL+Llv3d3d5vpHY2PDkPDLr+nkmv5X3fke4ubBfLGg2thcJ bM97NDbrBxNQkvpUb7LSHpTOq0+pMwB1rdj2p2GdIJGZ24qjvfPcXt3PQPMqodHnZiysorGy9Q9f h38CeuT1eLjxkci8X2uQNPInpyrv6fDveMyfmPqGgXOoXEkMM9rDMrTSyxvNNVDSTjQjarbH6a5o celnjNmMZkm/ULLoOxMsogynvxHfr8k/t/zIvtb8uanrACHT9NKQzTxLIjcnIHwqTvXkvyzD7Rwa jPMZT9OPav2d/wCx9G7N8MzAPXko+VPMXlPzLc27PollceapJEgur+azQtIlsA0RMz8pCVWm7eHy zodHqZGEYy+p2/aXYPgmWW/3JjxDfff4fjZ7DmyeUdirsVdirsVdirsVdiqheXMdvCWaRY2YFYi5 oC9NhvgJpo1OeOONkgE8r71PSv0l9UH6R4fWeRrw6U7dMQ09n+P4f7+vEvokWo6zq175hsLPy5d2 k9vZzMvmOAsrTRpyUKAOoOz4vS4NLix4Jz1EZiUo/uj0J3/YyjC6d2KuxV2KuxV2KqF/eR2Vjc3k n93bRPM+4HwxqWO5+WKqGh6vb6xpcGo2+0M/LjuG+w5Q7jbquKo7FXYq7FXYq7FUn8w2M+oxrZxg U3c8qgEjpuBUZfiEKuYsPP8Abmly6oDDCu/fl5bvn7857LR/Ly6dYiIyhpVl1e2imq0oMopFFyUl WVByO/fOk7O7NOqEpAbdPLbn8/udfpNLLGY4ZC8nORHLmaHyASXzz+dlz5g8u2Hlny1ptzo1tZOk N0LhYJxLbxBfTCsULKeSA1Wle+aTXdj5oEh9K7C0nFMWNh+P2PWf+cd5bVPLU1s6uNTZhc3LycQH ElQvpKAGCqqqGrX4sw56aWIAS5uz9qo5DOEpEGHDQrp7/Pn8Hp2rRzTWb20Q3nBjZqVAU7GvzGOI Rv1cnge1ITniOOHOe3w6vIvzQ0TSfL+gXs7SJHq13xMNzzPCKNNqujbHn9ge/wBx3fZukGpmIxHp j07z+N3msXZ0tFAxl65zl6a/hj5/j4vNovzvez/L1vJflzQZ9HupoY4k1OaVZ15zOTdsYnQ7MoYd fh5dqCse0Ow8uOR8/J73sfTXKIrl+P2sz/5x1ZYtYvJdTSmoXtvxs5z+7UIGBeLhU1Z+IcdwB75q Z6CeGIMuvSuj2XtBiyDRwiD6Iy3Fb9fVfdufm9/yh4Z2KuxV2KuxV2KuxV2KoPV7SG5sZRIByjRn jc1+Fwpo1B1pkJgVu4mswY8kDxi+HdK/Ir3z6Ahvb86jP6j1uWUqSK7Ch8Mr0+UTjYNuP2RqPFwc XFx7ndjPmjSrbR/PPl660m9Oky6xeSHVoUV3+vUeIhXNSEpzb/gszMeIkSI6PaaXJl1OlyCY444Y +nkOHY8u/l9j0bKnnnYq7FXYq7FXYqlPm/TL/VfKet6Xp0kcWoX9hdWtnLNURLNNCyRs9A54hmFa Kdu2KpR+VHljXvK/kDS9C1+e3udWs/X+sz2hYwt6tzJKnAukTbI6g/CN8VZbirsVdirsVdirsVeX /mD+T8vmTWrnWFu0K+kv1eyeMvSZeILglgPiVadM6bsvt4afGMdddzfT5ODLRnxTkB/tYlP+Rd9Z tp01uWmmupKXirCv+jgEBWJD/EFBqMtHasJxyRJo84y6nyd/2frDigR/F329U8p+SB5fYMtysjCo YrHwLKVpRjyP7XxZo9VrRl6b0N23XdqHURESDyHM3v3spzAdSxL8wfy8tPOVtawTXP1QQSBpiIxJ 6sQNfTYEr+1uPDwzbdl9qy0kiQOKx31R73E1OkGUg3RH4p5tc/kG8dnc3cXKW8tmX0LUpF++Aakj Bq/DVd6ddqZuP5djPJ6/pkK6+l2nZ+oOGW/9jOvKP5aLpENrcSXPG8WNWeMRrWOQ7uocMa0+zUZp tT2gJ+muIC6J7ujss/bUsmPgIse/mOjPM1TpHYq7FXYq7FXYq7FXYqxPz3qOraXaLd2vqywu/GRI gfgQISzOaGi7bnNdrIZecZUHU9oY8x3hIgfsUPy21yTzD5N+uQTD1HlljWVSrUKkfybZPskyGMce +7PsLijhHierc9KYbqnmvWbT8x9H0vUGmSGbUPq9q0qgJIA6KxjLgGm4+znWYzhOCXpHFwvd4p4D pp1ECXD3+97LmheXdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVQ+opBJZTwzBWjmjeMxs3EMGUgrUEHf2OVZsgjE2R8WGQ1EpP5F8u2Pl/QE06y08aZA sjuLVZJJQC53blK8rb/62UaA5Dj/AHkeCXd+LcfRCfh+uPCe78Wk3nHSrXWPNvle4h0yDVpdGupH mufrDq9gWaL4/TjljDFvT6OrfZ6ZvtNigMUzkJga9O31c/L3O2wUISs8NjbzZzmtcR2KuxV2KuxV 2KuxV2KuxV2KuxV2KuxVb6kfMR8x6h3CVFafLBxC66seMXV7oPTtYtb3kFZVcMVCcwWNBWoGa7Rd p485IsRlfKxZ+DTg1McnJXuLr0o5WCF3jRnWMdW4itB88yBq4+L4R2l08/cHK4drSTy/5+8q63xh ttUsv0kZJIZNNS6hkuEliYho2jDcwwC1IpXMpimurXz2NsLmlYkYeqKVahNNsxtVmOKPF0HNw9bq Dhhx/wAI5setPMD6jqzzwSxRQW8iwxpM4RmD1rVS29aGlM586jPm1HiY+HggeH1bbHn1ddo9edTl lKP0xNBO7/WLO3j+sLIJUQcmMZDCh6dD3y7tXtIYs+OMN5H5V+t6bT4DkNBgZufzXtvMl+2hWumT +U757aewv7udvrHOSn1iN0DKBxNVoB0pSp2HQY8glESHItU8UoyMSNw9PybW7FXYq7FXYq7FXYq7 FUJqlotzZyCvGRFZonNaK/E0JpXp8sxNZp4ZIev+Hdry4xIboHym2sNo6nV7uO+vOb1nhXipWuwp wi6f6uV9m6jxsXFxce/MCv0D7mOCRMdzbEfNUUugeevLkmj6pDpY1+8kGsWs6SyveqrxEJCRDOsZ HqN1eP7Q38Oq0/HqNPPijxDFHY7Dh589xfLzc0ZLjR6cno+aRodirsVdirsVdirsVdirsVdirsVd irsVY350nmsbRL+2n9CZKqx2JK06gHai981vaEJgCUDUvm8/29llhgMkDU+Xmf7Hneh/mv5f0poo 72zVLrnKFvZblY1lIJHJVYcfskDbNLo8WLBMSOMnIL9VnrfTlyR2HKPAAR67Pq7/AIfYnWqfmXWy h1eFDDbtJxtyHVlmUEBhG5FG79Mw8moynV+MRKuUduQ9/nu+gdl6KGa4kh3k3yt+VSa5a635e02L 9Makz6ndSw3s1wYZ5x++LoZXQHk/EjiAD2GdjiziYFdRfucPVdl5MHHxgjhlQsEcXu9/Nn2vhjo9 0quE5IQa/tA7FBXuw2y6WMzHCOrzXbEq0s962+fl8eT588zaLq9umqalGzWgiZHXUXj5RR1IEaNU 8ainDfv2zF/kESrvPTr97ynYuUcBMRXCfkn2gadfw/lRqnmnX7pNLupwJ7GW5QxCGIMFQMpoG9bl SMkftKc1+o7Dh6uL672O/R9J7K1suKJAPq6fj5rPyX1HWdfvW0xroXOh6czXT8FUxAyUCxB03DE1 YVPTlmTpNOYxEb9Iew7cngjgGYCs2QcPPfb+Ku79Ye8Zsng3Yq7FXYq7FXYq7FXYqk/mTXYtGt0m uAzRTP6YCBW6qTvyI22zSdq5NTAXAx4T3+73NkBE81HRNVGr+X3utICwuzMkXJUUBlI6heQy32dk TgByAfUfpAH2CmOSAiajs8W1X8xfMbfm1oOhaj9VuUi1b6orSWlvI8dZEVjDKyc4yaDdSD0z0nBg 035PJKHHGXB/OIB2PMXv8XDlkmJAXtb6Gd0ReTsFXxJoM4xylP63a/7+T/gh/XFXfW7X/fyf8EP6 4q763a/7+T/gh/XFXfW7X/fyf8EP64q763a/7+T/AIIf1xV31u1/38n/AAQ/rirvrdr/AL+T/gh/ XFXfW7X/AH8n/BD+uKu+t2v+/k/4If1xV31u1/38n/BD+uKu+t2v+/k/4If1xVVxVj3nS2jutOEH Au7ct1qaKRRgQP5hlkNP4nWnmPac3iAiCZ89unw83z9+YnkiGwtNJjupoUm1CcpaTuWT6qpkVS8w J+FGI+1v0zMj2TjymVR+kee/ucTs3jGOEroyJodRvW4ZJ+Zev+R9E/LvQfL9g8GtX0LJBDLp90Jx bzDi88rqrsSslX+0Nq5p9TowIiJiduXN9G7GGQ5BR9/4+1kn/OPOmWn+G5NXIAvJneNIyxEkUBYH i6VoObpyU06YcOERF8PC7n2r108mSMKIjQP9aXeD7i9UurVLlVRz8ANWXqD88yoT4eTxOq0ozACX 093e8o/Pm4k07QI7O1tk9C+Y8Ya8VmuKj4XHSiD4xXv3zp/ZzFHNkJmfpHyHl9zptfo4YzGOMcEZ Ss8PU+b58ktfNlxF+jLvWdQvdMkEMb2NxdO8BW3NYV9Njx4VOy02pmz1/ZmCcpcI5CzsHsuxMceK +kR8nsv5I2r6Ber6BP1O5jKXiV+GoIpJxULVlb4RX9muc7q9DCOMeHy7+W71XbPh59LGvqG4NfMe 79Qe95pHhXYq7FXYq7FXYq7FXYqh9RjjlsLiCTdZonjKg0J5KRQHxyvKaifcyhGykX5eeXrXQPLi adbQTW0SyyOIrhub1Y7mvFNvozE7NMji9UTA3yLdqYRjOoniHekH5jeXINX87eRr86dc376PeSyi 5tpOMdqWeA8p14ScgfT2+Jehzo9FCJw5TKYia2B/i58vx1ddmySE4gC75+TOdW/3hf5j9eaqRoW5 EpACyxq6vIrdVLGjSHimxNT9Ga3tDtGODFxjmeX6XDz62EIgj+I0Fe0aKXgXJCH7RHj/ALeSwa3x dMMsNzX29f0uVhlxgFjf5i+eNJ8kaZBqOpyenbXF2lojcHlJZ0dxsm/SM75fo9VHPjE4/gtso0U3 0nX9C121N7oszz2YYxl5EZDzUAkUYKe4zKYoxmCgkmgGV5csccTKRqIYykIiyujUyEBNy3THxY8H Hfp5som+SxpEW7itCf8ASJiRHH3JUVPtk4yBFjkq90ZGKMKMpoR74VaxV2KuxVlOKrDDGZRLT4wK A+2S4jVNRwx4+P8AifOv/ORVhq+oeZINNY+pbSwpNsp4+gHbjGWVahhIjt16Eb53vsx4P5cmQ618 dt+fdTqM+IfmxKQvbb+rvY+95Xpvkb0rwtJASjTFhx9Tqf7zr/N2ynV4sGQSyAekHle/3vedh5Me McdbA/j7H0Z+TOmzaSJIVHC2uUCenQk84l5IxLCtOBIznu08UbuP00KDne0WeOaESP4Rt7j0eq5p 3kVOa2t5+HrRJL6bB05qG4sOjCvQ++SjMx5GkEA80luPJWhz6bd6eyMsN4ys7KVDpwPJQh47AZmQ 7QyRmJ9Y/jdnikYGwmljpljZWsNtBEojgjSJCQC3GMfDU9z3zFyZZTkSTzN/NMskj1RWVsHYq7FX Yq7FXYq7FXYqluv6dFe6e/NuLwK0sLVCqHVTTkT28cxtVp45Y79N3J0ueWOXp67IHyNqOr6hoKXG rT21zeGR1MtoytFxB2FV2r446TJKcLkQT5MtbgGLJw0Y+/mk+tWeq6P5t0mbRbiCK31i5f8ATUd0 683RWQqLdSAa/G1fozewyeNhIkCfDG1fp+ToskRhygggeId76+75st1b/eJ/mP15qyHZEW8e80a7 caTqluYFE7GQlElqVKUPIkAj7Jzms2kMcnF9UQeR5fJ4KEz+c29UIy2v7dvJQ8v/AJuDVdSh0y1h ia/kleH0gjqvJKlviYgbKK5DVHNlw+DCIjxfzdut7dHu9DljMAnaubT+cfLt5rM2k69awXln6jsk dxAZwtxECDxVwyigZhXJdj5J4fRMUPnuHss3Y/5nAJ4d5Rq+mxZ3oYtDp0clpbxW1vN+8RIUEakM B8XEdznRQPFG+95jW6cY5mAPLY+9I/MPmVdPuoor0tHbiYKxiBZzyHw96dOuc1rfEnIYpmoCW9c6 6PCZtdk/MDTyPpjLeuddEZpnnXRHWEo8yh2oGZdwSSN+JOCWrjg0ZhjMpy6cXn+p7HRQEgK5JN5t W2833EWlRa1d6DeWk8d9b3+nv6MzRxcg8TOezA8qeIBNaUN3YGsMsfBI78/1u71nZU4YxkA60fey Hytpn6O0xoDfXOos80kr3F23KQlzUiu+3f51zooysW6rNhljlwlOMk1OxV2KspxV2KqV1bRXNvJb y1McqlHpsaHJQmYkEdFKVXflHR7qPT45Vcrpknq2tHIoxYN8XjuMyoa7JEyI/jFFnjmYAgdU6zDY OxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsE/MrzXrPl619VUh+oXD+iC6lywMZLCisD2OY2eUgNuTu+ ysGnyEcZlxDu96n+TGtHWPISXsMEMLG4mRY4A6x1UjekjM344NDARhVUL6L7UR4dQfDJl6R9VX9l N33mXS7vzbYWF4iPd2l16VuSklVkZlDcSp49VHXN7iIjilwkjiG/m+a59TqJavHGcYGIn52OXmzP VRWyYe6/rzWAPXSkIgk8g848weWY76f17lngihJleROJChRU7bkig7ZnQxYxsN5no8INHnnqjlMe HDI2SOQA8vOt3lHlLzf5C8u+e9YvvMWo/VLpWFtp9u1vNMjLJxKzL6cb8HKIP+COZms7InCEZCPv 5bH8W9H2WRImQNg7Dz3SL/Fw80+cpJJkjsNImuZAt3bK6n6shKLN6Z+Lk/VhSte2aGOjyHJQj9z6 z2JDLixGUBxz4dok8/7H03HGkaKkahEUUVVFAB4ADLAHgJzJJJLEPNPlaXVLuJkIhMTlleTlxVCP 3lSPGlRl35HGQTI35dbL5+ceWWtuESMcpHcjYVzJPn+l5n5PHk3TfPt3faxqVvpsNo4Npb3M/ptN dzPx9eMMd4yFPfw6DLdT2H4fDMRP27PUdl5TK6IMfvKU3nmHSte8/wByukq1rC97IkeoTNztmXkV kn5A7I56bdD92oHZ/wC8NRO7612FmyY8EuIGceHkPqscq93V9HWltFa20VtCCIoVCICamgFOuZcY gCg8BmyyyTM5c5G1XC1uxV2KspxVTuLiK3haaU0jXqRv1NBlWfNHFAzlyDGcxEWeS5ZUaJZVNUYB lPsemJzxEOO/TV/NlE8XJDzapZQ3tvZO5FxdBmhWhIIUVNT0GTjISAI5FUVkldirsVad1RSzGgHU 5CeSMBcjSQFKzvbW8hE9rIJYiSA69Kjrk0K2KqN1dwWsYkmbipYKDQnc/LKNRqYYY8UzQumGTIIC yqswUVPTJ5cscYuXJsAtDDU7M6kdODk3ax+qyUNAtQOvTvliEVirsVdirsVQmraXaapp89ldIGjn jePkVVivNSvJeQNCK4CLZQmYmwkv5feRrHyT5dXQ7K6nvIVlkm9e6KmQmQioJUKKCmCMeEU26jOc suI83eYfIema3ruiazJNJbXGiTtcRpBwVZmdkak1QSQPS/E5YJkCnByaeMpCXcnWq/7xP81/XkW4 gEUWJeYILi40a8jg5GcxMY1XqzL8QT/ZU45laLII5oylyv8AB+HNx9Rh4sRhHbbp+Or5zvfyu166 uGkutMuPrd1K0vxBGdd9gpIrRQNs7/V9q4JY6iYSiKBP6WvsuJxgCUa/R+ObIPJ3kWdXaJbAu0BK zNxQshrTgSB3pXNLqp4oSMdhGQ2lW/wfRdL2rDF6bAFc3uenxSQ2kUUhZmiAXm5qzUHUnOVygCXp LyGtMJZCY1Uvl7lmr2891ptzDC5WZ0IQg0JI341/yumT02URyiUtxf4+TrcuC8RhH0vnvVPyp8y6 jePcXtgfrl3IWhjkaFmjUsQioa9I+vbO81HammliqHDIRoE0fm0dl4vBiAY1umvkz8vrpZ54BZhn taR3Cn0qxtyK8dqVqQWNNs1Wrlhx3yEZj0y6976Ppe18eEcN0K2O/wCN3udjHLHaRxSszvGoUuxq zUFKn3OcplERL0nZ5DVGByEw5H7FfK3HdirsVZQ6hkZT0YEGnvgIsUgixTzHzL5nOl6hbWOpcpbG G4FRagFyCKggsQKL+192cvrMc5SGPJ/dxN+nm8XPV5PzIwS3hCV7fr+/4phpP5maA9rDZ2sNz6sj emqv6bsHLfECFau1R274J6kYdJ4WES4uQ4t+Z3/Y9pooxlEVySXXp/LvnKaPQdZ1G50o6fdR6pZX VnMltJW2qTA7sWJ5K3IqPAHtlvYGqrHwS9/6/wBj0Gq7Hn4YyQHFvRrf48uTOfIuk6fpfl+O1sJL mWAyyy+pdsHlZpGLMSwAB3zo4ysW6bPhOOXCWQZJpYXq3n+z0XUJLaRkuub0QmXhSm7gDi32a75o 8/aZwzIrj+P7C81l7aOLLKEB4u/Q8vsPJ2nec7K5ExmmSlwC8UbSV47n4RtvUHOd1HbGYCYMJHj3 jv8ATz5bfqd92X4mWVyBHEwXyb5Y8zN5vu9Sj88vHpT6ik8PloF+MUIl5mAfvONJAeP2c63s7VjL jFn1D7fP4u41fZ2TEbomPO6e03UXq28kdaclIB6b5nZI8USHVZocUCHl2reco7TV4LfV1kmigSaO IWgWvJejEsd1bYA5yeqxSyyrNfDEEenveO0utnl1HDk+nHY27/0j9ieWP5laFdwQ2lnFOWZKrUI5 4Iu9eLHoQRku0dYY6UYsIN0Bv/NA+97rRYxPhrqxjVbfyt56vLWTVtWvdM1Hy20slv8Ao+5S1Fwl wojMZryaTgw49upH7RzZdj6sHFwn+H7nca3sbJHhlAXGXvNe/Z6d5YsLXT/L1hZWpla3t4VSIzkG QgfzEUGboGxbpckOCRj3JnhYOxV2KtO6IpZ2CqO52xVCnVLIGnOvyBxVWhureb+7cMfDofuOKqOq /wC8T/MfrxVIcVWNDE0iyMgMifZYjcfLJCcgKB2K0p21jZ2rSvbwpE0zcpSgALNvuafPJZM05gCR JrkyMieavlbF2KtFELBioLL9kkbj5YRI8lWpDFGzsiKrSHk5UAFj4mnXCZE8zySSvyKHYq7FXYqy kkAEnYDcnFBNCy8z8zeTTrOorc0+p2sbs7XDKXRIlFZamo+1Sv4ZkDR4qN+onp5l4KOnzS1fHGJh hJO/MRA5m/P9Pk8t8gat+Xfl38x9R1PzHqkOntbSCPS7Vklb1J7k8frQaMMFBRTVXFBzqaUzJ1XY ZxiMxH9n2vTdlZeK6NxPLzSb/Emk+afzDaXT7caTplzfSBbzmZl9Fjwa44sFb981Tw/ZrTNKOzjx mofa+t9i5MuLTy24xw/Ty3Hn5fa+srS0t7O1itbZPTt4FEcSAk0VRQCpqcyAABQfP8mSU5GUjciq 4WDyn80dBtEc3FvGodD67FV5sAf7xSSdufXMcdljLLaPPyeE7V4NLqxwUOPf4+fveWR2fmzUda04 aNezfoy/ZYTdRQc7a2K0VmkmTkFUA/FmPqewYjY1Y7w9N2Zr8olE1KtvgjNA1bUG85ppWj6xHqUt xOsH1yy9OVCVlC/WeUfIlFB37ZjaXReGaid31vT5ceTSSlmj9AscR5ij6R57fa+mHolqfV+OiUf/ ACtqH783UY3s+baicRGRPJ5X5h8lfWbmbVZ2FhawRO7SyIXUpX93GGruQ23vmTHQ4z6R65E7B4LR afNHNKcwceGibPIC9o31ee/lJ5k/LDyreatqGvalDaau0k1tp+lFZWaCJEaSYc4g8Z9SvEVG3Gg6 0y3V9inDIGMenN7DsmUpwG/M7JP+Xd5p/mfzzZhrUaLptzN8Vs0hnHptLyjtQ/wsfVAoHzTY+zqJ 9Gw831XDqM2LQzNcdCx04dj6v015PrJVCqFUUUCgHsMynzsm28UOxVbJIscbOxoqipxVhnmPXoRa yyyXccBWnCrqOI5AHqc1faeoMcZjA1I9fi6TtLtOEIGMJDj8iL5pfovmfTrnZ7iMoh4mRpUoaDrW uczPtfUY8co+qRP8Xd9nVu7G1c820oy9593NFP5r0sXKIXitqEhZGlUcgP2hWm2bzsnX8cBxy9W3 M+T1s+yZmHFG5e4FkQ1GO90kurrJ9kh1IKspOzAjN6DbqZQMTRFEJFqmpR2ESyOAQ1erBegr3zA1 +pOIAjvdP2trvy8YnvKV6f5w06/K8JYo+5/fI21ae2c9ru3MshUISiR3f2Nei7SnqaqEofb19yYX 3mnSYCUrF6dRxuRKvAkj7IPT8cu7G7UnIfvZb0eZ83s8XZUssbj91oy2urW6j9S2mSeOtOcbB1qO 1VJzqoyB5Oqy4Z4zUwYnzFKGq3T2tqJEpXkBuK9QcxdbCcoVDnboe3dV4ODi/pD9LAtX8z3+jqLO BYWuXAmUSKxXiSV6qV/lzkNR2RwRN/R+PJ5fs3VZ/pHD9qv5u8zeb7/yRdw6JDbza+JYltIpBxiM aspfkWZRXjXvkdHkOLWQM9oiJ/S+p4dJPJiuKcaBr/nGfQp5/MsNvDcWvppbiAAgqaA8qM+9c67W Zbwkx8vvDoe3BLTaaUpbcv8AdBgUvne40ua8v7toxawzuGKozNRm4LtXxYZy+Xsrjonru8t2Rixc XiSvnfzDI/Mvn8T+S5LLy0vreYdTghfT4rhKRM/JGYMSygfDXvlXZM54c3qrw4k+/k+gaXQcWO8f XvTb8uda88ahpf1fzVBbQS2UNvHALan2uLCXkQ71pwWmdhptdizkiBvh582OfSzxVxirerkAih3B zLcci0t1/TZL7Q7yytiY5ZIz6PE8KuPiVSeysRRvbMjS5hDLGUuQLj5sAljMI7PmvUPyO82Xd+Bf 26JqOoSeqqGeNuI5HZG+IARAVA5bDpXO61HbenliqFGMaBPD9vvLT2biOGIBACbeQvyy1N5p4IrZ T9RcRXRLRhkfkRxBNK8aFtu+avXZsOOx/BkjcT1fQcPbMMMeHoRtsfxu+g9GgurfTYLe5JaWFeHJ m5sQuwLN3Ocrl4eL08nlNVOMshlHkUblbjsJ/NMQWnly6vzGasBG8gJ+GoO5rsAQOPzObnsYGeUQ /H46vN9tdmQyEZK9Vjff4HufLlj5x/MbSbVrTRNXurHRp5Jnj01beN+KylqAs4ZthTvvnUdqdjYi RXDx8ve7zsfDtEXf6f7ebIvyl0q70DXLHVLMvziMatCiEyPGXHqpRuVPV7Zy8+yYY4yltY2rex73 03wsWTTSxy4Tt38jX1PrUUkjBZdmAJU+/Y5p+T51OANg7hIPPej6hqflm5ttMXnepR7e3LiNJCu3 ByduNDX5gZn9mZ4Y84lP6ep7vNwtbpjkxGMfl+h86S/kR5le5+qzwLJqBVppi80byGq8i/OlDX7H Wv0b52Wq7awZQCAPDvf0/DZyezB4RjGQADKfy7/LnUri2ju7aEC2jkCCdXRHDKKsRWh+HZVOabX5 cUOLFLbqK6+97gdtwhAwl8NufkXv1p64tohOKTBRzoa7/POYnV7cnjslcR4eSrkWDsVQWrsRZkD9 pgD+vFXk/wCY/wBW+rG3hhY+pGC8i1Kglx139slPssZ6Ajv8Xz7tPTY45RKM4bXsDbzCV9Wfylq/ mLTb1raHQX9CbSgivPdO5VA0QP8ALyrms1HYfBcTtfveq7IlIxBB2H6ku0vzPqGuXmkWQvgk92oj kabgBbuyAlJqAlCp65g6XsoY8mwvfzfXOyNXGGnMpRM9o7Dn+19HeW7eW00G0tZJkuHjt0R5ozVH Ip8SnbbN8I0KeH7RyjJqJyAMQZHY8x70k85/W1ijAhkuY5WZVEak+mCu7NQdMH5Pxrvo+fe0Wnym cTKY8Pi2Hc8psNO1G8v9S0ywvxpVxotsb2eWVAfrCBQ/oorH7Tc8wsvYfAOKqB97suyCSAIS5fbu xSx89ahf6PCt3eNaTh2JtLlY1lABYB+BoQDXNX/I8Izuw+v+yc4yNS5AHrXUPqDTtMsdNtzb2UXp Qli5XkzfEaAmrEntnQQgIig8VqdVkzy4shuXw/QqXVyLaL1CAd6UIr1zM0mPinXk817Q6rwdOJf0 h+l8w+YfzHGnTyxwsjRtGJecqSEgs/E/ZI2zou1ewQMZ59Oo7/c6ns7BkMeXVJtI83WNi0qLcyNd NIzqjBytCADuPkc851nZ8hfHsenufX/ZXTDDIRnyJJPX+F7r+W17fXug6qzqvMTR+mN6UND3JzcY 9DKERH+c6r/gmQGPTg4+fCP93F5j+bmuLp3PS9OYyz33KSUSVBEiSglVYcQB8PfOrxdkcWME9w7n y/2fw5c9g9/6GIaP5h8x2BtvVii9A8mkYlmYKEHSj/wzlO1eyvC4q/is9H2vsTs/wjC/p9Nvoj8p fMv6a02dfh/0aK3FQGBq4eteRNfs5i6HGYxo+TX7V6QYpQkOU+L/AHv63s2ZryLsVdQVBpuOhxVo KoJIABY1NO56Y2reKuxVB6xo+naxps+m6lD69lcgLNFyZOQDBh8SFWG47HLsGeeGYnA1IcmGTHGc TGXIsK/5Ut5PGoPItggsREUgg9e55K/ECpPP5982ku280gCT+8B50P1J08fCkDDal/lr8r4NIsUZ /SbV45leO6R5eIRKcF4naoI/lyOp7SE8hMQRCQ3Hm7aPaUrI/gkKI2Z8teIruabnNOXVl2KuoK1p v0rirSqqiigAbmg23O5xJVvFXYq7FVC+gM1s6D7XVfmMVfPX5nw+cIb1bK2u5fSuI+dYYuaikhID Hj1+HO27F1mmJFw38zXR5efs/DxOOQHXveTxW/niCSfToryVbK6kla7BtVYMwHwkuyll3HY75ldq dm4c0wQYj48nquyuzoxofw/sZ3+XX5dwrqtpd3URk5SepISsiAu6faNDTrvmk1Whw4YkxrbuPN72 eTHptMeEi+EVvyfQdjYLb6WQq8Y4lWOMb02I6VznJkEkh4XPl8SZl3lIvNurLpmjXFx9Xe4YRSlP T6qVQkHNl2Th8TKBYG4+90HbWnjlgAR3/B8w6lrfm+61241nT7iazGocIpo3t0kPpqiowJZSBXhW ozse1Oy8QxiNxO/f727sHs2GPHECQ/Ekx8s+QZtVuUuLoi4l9Jlb4GDkK+3wrQDOYn2NjBufD7rN vrHZ2PDpofw/N9NaRb6lBalNQnFxcciRIooONBQdFzRyIvZ4bW5MU53ijwxrko+YbKe8sBDDy5iQ N8JANAD45mdn5o48ly5U6rWaYZocJ73hMP5O+Y5WaG5tLsxEV9VpYWaoI2BNc7PV9vYMkuG4cPfR b+z4Rx7EUEzuPyi1W5t5HNrPFOpAiCtFQrX/AD75pcuswakVkMYe4Pa6HtDTj65CPwL1HQdFudA0 Nks4jcXfGL93LT4ioCt049qnrmoHh+KAZenfd5n2j1J1Uaj6q/WO95XF+VvmW9124ub60nijuLma UyF42VVkJYcRUmngO2djqe2NMcHBGUb4QORvZ0GhhkhEgxpMovy11eOUQmxkkhqV9ZjEWC0oCvhX NVqe0sE+GPpIrc1u972d2xHEADw9Od9HpHlHy7HotoyqHV5UiEgfiaFAf5QP5s53VcHF6DY3cDtr tAamQqqiZcvOnpGYrpHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUDe6WkzGSM8JD1HY4q xbUvy8sL+Uvc2xkbfdZSo+I1PcZmjtDKIcF+n3NmLLLH9Kd6b5dFtbw25+C3gRY44weR4oAoFT7D MMmzbCUiTZR+pIqaeyIKKOIAHzwIY9dW0N1azWsw5Qzo0Uig0qrgqRUexyeOZhISHMG2MoiQIPIs LP5S+WxJKEgIgCH6spmlqslBQt7Vrm7y9u5ckY8R9QlZ2DXp8EcRHDyCP8p+RbPRoRJOqtfDknON 3KcGNaUbj+rMftDtI5cpnDke+na5dfMysFlWalwHYq7FXYq7FXYq7FXYqynFXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqsl9HgfW48O/OlPxxVQ/3F/8AFH/CYq7/AHF/8Uf8Jirv 9xf/ABR/wmKu/wBxf/FH/CYq7/cX/wAUf8Jirv8AcX/xR/wmKu/3F/8AFH/CYq7/AHF/8Uf8Jirv 9xf/ABR/wmKu/wBxf/FH/CYq7/cX/wAUf8Jir//Z + + + + default + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:fd130abb-a700-4d28-8052-792488dea804 + uuid:7d361e5e-1b4e-fc4b-86a5-1baac287f738 + + xmp.iid:1c697c4e-d44e-41b1-9338-0739fb8c0a57 + xmp.did:1c697c4e-d44e-41b1-9338-0739fb8c0a57 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:2fcd6be9-3216-41ce-991a-ec551c895ede + 2015-07-02T11:28:35-07:00 + Adobe Illustrator CC 2015 (Macintosh) + / + + + saved + xmp.iid:fd130abb-a700-4d28-8052-792488dea804 + 2015-08-14T12:08:32-07:00 + Adobe Illustrator CC 2015 (Macintosh) + / + + + + Web + Swatches + 1 + False + False + + 960.000000 + 560.000000 + Pixels + + + + + Default Swatch Group + 0 + + + Pebble + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=85 G=85 B=85 + RGB + PROCESS + 85 + 85 + 85 + + + R=170 G=170 B=170 + RGB + PROCESS + 170 + 170 + 170 + + + R=255 G=255 B=255 + RGB + PROCESS + 255 + 255 + 255 + + + R=255 G=0 B=0 + RGB + PROCESS + 255 + 0 + 0 + + + R=255 G=85 B=85 + RGB + PROCESS + 255 + 85 + 85 + + + R=255 G=170 B=170 + RGB + PROCESS + 255 + 170 + 170 + + + R=170 G=0 B=0 + RGB + PROCESS + 170 + 0 + 0 + + + R=170 G=85 B=85 + RGB + PROCESS + 170 + 85 + 85 + + + R=85 G=0 B=0 + RGB + PROCESS + 85 + 0 + 0 + + + R=255 G=85 B=0 + RGB + PROCESS + 255 + 85 + 0 + + + R=170 G=85 B=0 + RGB + PROCESS + 170 + 85 + 0 + + + R=255 G=170 B=85 + RGB + PROCESS + 255 + 170 + 85 + + + R=255 G=170 B=0 + RGB + PROCESS + 255 + 170 + 0 + + + R=255 G=255 B=0 + RGB + PROCESS + 255 + 255 + 0 + + + R=255 G=255 B=85 + RGB + PROCESS + 255 + 255 + 85 + + + R=255 G=255 B=170 + RGB + PROCESS + 255 + 255 + 170 + + + R=170 G=170 B=0 + RGB + PROCESS + 170 + 170 + 0 + + + R=170 G=170 B=85 + RGB + PROCESS + 170 + 170 + 85 + + + R=85 G=85 B=0 + RGB + PROCESS + 85 + 85 + 0 + + + R=170 G=255 B=0 + RGB + PROCESS + 170 + 255 + 0 + + + R=85 G=170 B=0 + RGB + PROCESS + 85 + 170 + 0 + + + R=170 G=255 B=85 + RGB + PROCESS + 170 + 255 + 85 + + + R=85 G=255 B=0 + RGB + PROCESS + 85 + 255 + 0 + + + R=0 G=255 B=0 + RGB + PROCESS + 0 + 255 + 0 + + + R=85 G=255 B=85 + RGB + PROCESS + 85 + 255 + 85 + + + R=170 G=255 B=170 + RGB + PROCESS + 170 + 255 + 170 + + + R=0 G=170 B=0 + RGB + PROCESS + 0 + 170 + 0 + + + R=85 G=170 B=85 + RGB + PROCESS + 85 + 170 + 85 + + + R=0 G=85 B=0 + RGB + PROCESS + 0 + 85 + 0 + + + R=0 G=255 B=85 + RGB + PROCESS + 0 + 255 + 85 + + + R=0 G=170 B=85 + RGB + PROCESS + 0 + 170 + 85 + + + R=85 G=255 B=170 + RGB + PROCESS + 85 + 255 + 170 + + + R=0 G=255 B=170 + RGB + PROCESS + 0 + 255 + 170 + + + R=0 G=255 B=255 + RGB + PROCESS + 0 + 255 + 255 + + + R=85 G=255 B=255 + RGB + PROCESS + 85 + 255 + 255 + + + R=170 G=255 B=255 + RGB + PROCESS + 170 + 255 + 255 + + + R=0 G=170 B=170 + RGB + PROCESS + 0 + 170 + 170 + + + R=85 G=170 B=170 + RGB + PROCESS + 85 + 170 + 170 + + + R=0 G=85 B=85 + RGB + PROCESS + 0 + 85 + 85 + + + R=0 G=170 B=255 + RGB + PROCESS + 0 + 170 + 255 + + + R=0 G=85 B=170 + RGB + PROCESS + 0 + 85 + 170 + + + R=85 G=170 B=255 + RGB + PROCESS + 85 + 170 + 255 + + + R=0 G=85 B=255 + RGB + PROCESS + 0 + 85 + 255 + + + R=0 G=0 B=255 + RGB + PROCESS + 0 + 0 + 255 + + + R=85 G=85 B=255 + RGB + PROCESS + 85 + 85 + 255 + + + R=170 G=170 B=255 + RGB + PROCESS + 170 + 170 + 255 + + + R=0 G=0 B=170 + RGB + PROCESS + 0 + 0 + 170 + + + R=85 G=85 B=170 + RGB + PROCESS + 85 + 85 + 170 + + + R=0 G=0 B=85 + RGB + PROCESS + 0 + 0 + 85 + + + R=85 G=0 B=255 + RGB + PROCESS + 85 + 0 + 255 + + + R=85 G=0 B=170 + RGB + PROCESS + 85 + 0 + 170 + + + R=170 G=85 B=255 + RGB + PROCESS + 170 + 85 + 255 + + + R=170 G=0 B=255 + RGB + PROCESS + 170 + 0 + 255 + + + R=255 G=0 B=255 + RGB + PROCESS + 255 + 0 + 255 + + + R=255 G=85 B=255 + RGB + PROCESS + 255 + 85 + 255 + + + R=255 G=170 B=255 + RGB + PROCESS + 255 + 170 + 255 + + + R=170 G=0 B=170 + RGB + PROCESS + 170 + 0 + 170 + + + R=170 G=85 B=170 + RGB + PROCESS + 170 + 85 + 170 + + + R=85 G=0 B=85 + RGB + PROCESS + 85 + 0 + 85 + + + R=255 G=0 B=170 + RGB + PROCESS + 255 + 0 + 170 + + + R=170 G=0 B=85 + RGB + PROCESS + 170 + 0 + 85 + + + R=255 G=85 B=170 + RGB + PROCESS + 255 + 85 + 170 + + + R=255 G=0 B=85 + RGB + PROCESS + 255 + 0 + 85 + + + + + + + Adobe PDF library 15.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 6 0 obj <>/Resources<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 960.0 560.0]/Type/Page>> endobj 7 0 obj <>stream +H= +A >6&be x\eM2<Żc-;`AEZQPl [1߰3*JM"mX3ʖeIuCcRT=u>g49a +_Z endstream endobj 10 0 obj <>stream +8;Z\ud0bCX#Y"Xj+5HLJ%.@\S!/m8NE+t?RplAUB/UsKgQ +&5<7lMJEnA1,%B]DHtKj]2(/0Zr'TiMKgtQ%&:*pA]W_S3j+FnTUm<)1dG +,a9F*`]]#h)Q6V?UTfi*=b#PJimE]?N62Km+a`P:(bF5BD&2`ofrn+M6(O,L9A9s' +\>KmtPYuL\_F!BrrKY,L[)0kmSl("!:%OBn!%k?%MZ~> endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 9 0 obj <>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +0 0 0 rg +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2783 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 TD +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +T* +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 TD +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 TD +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +T* +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +T* +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +T* +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 TD +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 TD +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + endstream endobj 5 0 obj <> endobj 14 0 obj <> endobj 15 0 obj <> endobj 16 0 obj <>stream +H|TiPYA2횩jAgTNQqWQp=hZVW4t{@QDDcXf\'fg11[0Ƌ|e~Eq|߬?U(7ݢ +P'%_Mbgo1π_frzFh=c!..v6a:-Z4en@&V{FEcՊXV!*J*yF + Q +!*WXBTѓ *yh\FNF>¦FR.PXbա*BbT%rj/ fasq +þưFcE]f%苭6a\gZ hiW+G<-Wə62Nb.-)2655.33q6g20vZ(9k;H&I@wiN- ){ʇHIdD: +-71&ny6֔OяL4qYzy&c +^Jfx3@+K*h. .( S]ۤBq'$~EvC~ҷ5gkTggrƠ%6 'kmH8t+gn+pf +Y ++K4 >^9օ?M_uvA+0ύ-l&R4N6n؋>y<W} +ͷDB2X^gpN }wBEqSq'7q'  Sy6;j$d3W4 axdU|tӋYCJxǿ6wbߜH +6{=x[~~E`&bV:؉LaNyIru/̆U*t A '\d^>x+(&׸{2ȻБY5阂3sK3v^~Q[G rLk*{;珛[YVˮWiߟp-Gy0 W -`^T{liqKaF0 7PV6:*F@R]̓.jje) 7l>/-,>WސS|<*Ki^D&m\cWsyU AJu63-m +fvJ<UZ޵ CT̥?3H֘n04錫Oog;Ea!} tNFVqAk}]Ӓ9i#"\Wv51=+&vX7^5JV/h:?ͪ.}?č"SٕAv 0f^yj(ɊFtD[QAr5οAӬc^YI%lPiLxMJ'2) rcׁG7vds4^F;ay;XU/K-٨8d8" .o唱v0ޛs#vQcO +G`8>=0#ȫӕCe#e"u]\aZtN@f_0"} |DDnܙDDӔU+•iX + +Sk$$^8ze r"}CBA'ٺq +]fI^F^f{-&ǖC%CP%6^x.@W{2vd4u4ݻa³Y[\Xeea6Q+nʤXC>ll+F[Aˠ*cSTH!IIX@hfB}gForϹ9>Ҥ8*KjeEd9c#nF318%lW\T-6<:mYQDpt^NAoa<|a>F$iPL~qY>} +cjA s֬z9 +m=oBޣWl*뵓#Pt{hzҁ3/:9M]Q* [ Eʯ-)z*h!,-peA_)UހC"^S?LP$y+]ˤ1!Y: +JOşR +Eyzo OSkfm0,1rA9b@Zl@]mȏF~u?5B>l0/>*suz z,cʈC*(,i `'Q2'@30\7& ~ {VFsa|fBηDZ$AFl?o紻gxA߀i|ᨱH*vPYNp羟\( /u5YHkEe&.t6}Wdřy'l.&f-ΦqGcQQcЫH3S{kU-5d÷ w>FgGS/eݱ=,:=z=JO/|5O7-ҵw70tOSJ*&KL/5vF;hw:}fNf}xcbԚXB=y\K +09- endstream endobj 13 0 obj <> endobj 8 0 obj <> endobj 17 0 obj <> endobj 18 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 19.0.1 %%For: (Keegan Lillo) () %%Title: (square-color-picker.ai) %%CreationDate: 14/08/2015 12:08 pm %%Canvassize: 16383 %%BoundingBox: 0 0 0 0 %%HiResBoundingBox: 0 0 0 0 %%DocumentProcessColors: %AI5_FileFormat 13.0 %AI12_BuildNumber: 54 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -560 960 0 %AI3_TemplateBox: 480.5 -280.5 480.5 -280.5 %AI3_TileBox: 77 -559.5 860 -0.5 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 3 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: 309.5 -24.5 2 1688 1013 18 0 0 -4 37 1 1 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: 309.5 -24.5 2 1688 1013 18 0 0 -4 37 1 1 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 777 %%PageOrigin:80 -580 %AI7_GridSettings: 100 20 100 20 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 19 0 obj <>stream +%%BoundingBox: 0 -560 937 0 %%HiResBoundingBox: 0 -560 937 0 %AI7_Thumbnail: 128 76 8 %%BeginData: 10320 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45AE638888ABACCECDA8A8FD05FF88FFFFCFCFA8A8FD6AFF64644064 %5D827BCD265252FF89FF888888FFCFCDA8527DA8FD68FF6541653A5D7BA5 %C7274B7DFFAE643FADCFFFCDCDC97DA8FD69FF1E665F3432827BC1C1C9A1 %FFAE65395D57A69EC7BACAA1FD69FFAF6060595E2D5174BBBBBCFFFF653A %583350A79FB5B5C3FD69FF8B6136360C2D509A93B6B6FFFF663B5F352EA8 %A86FB1C3FD69FF673736072F2D516F93B5B6FFAF6743A9305AA84B9394FD %6AFFFB370E02014C214468B6B2FF8BAFAF61142A01466AB8C4FD69FF612B %312B4E73976B9494BEFD04FF5B314F4E72BFBEC5FD69FF5B554E544E9C72 %9C6A95C4FD04FF7F317F547371BFC5FD69FFA9555A539D779CBFB9BFCBFD %06FF7FFFA3CBFDFCFFFD37FFA8A8FD3EFFCFCFCFFFFFFFCFFFA8FD09FFAE %FFFFFFCFFD28FFA852A8FD3EFFCF88CFFFFFCECFA8A8FD08FFADACFFFFCF %CFCFA8A8FD25FFA852A8FD3BFFAE89FF5D8787FFCFCECDA8F85252FFFFFF %89AEAE6387ACFFFFCECEA8527D52FD23FFA8F8A8FD3BFFAE89646488ACFF %FFCDC8A052527DFFFFFFAE658964ADADFFCFCDC8CF527D7DFD62FF644016 %5E57A7A6C7C1BBC3FFCAFFFFFFAE6540645E837CA6C7C8C1FFCACAFD23FF %AF41AFFD04FFB6C4FD36FF8A403A3333517B9FC1BBBCC3C3FD04FF654033 %5E57A67BC8C1BBC2CAC3FD23FFAF66AEFD04FFBDC4FD1BFFA8A8FD19FF3B %6512340A5126FF6E99FCBCA1FFFFFFAE663B34342D2C7BFF758DB593CAFD %22FF8A8AFD04FFC3BDCAFD1CFFA852FD19FFAF413C35590C59FFCF76B6B6 %FD05FF66421360592E84FFA19AB1C4FD23FF8A65AFFFFFFFC4B1C4CBFD1B %FF7D52FD19FF436743AF0736A8FF4B6F68C4FD04FFAF674367A83059FFFF %446F8DCAFD24FFAE11AEFD04FF69A2FD1BFF7DF8FD18FFAFAF6767853029 %544C4C6995BEFD04FFAF8BAFAF61362A014D4694B7C5FD24FFAE8AAFFD04 %FFC4CAFD1BFF7D7DFD18FFAF8BFFA961092B244D46948FBEC4FFFFFFAFAF %FFAF5B312A4E477195B9BDFD24FFAE17AEFD04FFB2C4FD39FF372B2B544E %9796BFBECBFD06FFA9612B55547372BFBFC5CBFD23FFAEAEAFFFFFFFCBC5 %CBFD39FF5B3155537972779CC5C5FD07FF3755557E729D71BFC5CBFD21FF %AE64641CAEFFCBC5BFB9C5FD39FFA9AF7F7FFF9DA3FFCBFD0AFFA985FF79 %A9FD24FFAE83583983FFFFA271969CFD3CFFA9FFA9FD37FF835E0BA8FFFF %A2A271A2FD77FFA80BA8FD04FF4C7DFD78FF89FD05FF9DCBFD77FFAE63AE %FD04FF97A3FD76FFADADADFFFFFFA9A9A3FD4AFFAEACFFFFCFCFFFA8A8FD %24FFAD88FD04FFCB79FD48FFAEAEAE8888ADFFFFCECFA87DA87DFD22FFAE %AD32AEFFFFA9CB237EFD38FFACACFFFFFFCEFFA8A8FD05FFAE898963ACAC %FFCFCECDFFF87D52FD23FFCFA7AEFD04FFA3A8FD35FFAEAEFF88ACADFFCF %CECEFF52527DFFFFFFAE643F4083AEA7CDC7C8C2FFCAFD24FFCF87CFFD04 %FF247FFD35FFAE89AE6387ABFFFFCECDCF52F852A8FFFFFF8940105E7BA6 %7BC8C1BBC9CAC3FD23FFCFCECFFD04FFA3A9FD36FF3F643FFFFFFFCFCCC2 %C9FD06FFAE663B3A332D517BCA99B5B5B6CAFD21FFCECDCDCCCFFFA95555 %F97FFD36FFAE4039335D7CA69FC7B4C9FFCACAFFFFFF6642355F352E7DFF %9A9AB1C3FD22FFCFCEA5CDCFFFA97F542B54FD36FF8A65335E2D5751C89F %BB99C3C3FFFFFFAF673C6784352FFFFF4B6FB6C4FD23FF7C7B50CFFFFF7E %530254FD04FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD07FF66413B342D2D51FF %A075B5B6CAFFFFFFAFFD048B3054297D459495CBFD24FFA851A8FD04FF01 %53FFFFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD06FFAE411360350C59FF %FF7694BDFD04FF8BAFFFFF3737032946716BB8BDFD24FFCF7CCFFD04FF53 %84FFFFFFA8A8FFA8ADA7FFA8CFA8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8FD06FF8B676784362FA8FF %7D6F939BFD07FF856109554E4E6CB9B8BECBFD23FFCAC7CAFD04FF317FFF %FFFFA8AEA8AE88ADA8FFCECFA8A87DA8A8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD05FFAF8B6767AF0D3053774B %708EBEFD08FF30553154729C71BFA2CBFD22FFCACFC9FFFFFFA9AF5AA9FF %FFA8AE89A85D8887AEA8CFCDCF5227277DA8FFA8A8A8ACA7A8A8CFA7A87D %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FD06FFAFAFFFFF85370229284C6A %95B2FD08FFAFA9A97FFF79A3CBFD24FFC9C2FD04FFAF5BFD05FFAE896464 %88ADA8FFCDC8C2A8527DA8FFAEAEA8AD88ACA8FFCFCEA8A87DA87DFFA8FF %A8FFA8FFA8FFA8FFA8FFA8FD06FFAFFFA95BF94F24476B9690BDC4FD09FF %A9FFFFA9A9FD25FFC9C975CAFFFFA9852F85FFFFA8FF5F64395E82A77CA5 %C1C1A1CAA8CAA8AE8989398887AEA8CDCDC97D2752A8A8FFA8A8A8FFA8A8 %A8FFA8A8A8FD0AFF5B315554729777BFC5CBFD34FFA8A0CAFD04FF5A84FF %FFFFA8AE403A3357577B7AC1BBBCA1C3CAFFA889643F5E83A7A7CCCDC2CA %A8CAA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD09FF850955537872779CC5C5 %FD34FFCA99CFFD04FF3785FFFFA8A85F6511580A2C26A77575FCBC9AFFA8 %A8A8403933327C7B9F9EBB99C4A1CAA8A8A8FFA8A8A8FFA8A8A8FFA8A8FD %0BFFA9A9FF9D9DFD37FFCABBCAFD04FF3D85FFFFFFA88A413C35602E58A8 %A776BCB6CAA8FFA88A653A58582C517DC992B5BCC3A8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FD0CFFA9FFA9CBFD35FFCAC3BCB5CAFFAF8B8B43AFFFFFFF %A884673C602F2F7DA8766F6FB6A8FFA8A8A8423B35352E52A8A79A93B6A1 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8FD45FFC3C394B0C3FFAFAF60438BFF %FFFFA88B4343845A07FFA86F4493A1FFA8FFA88B6743605A0DA8A87D4B93 %9BFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD46FF766F68CAFFFF84591384 %FFFFA8AF8BAF848514020122216A6AB7A8A8A8AF8B8B3CAF3630294D4670 %6ABEA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FD48FFA144A1FFFFFFA82E59FF %FFFFAFAFA8FF61312B554D7271B8BEC4A8FFAFAFA8FF853D084F23716BB9 %B7CAA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD47FFCA4BA8FD04FF2E84FFFF %A8FFA8A8A85B092B244E719696BEBECBA8FFA8A8A861312B2A4E7296FDFD %BEC4CBA8FFA8A8A8FFA8A8A8FFA8A8A8FD08FFAEFFFFFFCFFD1FFFAEFFFF %FFCFFD23FFA8FFA8FF5A3155544D97779CBFCBCBFFA8FFA8FF7E31557F4D %9778BFBFCBCBFFA8FFA8FFA8FFA8FFA8FFA8FFA8FD04FFAEFFFF88ADFFFF %CECFFF7DFFA8FD0DFFCFCF7DFD09FFAD88FFFFCFCECFA8A8FFA8FD1DFFA8 %A8A8FFA8A87E855AA27879A2CBA8CBA8A8A8FFA8A87EA97FA87879A8CBA8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8FFFFFFAEAEFF8987ABCFFFCFCECF7D %527DA8FD07FFAEFFFFFFADFFCE7D527DA8FFFFFF89AEAE6387ADFFFFCECE %FF277D7DFD1EFFA8FFA8FFA8FFA8A9A8A39DFFA8FFA8FFA8FFA8FFA8FFA8 %A9A8FFA3FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD04FF89893F89AC %FFFFCDC8C87D527DFD07FF89AEFFFFADACFFCE51F8277DFFFFFFAE646463 %ADADFFCECDC8CF527D7DFD1EFFA8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8 %A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8FD04 %FFAE40645E8282A7CCC8C2C9FFCAFD08FF64643F6387CDC7C27CA8FD05FF %89406482A7A6CDCCC8C2FFCACAFD1EFFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FD04FFAE40395E327C7BA5C1BB99CAC3FD07FF6540335D57827BC8C1BB %A1FD05FF65401157577C7AC89FBB9AC3C3FD11FFA8FFA8A8A8FFA8FFA8FF %FFFFA8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8 %A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FD05FF8A653B58590B51A8A093 %B5BCC3FD07FF65403933827BA59FC1BAC3C4FFFFFFAF663B5F582D517CCA %99B5BCBCFD13FFA7AEA8FFCACFA8FD05FFA8FFA8FFA8FFA8CFAECFA8FFA8 %FFA8FFA8FFA8AE896388AC87CECECE7DFFFFFF89643F6387ACACCF7DFFFF %FFA8FD04FFAF423B60352E59FFCA7693B6FD09FF655F3A330A2C506E99B5 %BCCAFFFFFF66421360350CA8FF7676B0C3FD10FFA8A8A8638888A8CDCDCE %A8FFA8FFA8FFA8A883AEA8A882ACCDCEA7FFA8A8A8FFA8A8838940645D87 %7BA57B27527DA889393957A6A5CCC8272752A8FD05FF8B6743845A0DFFFF %764493A1FD09FF8A421360350C58A176B6BDFFFFFFAF67436784305AFFA8 %4B6F94CBFD11FFAEAE646463ABCCC8C2CAA8FFFFFFA8FFA889643F63ABAB %CCC2C9C3FFA8FFA8FFA8894041403910A59FA5F85252FF403A335D57A6A5 %C8F85252FFA8FFFFFFAF8BAF8B8514082928216A8EB7FD09FF8B423C2F59 %2E7D6F6F8DC4FFFFFFAF8B8BAF3D1407004C4570B2C4FD10FF848A643939 %5D81A59FC2BCC3A1FFA8A8A8FF5E40165D81A5A5C8C2C2A1CAA8A8A8FF60 %413B5F11577BA6C1C2A1CAA8403434330A7B7B6EC19FC1A8A8FFFFFFAFAF %FFFF5B612B4F4D7295B9BDCBFD08FF67673C307EA97D4B6F94CBFFFFFFAF %FFFFAF5B312B4E479696BEBDFD0FFFA88A6641175E577C7AC1C1BBB6BDA8 %FFA8FFA8653A39577C577B9FC1BBBCC3FFA8FFA8AF8B605960582D5199BA %BCBCFF653C35600552519A99C2C3FFA8FD07FF6131554E4E729CB9BFC4FD %09FF8B370E07004C6A8E8FC4FD06FFA95B2B554D7272BFBEC4CAFD0DFFA8 %A8848B413A345804507499FCB194FFA8A884664141115E2D2C269974B0A8 %FFA8A8A8FF848B36362F342C7575BB93BDA8663C3C2F2E274B4B9999C3A8 %A8FD07FF8531557F549778C5BFCBCBFD08FFAF37312A4E4D9595BEBEFD07 %FF315B557E739D77BFC5CBFD0EFFA8AF846035352EFF27516F939AC4A8FF %A8AF84603B600CA883276FBCB6FFA8FFA8FFA88B43370D300C514B768CB6 %B6FF42431430014D46458CB6B5FFA8FD0AFF7EFF7979FD0CFF85372B554D %72729C96C4CAFD08FFA97EFF73A3FD10FFA8FF8460353C0D59A852276F69 %BDA2A8A8FFA8A83C60592F84A8274B6993A8A8A8FFA8A860430930020121 %4B446895BEA8673715024D716B696969B1A8FD0EFFCBFD0DFF0F5B545473 %9C77BFC5FD1FFFA88B3D430F302FA852524594BDC4A8FFA8FFA88B674307 %847D5245948DCAA8FFA8FFA88B61312B4F4D73729570BDBDFF8B312B5553 %7972B895BDBDFFA8FD1DFF7F7FCB79A3CBCBCBFD1FFF5B37090908020046 %6B71B2CBC5A8A8FFA8A8846115020122216A6AB8B7C4A8FFA8A87E612B54 %4E78729C959595C4A88531314E54729D96BEBEC4A8FD1EFFA9FFCBCBA9FF %CBFD1FFFA8A95A5430554E4D7297B9BFC4CBA8FFA8FFA88531314F4E4796 %95B8BEC4A8FFA8FFA8A97F555454799C9CBFB9C5CBFF373731557FA3789C %9CC5C5FFA8FD44FFA8A853554E4F4E7272BF9CCBA8FFA8A8A8FF7E310855 %4D4E6C9C9CC5A8FFA8A8A8FFA8A97EA87EA9A2A8A2CBA2CBA8A97E857EA9 %A2A9A2A8A2CBA8A8FD45FFA8FFA8A97F9D77A2A8FFA8FFFFFFA8FFA8FF5A %3155544E9777A2C5FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FD46FFA8A8A8A9A2A8A8FFA8FD04FFA8A8FFA8A9 %7E7F7EA273A2A2CBA8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FF %A8A8A8FFA8A8A8FFA8A8FD49FFA8FFA8FFA8FD05FFA8FFA8FFA8FFA8FFA8 %A3A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FD52FFA8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FF %%EndData endstream endobj 20 0 obj <>stream +%AI12_CompressedDataxB0(@Œ#<`L` ܞW8Ou;!:bYH:]]5wW],uʵdys :nBG?y7\t<@Aq"5eyu]t2@hxX@o{OYΚ6nrf5`x_5k-0͍!p-! R`.:M%\jtb?"N 7֛ ϶׬`,/\,E+f,:LEc:T ŠSehnFRb},lcׇ5XO{ِ^[]?\7zXf;6PcIPQ!Tpx&qz :5ٿ]\ɐ%̑EAo>/~J8l K+b2ÿ}#=c&J !Ϙ]aLx%tn[i6*Wyoqhrce $..:G⏸SnY :|^D1cHx<LT@b%!߬+Fl(?h(,!*Jct4AGB$D2/dU@K :$h~ADܻL냞HQ|\Ob{yn;6%Ŋh0| _>\~ |Ťi ={A13wye;>5#0exg@ s_K5H?;wek`A]OzUH-!}PU=hkHZny#b!sM ,6~Y졝5#ſ1|oo!YFq&A߭(m[d:*\r#濝*n +TaZ-+َ}o9xT5ms=0m3]M- !jzDk4uҠguzȅt>,F)WY$Tєt{ |gLf 9G ՃN |:[K43pey\c }En-yl}6iĶloݺtpc˰,w FIj1.4>ءThR|uhi jp+PФ5*1"\i"hVdW!$XRe盱r~Reȧ\ηc"E;\_0S4@EIq2[ٯ/^ս"#ŐgA36'@&C-nU-eyA6٬ˏ0ڨMGm7k졘3!GV?(<8^pG*;bؕ!B"^"E!l1'!`d'Bπ߾" XB/$?-b@jp.bE q1#~K3_lԹĂHĎЯؿxG BS1k(U5!3&86$ɵc'#9"ˉ̺s%c~c8rF2]kg qF6yQjGkA Ɩj4/-ڗAb u.9n/~?Nr]ό;ce_Ͳl\pXKS :m*ԯ|R8 ES*ʾ;oRɏ~;.\t>)wmQ lc~A6?xCGِb8fV\gJ{=%`ÑܜRHϸJq'@|9U !}r?κv NlvY0AMg)iČ[6(g_5"9:dI!416E^vv,fh.֟IXg,h:D8ŚbyXs^98Gk΋S"/֜k΋5,{ar;tQo6r]7Xzp7p6}ڗvv5tx,1׾K~eM"NZE +nRpyYwn&f;<[xk͍H"7F|vEn@cAN5 ;ňqb<8?CpLs(:oe_5p(]9Q?`K ɜE2R7b2G"Wċd".9d"H,9SIc+TI75ec$nE8cFui3O99Y1gvͶm7Ei_Ѕ 2vcynF'\͚]p5kfԩř 8WlxZZ1o:ժԹJsHD|t٢&E?3C|h6ZYFY{> ;h.1a¬L!} SR>``9Bͺ RoOG1"~r Md~ tw#[,s!ϛX?Y"1W!\p $/_&ҷ^P75L:BEͶ#'4|Rx/bwйIjWe*#yd{9K?dknc0Q8ig_Q uSͶYk\\p~P܊y?jqZ43)LRyWm9L5.΁UߜǨN[a-;pc9>߁XMqb K}Bm1[ [o Hf ]1ƀNDh:" +EQ ': +\ld8ČQ[:a" \zla2 0N:dC; I\ās:۞ +a~ey,N{vZ!nGpVy!Ӧv^wg֊oWLzLwAQ&OKϴg]Y8BfdqDw&M'9hs>m2U 7bVTo¬Rq,h3q$^$^zIW;yXgӧ}ۑq m*ToG%3n~Spjab8X4Ljbo ?wZ&gNi9Q?I&d|0o2cE>ɝٳD㺬XdٍcupӟsJ(ϯ; ~.1\ȪOp+tXϓpy@ôlRa0"S!%Z6-Ո3=#6vԈD~1eu~iE 0Sn,t(Ҟq3JȌQr0iqXIU/.a菫?7c Hk=w' ޫ~Ic-̶GGzt|S~N(_o/(oq|l!*o_K p0_e us}ط3EPoIY}3N:y=wnfұ:JS*3x7M~3s.Lx')*0{*; : +S# +50I:!bB04:JE҉HGtBBJ)R'` GaBT8)FGx&Dg@͙""b8'f Ł_L:$á1fǤd8bU,pB#E'q*L%8e* (tH1 + P$%3A'# 2 G@0t G@x$Z +CP4 FFQZ)#'+!*C@T"QD-޳pT,@ >6͐@T 4hA$!,d,J#%$t-@ȽD-8RU0'HEEx؋%b2Q0(d 6BECR0T[[+UT?]0H6x$ #E: \F +@PaA"8 +d2PSJIH,G@ܣdZ|&~EhGE@, +X+j)E@y% #X|h4Fur] Ţqħ  tŒ!QAaFAk,#l$54>'Q` +ab9):n,BHwAkP8ր#4+/4 +z*G0Cq+V~0tC l7EŐ4 S˵h\'KF(KA$Eg YM A<~AJ:S0IHbcEhP;-&@{[NȄV 1X}) !:&>"8Ī1,P 9 I57 Ԁ +0b HhilAh(_X?GbIEf(-~9}`Gp()hp 06?Q$W釅4SEh@D*hH#{s+ƁU&+ckBz$Cdt{ܫ=Eu#W7XP0HL1#Wu +9^ 2!/ +و4KP - +8O͠b Jr\Xq(aWF=53@?F,:C(%L!dہ6Ha8J(Aq`QAţbYʴ)1&Heq[̘a\lS$5D@E!`!,pB-pqpQ #ȢCivu|| xa0<11nT)E+j[QG;%`ڀh&)ZȔxV+D)CIdl*J #7Q-3 b)#3Ͳ:#0XTBʖ% ⠒1+ +,4N8RQ%!lj(noa$vl5I%Mxs  7.bAUq'h! NhH1D&\I.2d@ 6 ƀ҉7D%:T&TIKNZ(\C84 +ia0x@6 PdΌQLJ vEMP1SFE;' (~ {%!VO!iŌt2\!Q\r\mag 8'.4weyV0PJ]2\gL}"EёBb +,y*V+w+Tbxagfڢv=y$9N٭4q Rc Y_: nÖS ZT浉v0LB)Q'-1r)9+9>N" \p! 7H{n]Gc^LVvv-V'q;nMQI !H~ ?Kɿ?3Qܜ%|ǻHm}g,=Vq 7ԃA!t>Sgq$'J]J8pRDN']=.Uh(JU.|q+C Q]MᒮwYj)?ڝSmu&!fz*>;lsU}mARy ]S1YyIk4Plfc!-(6A[sx>0"xmW+ rMf=g$΍=T C kI,) feZQD ~ZcT4uP|db(LEG7c8qIscq6C. E7<ʽfl[1ie\QStQٻ0]g_l{X!% fu }kɆRmk gm1}J9A5'&-ϸF,nnPF! *& PJ[ [ g:B. #a6%.EP6ꃒѫh:T)x#Wg{.4eu +LMw!i ą~VL>TP_hwlsf=eC 6-*ZYJ_fĆj:}3a9j3oN4l( Veo<&]`)3GKkļmNʵ&+zܚ`t +E-BXX4T18:iBgŌ+-/W#kQZ]U'ttp0u\$W Z $ cu]3PJ~)}J@ +rnb=׈6eooaŁ'cu5U~qShBߡ `x𢠪?ۮl@ŗxr# Žָn]6t] yxYwA%|^`QC^[b.e VWyk3+#ylX[`S~Pum-Yx:R {B{UvqF@>J΋iDۂ. ܂]vARRrOA+ֽLY i 5&l%Zٱ?=1"14(b\>i X&nPg OӲ89Z4 +YIg9i&>oG5߆‚<.Ldjۙ0umY4V +V⎵o8ܴ^/t`9ZUZJVޢc]fQ aU C7+_!xqo$EiVFīB2 ʘlm*j2)2AU86sAJ(tjBPBiQ\ڼʘZ&FVB0M9ocKw  " .( f4g"f[VTPLNN/mGj9!ki֭͞˘ͼzsWT:WU2#?l ##k9 ވh+TdZ"vshOr@wWݫOz y8my2=qC$t(nHjC^#0wa;CelbxU5#;V-[5qjv-Ti&ꎭ1Ie?`\ Ϻ̸$Iu#Apv]RLץ)b7#uʺpKv/7pA\\ŊAݽ3ͮ`]P5ƌV`Ť0}wsPuE0:o +AYXC5ٳr5um/V ifюt OeߺlgeY|,{V-|ITR~ptQHMLf+ڣ2xіTYy /RBWikl(lZ n.~..??ji]DC B!X򘘢Ẹy`N1o5Zb _Y$S #:#͒ !K'k{ANViP}5~DHTWEf^T_0v4:F[E#)4ƨ"Z6sIZ<īw,Yĉ2b2VEz,LGn:7aHLZV@ILpK4mSIk^Ffidx +QFXt,4E D -)b4j=>NDs,50rp۽[aNkL?|lw'w=dj([NGϞS/C P̽oQfjw'Ppɓ9sF'Yj7–ͽZ.XuVx\<>_2GVÝ R÷[rջg{nɓkP'UVF9]ńmv[Զr[A{Xo,y0 +o ]}F+0뫙X*Yߤ/bQj~^ߗ*g,x & ǯ/QꜦ/6O H!EއW[KDh?`R;r|ÉîJ 9'XvrT9,[wln2\U?WZʪ'v8"|L%EMt@-?6V_wŁh\'قw]@ATiR84BYV6y!SNCw9(o5}/[դє$[ivɓ.}aChCBQ(􋅍b{>|o%`Ukh<_bn0\𭷙;vK?WSA/] +kSebQ[nG1-!jPӗrz߻G +-W|ܥo<5An-}]~j'>]CP iy{!UMFM%*eMSA޻uXSdo?cK#ocЃSv&oݣl݂8[ЏJ4(G11kkl3{pƯ^zW󈧳a,S'V-o@m_ycɣĿ?3HfX,todosסc/ܒG^5P x.R7vp4"ndɃFܙw'pzOE~c4Rxx";W2 J2]k'`jǘ4HPw1NZra?qg2C1 qٷqܣTÔ-P3+U 'kOKΐ{ +ΨQu*pOMLmmde"OƊ'+}kqsD#,CdR@}ܭsR[ &9"Y1#QES"ː-c zɏGZo3]=Rl;l_Pʮ7 k[`&F:S}ϱ#ŷB FžMD@hbd5Dli4.[>N&S08!M$OP?BM 5kKP$%G'*tR]s4[Ui3sycwԱfi wgAd'?=߶˛S`wN9"=cMY35*TY/>.Y^YJiC^,YHMdءvPt챪^(d(zFpk59ʑ@3cV_>9*lnlE@]A|Uu.*i8[}|kR_J;$0~MQqŖj:#P,- oXI:ZKj Hz!٦Ќ:eu=Dn`崍l 7b?}@i;֐Pъh{? &^t}[c%:vNK]v,yD7|w2^b}|Z7J$yWM5%{}RWne|7g +&;H465xW/ vnrǝS&z{H_meD×xEy"`l9׸ hݒRk2VĻ΍,ؓx';҇k!ی5 UyMݮ23wwVN~ͣu7ut+u!W=l4Wm M#4% lv3~Q۹5a_7Z8}oq4h(.E /:j90#\"-9a"{{ iw?u RyRWT{[9|b{\,8$sm.F#1gCh^]r^ӗNzS?9@:9/r[0tL~eir(e]>+Ad/6bd+/k6Vr+,*B6~}GVڸIz3u[>f +j4WSw{*+8/9 +MM+\ v AX)~AA%bEz٤BGpÂ9 wSPpt}}EUw#w+:L_ҙ0~oE-C7Ճ**Rcl&OGWHQУPp#K8DF:|H¡]NOTiS82chq!9@zeL) *ntc*]Wu/ V%Vb ˯Sj Q -^ q|Hl|]@yY[3B9h|O6@)vuont F<2nW"|ŷ/<&x@E+9FJ;ktP裥o{7a^j_E"|PcrNT{*sG4,Wf.zC^L}ۥB!L^A/΁}B잧K[)ͮ +f|m\I'etf"z=v{rdOea-v¯eyXcf-y3~B[_5-^(a>%l/NO+Bʠ䁖ٽ{45xn6m~v 5e$N 5^*{` /x7[}+zV1LO0/\Vmc#wl +]K"F#迃wAm_ClU{,4ނ:>x@] "ݥ7Bd+yw4)KD;0Q=JHS=UN+[m6ݭL:u[k.XVeȌɑ<<8SZ-? :4CZ>0w'DOr (Aӧa +Ϧ}6;\ۈf76El:cxD~7_地:e 6BpKkɽlT[{Ê~pX6cE*sS畂4y)>nq*5y(|Tьȏq80C9#6粝g>w} !axΝ2aߕםˣMpa &ifܼ ǶW^E_ܗm |mY&oO;adG(/xM?j}4g_. +V\CpEk䨸0WyHمUevXi0;m#ԝcó1lJ˼3EٜQB? eYSjVSw[WUΙؕAٴbɕFEiM7RNZV) rzv3x+VeLNJr*[BQ9Ij6ˢ-Yih,5eйxAڼ;)uG޳82aQ,bqVs>KgtR5ձ]KgKtz <XISP[vFŎxZ[^zdC= -_ 7 oqm5H}~G?"Wa ? [G+{ [OC؇{#|ׯ?nl]ox/ne=Z_B0tr +CnH3w[8H{msN]y"ECT̿d7SYb?t)d@#uk(R()`5@ޢ_=khh] >*@b*'ah jo};tbԷS~,y6W"ƸFyntP+va*r +P5؊XJ̀Q+7@j('u(d5%trni)wa݂)hro_7n"("51z[mz7]~x blb e([N^' @%~Uk=xytkTRAe=R)tCVD Oo +h`Tz7@   +;}].E>9;S P_C@LoK&=q yЪ;Pa4\;+OGok w7}Jw[ç9K= ߎ(rif~gyd5`r4ZM&#Jk) + ჁN ?}̟ޭW-ƞ + }̟>F|O_ZK(^{__Yӧ`J#~@i&?s/wLnS/>#sﷆϫzQiO!5s-kR~:JʇHNhkL.žrR9w a1g υA+S+^uq9Z^/^(Qa^O:X8AuȌ jMq ʒE:/ +X ЇgS@Qd-X@wjҪhH2ذhymcs&Ǵ`oAL>TL.ˇ1S\o\)3 +P()nϾIp"zN[%v}H:OgC1"KXG7rZ)z~V e8B|ˡAC)&S?<lN@5ZoؤR@Q'QGyecƽsJizxș:F9)G3G2CJ y?>9AiT,Bo[I4KSkK}Nu}AWwN Cvl4h53NWG'eHfb?['2gT$:#bvοFt_8嬝订~:C5!'A,?ojUO^Q=Cٗw8[5D_ N((>VMe|Kz|e95V܆R) P/6"6l_[&!e ZmՓe +5i =Rk~bP8sY 5|>%2yA/?8',sZ;Jlw.&u;<&OLLՒǼ31ˇ>&LlMF54;5o H:hBdhe~B=K#Qс)GFV o2,|2E2LŔfJ0dCf`΢ǨbD0/n %\,0?YzOz==JMٕ .ʔ<!!V^^ +xvl}~UV3iWDb Ã; ZkQg4)FObiR,<$,1h~DיuTD2uCaК6#m$>TVx=֐2}$AYӍ2P$ D!v(\@|Kηk *IlbCfh`pꚀUo)q`7ǃ?eU}(0i ipmZDG@E2FL*l̲0a^޵>-2сYϵ&1^?n(!Xw0ɁJZqYO3,Hč\.v@:&{ &3$Kn{WMGqTSƉh=Px4Ri0,[7d\tN !X%:8Kg9y% aGu0@*dtpԕ"i[&tƷb#/L;X5:AEm{rybHIS !3SK 'L[6Ė<eOG 'hJJRꖈ)aߘ[#vmD^򭨳:4"n*gڏ~P˒B 4eGg +˨ݓkFX>lԈboG˼^~ XK.Qp1{Ψc증!ԯ񙒲5"PKvˍj4@<@_ųhّMk4lWC$ {J7h4EaަP/͈lqdOI 2hrRr ŅC97nԢuo ` \>& `Bm4#J\jofQgaR53mk$gPo%bhSZ,p QCRێxj%w,[Qk9#>ǂF|YP#X"ru\Q4{5[܊~{R+L߫{B˽ph{3[hʣ;khm k|o~kxO*R}X=JiV#SSHEV=JE }l +YmhCL˚wbZĈάOA0h2'B6ejqJ1i4gH/WS=u8U_Um\1Ivr g(F;gVc{Gp5*z_4:E`1Ӧea66x5>3>,ĵ.7{ٷ'Qgʣ7ofkG rIGu~c/ҲJGW +'۔ٔiΈ)>͛sgKߣ?H $^bJo}:)cKg*<,##I(#c1$*Љ(#c%i32d+Ȝ8'N|-O蜨"1ss@.sV1QSTfxpvɘ]E**C4#3uT1rb]8]Pn<6iHeV&I<%ɠseN'>7LW̶"!n_.z0sVuu#f ۺ:{G$D۽6q"N&ܶ5XG-ٟGVFZgs^ߔĵ$}Eܴ8pF;TQ%t&١J^gU:z8MܺN-:N>UTcz8=SmQg_YO9xz D|KT|6=7J=r՞^PC1 ]/& XW`-3A0~A.ٞE4B;MިoZ!4E@&N48ʏ5wLK o&; 5u|`ŷ&CԋNlj^~BUr~&=b!s̖]NK Jl\iݙæ_SHmQ!Hqr4HeYkwnJwSHRET*RVE|k ٰ`hC'@ZkFvp".G&VlZpc=F)|ag*yK}R8ørpFY‰Rڗ‘9!†NG>͉F[TyѼ@gSxUc dq$Mm2=&XU`i4$GhwD8كH/3ΐo n޺}ͻ̣̳ۃ{㳌>Rn_-p]gR3*Onҷ +aq@{xj*"ˡquqEo7~e4rk?=ZT ˅O +E}Rk+X +mUlk#cl<WQ$uaw ՝YV s[o +Xo>гuS /;Ww_*ߤ>;[:]c+_KvszQsZpG$m9KM=fX 12/Ѻ{qZKMrȍˑoE4 Y6KL7O8q*OFY5@>nyll${T v4i7j:Qy?Z(pϕ؏ Hn\gZ\8go|f)i& T4܀fҒNw7W9ZeQ泫JsO;[A%n&[6 XΉn<$͎݉mΈNMK^iɊxoNY=luGLY)/c`[w?槳n-V.Ĭ6YeC{MޑΑ,ިgYViK*J ̴lD]ɻ 0Wr7PWm6\&0`J1ra:,[9~!c:osEub̨xAiM)e!}>q)gצJʍr0ЧxE9j~vF1ZGYtѦC1TG/bgI i0e*@@S5̺ QOy*@gqex65؝V.y&$\ +"]HZUZ8oUhF"O O6U[c_ʧ,إ|Y_gwt.'tfz)a/,lo +\S;^?&OAm +gCgg}XgCg.3tz~$LeV?SǸoJiV*Tdv+s^?^?;TǹϨ~/{8y:Y%5{Ҧp߰~V?}{8#N]{u/Mz>ig].XMx67Shg^?cg_Jv5)p4wi=o3-7gY&SUNԏ)=V)-[Q҄Y'Y +<ӹό[,+glƸ.So\iub ᄧ7YbDqe 3~ra:z%MIgSVgWKzd,>b\hϕ~d1.K^?-gǜg}j}{vyq~~-# 9~F5O5g8$9;m^QP}i{ &V +<ԤϺ V珍}Q->(:ϾvY/.^?Y> o3o^?+PeG-/}=8 Wu+PiK]j2H>Yv-6ދyKmbAK8ayG.XT 6X)Ţ(w w;7VIM*lBӍGTɕܡҹoh?:腩B2 +//G:gu)z*U2gNݮw?3희Rwr:^{rRomuW^y!nl^%Re;W܏o2PˬŕjB[i;\>}Bq}jB%>aJ|Tw͗y~}WWn4I\y&[}mbFרs5ZޥB@pE^Hq%\(~|XyAvQJyTU + e[ϒň/^>s3˨}+5npmݵ.L)~m;xLHݽr[A{t| hPx:~"ciVb}ПAIGov AZM,sK3?A_9|pdLo!:G>PdI휹yȧ`W^@==wI+dM6_,#5?n0ٽ*=6@~5 ܸbjs#wQ&~-`zo6H&$@|.yhrDGm} v3ϧp +ûeoew|8@߆PހʱG.mwo +ȧb$}xN Gjp5Nrnɒ'qLRl9w\*[{z,2ve%_k}% gJ?)MXҏDwϛ)zvߞhqʄ܅4kܣ;`}PzD Ҫٯ+v/UQ*ο<.N [p&N찯Gpi'V*5QJR>Ĭ0Q@hMb+E)/|czHiF$o\=h/=Ilk!N0W/1IŬ mW ţ:6>ȽqP6Л*?yMԛ=xRI{ UX'5aD,/Ru5DOևuцXXB3GPOm]ZJ܃(QjEEb) ;εml"Ad3;;;C8YG ]m;!!YX4pd~12c+n%b4BNІ]kדjB  Yd /K| @x>q>cx/؅FjKp},% 5#bEg<;Z JX6KT V%4g̨ H EaGJ2 [ΰ +Cn'F\Ɋ:.}(Ky0>Y4ƣk"f˓x}4$dcbx_c+6lCp/kE;)ČF`xӪt bSU[:F(]xnd!oJ-Z"fo D;P`SۘgWncmw16@ZA֘+Ƥ{Cd+;jb<";(L Q0lk-ܳ(_Ce8Y <>p6"fHy`guPyKeWRT6 nCd3zXOCW–ԪQz*YOexXbH`A9b$`[>rѢ1Q.Z䬑SEpӷF@o&GgُP*0E, "‘q${j$PDpBxlp3ɿEuaTAbϣOR}˴& ~ Z'/E +ϲ /_ $٧mlAH" ؂[rBE a%\Jʽ6OeW'/J& n(遥@) }.})jQ C>QN6k c +JƊRL^@*yD(v6&kqkAժ.,m0s?c һ|pf麀U.1Уd%&dG5-m)9oC!$㕎5~~tF7~>RkXlNo1\we0zJ/.Fw^d/]U{fh!SAY4CKD:Tja\ 冐Ieo!ˇs{kJV>;?zt:8H*/Xn1'xHrֳo5U_o$Vn怌,nN^]rڠ S{ٴ_lkC CꘚJ=H‡JBu0:{-ec.* < +ѣ0;)风h@IHf{_yVtrBlPp {x-}t<Co *4kHaw; ka5`ěnN 3"XZ@c#RFy\49z؜+u|;(\b/?щ7RUv-D*dZWjr1a +t'Yx`wh?yBZlB-їė[VH6bh[ogUk,z|i>=¹Wx v #HMYc֖A l;@µBor) µB>WpTSQ"3zjfLFYlCdV(l{PW։&Nl`7g~A:SUFS@`D"$Yrɭ`rX*XSl6,[Y?"\GUVD@gd[+z'MІWƤrSM>zxczS_'Ş-Fy+a< RSàz0_]׍+pXA BiJ XF,ɳǩ5C;3+O rag0ׁ%4DĹxѻx {`A#R>h(Ik_JfVoFvJNɵâ%#FJjш)cՒE#KF88.rшj]8.rшxLWGgÁ vkhW{!TsùdE^b|,d0aEX[-Msf铯 gfX G(jZ%D(ş)  %轐zǠ˘f1e\ ]6o Nn<`LX~rwϺo#} {x./pgo8LߛN1~GX} endstream endobj 21 0 obj <> endobj xref 0 22 0000000000 65535 f +0000000016 00000 n +0000000076 00000 n +0000053640 00000 n +0000000000 00000 f +0000056251 00000 n +0000053691 00000 n +0000053985 00000 n +0000060539 00000 n +0000055133 00000 n +0000054188 00000 n +0000054572 00000 n +0000054620 00000 n +0000060426 00000 n +0000056765 00000 n +0000056849 00000 n +0000057231 00000 n +0000060612 00000 n +0000060786 00000 n +0000061999 00000 n +0000072496 00000 n +0000101206 00000 n +trailer <<268DB876A3B1421B94EE1ACF97837FB5>]>> startxref 101405 %%EOF \ No newline at end of file diff --git a/devsite/source/assets/other/pebble_colors_64.gif b/devsite/source/assets/other/pebble_colors_64.gif new file mode 100644 index 00000000..bd2e3754 Binary files /dev/null and b/devsite/source/assets/other/pebble_colors_64.gif differ diff --git a/devsite/source/assets/other/pebble_colors_64.pal b/devsite/source/assets/other/pebble_colors_64.pal new file mode 100644 index 00000000..166bb0f1 Binary files /dev/null and b/devsite/source/assets/other/pebble_colors_64.pal differ diff --git a/devsite/source/assets/other/weather_image.pdc b/devsite/source/assets/other/weather_image.pdc new file mode 100644 index 00000000..c9bef560 Binary files /dev/null and b/devsite/source/assets/other/weather_image.pdc differ diff --git a/devsite/source/atom.xml b/devsite/source/atom.xml new file mode 100644 index 00000000..b299298e --- /dev/null +++ b/devsite/source/atom.xml @@ -0,0 +1,41 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /atom.xml +--- + + + + <![CDATA[{{ site.title }}]]> + + + {{ site.time | date_to_xmlschema }} + {{ site.url }}/ + + + {% if site.email %}{% endif %} + + Octopress + + {% for post in site.posts limit: 20 %} + + <![CDATA[{{ post.title | cdata_escape }}]]> + + {{ post.date | date_to_xmlschema }} + {{ site.url }}{{ post.id }} + + + {% endfor %} + diff --git a/devsite/source/blog/feed.xml b/devsite/source/blog/feed.xml new file mode 100644 index 00000000..ee30f4ed --- /dev/null +++ b/devsite/source/blog/feed.xml @@ -0,0 +1,44 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +--- + + + + Pebble Developer Blog + The official blog of the Pebble Developer site. + {{ site.url }}{{ site.baseurl }}/blog/ + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for cat in post.categories %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/devsite/source/blog/index.html b/devsite/source/blog/index.html new file mode 100644 index 00000000..687d13fa --- /dev/null +++ b/devsite/source/blog/index.html @@ -0,0 +1,72 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +title: Blog +layout: blog/master +tag: all +regenerate: true +--- +

    + Pebble Developer Blog +

    +

    A Wristed Development

    + +
    + {% for post in paginator.posts %} + {% include blog/index-post.html post=post %} + {% endfor %} + +
    +
    +
    + Prev +
    +
    +
    +
    + +
    +
    +
    +
    + Next +
    +
    +
    +
    diff --git a/devsite/source/contact/index.html b/devsite/source/contact/index.html new file mode 100644 index 00000000..0e51d6be --- /dev/null +++ b/devsite/source/contact/index.html @@ -0,0 +1,72 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: more +title: Contact Developer Support +menu_subsection: contact +--- +
    +

    {{ page.title }}

    +
    +
    +

    + Pebble is no longer providing direct 1-on-1 support for developers. + We are now focusing our efforts upon our public support channels, + which will directly benefit other developers who may be experiencing + similar issues. +

    +

    + If you have shipping, billing or other non-developer related questions, + please contact Pebble Support + instead. +

    + +

    Developer Support

    +

    In order to find a resolution to a development issue, you may want to try + our community forums.

    +
      +
    • 1. Try searching to locate a resolution for your problem.
    • +
    • 2. If you were unable to locate a solution, create a new topic. Try + to provide as much detail as possible, including logs, screenshots or + code snippets.
    • +
    • 3. If you haven't received an adequate response within a few days, + you should seek help within our + Discord server, providing a link to + your topic.
    • +
    +

     

    + +

    Appstore Featuring

    + +

    Unfortunately we cannot guarantee featuring with the appstore. In order + to be selected for featuring within the Pebble appstore, you should adhere + to the following guidelines to improve the chances of selection:

    +
      +
    • 1. Ensure your app is high quality and bug free.
    • +
    • 2. Support multiple Pebble platforms.
    • +
    • 3. Utilize latest Pebble SDK functionality. (Unobstructed Area, + AppGlances, Health etc.)
    • +
    • 4. Provide app configuration options.
    • +
    • 5. Have a complete set of assets, including banners.
    • +
    • 6. Promote your app in the Showcase category of the forum.
    • +
    • 7. Tweet @pebbledev with + a link to the showcase.
    • +
    + +
    +
    +
    diff --git a/devsite/source/contributing.html b/devsite/source/contributing.html new file mode 100644 index 00000000..a77f06ae --- /dev/null +++ b/devsite/source/contributing.html @@ -0,0 +1,31 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Contributing to the site +layout: more +menu_subsection: contributing +permalink: /contributing/ +draft: true +--- +
    +
    +
    +

    Contributing to the site

    +

    The new Pebble developer site is open sourced on GitHub.

    +

    We welcome your contributions to improve the site.

    +

    {{ site.links.site_repo }}

    +
    +
    +
    diff --git a/devsite/source/docs/c/index.html b/devsite/source/docs/c/index.html new file mode 100644 index 00000000..9935b3e1 --- /dev/null +++ b/devsite/source/docs/c/index.html @@ -0,0 +1,70 @@ + + +--- +layout: docs +permalink: /docs/c/ +title: C SDK Documentation +docs_language: c +--- +
    +
    +
    +

    {{ page.title }}

    +

    + This is the contents page for the + Pebble C SDK. Here you will find + information on all the available modules, functions and objects that can + be used to create watchapps and watchfaces for Pebble. +

    +

    + The C SDK is broken down into these {{ site.data.docs_tree.c.size }} + top level modules. Click on them to browse around the rest of the SDK. +

    +

    + You can also use the search bar at the top of the page to find what + you are looking for. +

    + + {% for module in site.data.docs_tree.c %} +
    +

    {{ module.name }}

    + {% if module.summary.size > 0 %} + {{ module.summary }} + {% else %} +

     

    + {% endif %} +
    + {% endfor %} +
    +
    +
    +
    +

    + To learn more about how to use this SDK, take a look at the + Writing Apps for Pebble section of + the Developer Guides. +

    +

    + You can also view the + C SDK Tutorial. + This will guide you through the first stages to creating a Pebble + watchface, adding customized images and fonts and extra + web-based content. +

    +
    +
    +
    diff --git a/devsite/source/docs/c/preview/index.html b/devsite/source/docs/c/preview/index.html new file mode 100644 index 00000000..28d8b178 --- /dev/null +++ b/devsite/source/docs/c/preview/index.html @@ -0,0 +1,51 @@ + + +--- +layout: docs +permalink: /docs/c/preview/ +title: C SDK Documentation (PREVIEW) +docs_language: c_preview +--- +
    +
    +
    +

    {{ page.title }}

    + {% include docs/c/preview.html %} +

    + This is the contents page for the preview version of the + Pebble C SDK. Here you will find + information on all the available modules, functions and objects that can + be used to create watchapps and watchfaces for Pebble. +

    +

    + The C SDK is broken down into these {{ site.data.docs_tree.c_preview.size }} + top level modules. Click on them to browse around the rest of the SDK. +

    + + {% for module in site.data.docs_tree.c_preview %} +
    +

    {{ module.name }}

    + {% if module.summary.size > 0 %} + {{ module.summary }} + {% else %} +

     

    + {% endif %} +
    + {% endfor %} +
    +
    +
    diff --git a/devsite/source/docs/index.md b/devsite/source/docs/index.md new file mode 100644 index 00000000..e03ae17a --- /dev/null +++ b/devsite/source/docs/index.md @@ -0,0 +1,80 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: docs/markdown +title: SDK Documentation +permalink: /docs/ +--- + +Welcome to the API Documentation section of the Pebble Developers site! + +Here you will find complete listings of all the classes, objects, methods and +functions available across all the parts of the Pebble API. + +## Pebble Smartwatch APIs + +Pebble's smartwatch APIs provide developers with a means of developing +applications that run natively on Pebble smartwatches. + +### [Pebble C API](/docs/c/) + +The Pebble C API, used for creating native **watchapps and watchfaces** in C. +The Pebble C API can be used in combination with *any* of the PebbleKit APIs +listed below to extend the application's functionality. + +### [Pebble JavaScript API](/docs/rockyjs/) + +Pebble's embedded JavaScript API, used for creating native **watchfaces** in +JavaScript. The embedded JavaScript API can be used in combination with +PebbleKit JS to extend the application's functionality. + +## PebbleKit APIs + +The PebbleKit APIs provides developers with a means to extend their application's +functionality by communicating with an application on the mobile device it is +paired to. + +### [PebbleKit JS](/docs/pebblekit-js/) + +PebbleKit JS enables developers to extend their Pebble projects by adding a +JavaScript component that is managed by the Pebble mobile app. PebbleKit JS is +capable of bidirectional communication with with application running on the +Pebble smartwatch + +### [PebbleKit iOS](/docs/pebblekit-ios/) + +PebbleKit iOS is an Objective-C library that enables developers to create +companion apps for iOS devices that are capable for bi-directional communication +with their Pebble API projects. + +### [PebbleKit Android](/docs/pebblekit-android/) + +PebbleKit Android is a Java library that enables developers to create companion +apps for Android devices that are capable for bi-directional communication with +their Pebble API projects. + +{% comment %} +## Web APIs + +### [Timeline API](/docs/web-timeline/) + +The Timeline API enables developers to create applications that interact with +the user's Timeline, by creating and editing Timeline Pins. + +### [AppGlance API](/docs/web-appglance) + +The AppGlance web API enables developers to create applications that push +information to the application's glance in the user's launcher. + {% endcomment %} diff --git a/devsite/source/docs/pebblekit-android/index.md b/devsite/source/docs/pebblekit-android/index.md new file mode 100644 index 00000000..4f1c14e2 --- /dev/null +++ b/devsite/source/docs/pebblekit-android/index.md @@ -0,0 +1,40 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: docs/markdown +title: PebbleKit Android Documentation +docs_language: pebblekit_android +--- + +This is the contents page for the PebbleKit Android SDK documentation, which +includes all information on the two main packages below: + +{% for module in site.data.docs_tree.pebblekit_android %} +

    {{ module.name }}

    +

    +{% endfor %} + +This part of the SDK can be used to build companion apps for Android to enhance +the features of the watchapp or to integrate Pebble into an existing mobile app +experience. + +Get started using PebbleKit Android by working through the +[*Android Tutorial*](/tutorials/android-tutorial/part1). Check out +{% guide_link communication/using-pebblekit-android %} to learn more about using +this SDK. + +You can also find the source code for PebbleKit Android +[on GitHub](https://github.com/pebble/pebble-android-sdk). diff --git a/devsite/source/docs/pebblekit-ios/index.md b/devsite/source/docs/pebblekit-ios/index.md new file mode 100644 index 00000000..cb485bde --- /dev/null +++ b/devsite/source/docs/pebblekit-ios/index.md @@ -0,0 +1,46 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: docs/markdown +title: PebbleKit iOS Documentation +docs_language: pebblekit_ios +--- + +This is the contents page for the PebbleKit iOS SDK documentation, which +includes all information on the main reference sections below. + +This part of the SDK can be used to build companion apps for iOS to enhance the +features of the watchapp or to integrate Pebble into an existing mobile app +experience. + +Get started using PebbleKit iOS by working through the +[*iOS Tutorial*](/tutorials/ios-tutorial/part1). Check out +{% guide_link communication/using-pebblekit-ios %} to learn more about using +this SDK. + +You can find the source code for PebbleKit iOS +[on GitHub](https://github.com/pebble/pebble-ios-sdk), and the documentation +is also available on +[CocoaDocs](http://cocoadocs.org/docsets/PebbleKit/{{ site.data.sdk.pebblekit-ios.version }}/). + +{% for module in site.data.docs_tree.pebblekit_ios %} +

    {{ module.name }}

    +{% for child in module.children %} +
    {{ child.name }}
    +{% endfor %} +

    +{% endfor %} + diff --git a/devsite/source/docs/pebblekit-js/index.html b/devsite/source/docs/pebblekit-js/index.html new file mode 100644 index 00000000..c1f801dd --- /dev/null +++ b/devsite/source/docs/pebblekit-js/index.html @@ -0,0 +1,77 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: docs +title: PebbleKit JavaScript Documentation +docs_language: pebblekit_js +--- +

    +
    +
    +

    {{ page.title }}

    + +

    + PebbleKit JS is a JavaScript component of the Pebble SDK which runs + within the Pebble mobile application. It provides access to GPS, + storage and Internet connectivity to applications running on Pebble + smartwatches. +

    + +
    + This does not relate to JavaScript running on the watch. For embedded + JavaScript see Rocky.js. +
    + +

    + The PebbleKit JS API is provided via the `Pebble` namespace: +

    +
    + + {% for module in site.data.docs_tree.pebblekit_js %} + {% if module['kind'] == "member" or module['kind'] == "namespace" %} +
    +

    {{ module.name }}

    +

    {{ module.summary | markdownify }}

    +
    + {% endif %} + {% endfor %} + +
    + +

    PebbleKit JS also provides access to the following standard JS functionality:

    + +
    +
    +
    +
    +

    + To learn more about how to use this part of the Pebble SDK, check out the + {% guide_link communication/using-pebblekit-js "PebbleKit JS guide" %}, + which also includes links to example apps. +

    +

    + You can also view the third part of the + Create a C Watchface + tutorial series for an example JS implementation. +

    +
    +
    +
    diff --git a/devsite/source/docs/rockyjs/index.html b/devsite/source/docs/rockyjs/index.html new file mode 100644 index 00000000..00b38dc5 --- /dev/null +++ b/devsite/source/docs/rockyjs/index.html @@ -0,0 +1,74 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: docs +title: Pebble Rocky.js API Documentation +docs_language: rockyjs +--- +
    +
    +
    +

    {{ page.title }}

    + +

    + Pebble's JavaScript API allows developers to write watchfaces in + JavaScript, that are executed via the firmware's + JerryScript engine. For + instructions on getting started with Pebble's JavaScript API, see the + JS Watchface Tutorial. +

    + +

    The JavaScript API is broken down into the following modules:

    + +
    + + {% for module in site.data.docs_tree.rockyjs %} + + {% if module['kind'] == "member" or module['kind'] == "namespace" %} +
    +

    {{ module.name }}

    +

    {{ module.summary | markdownify }}

    +
    + {% endif %} + {% endfor %} + +
    +

    The JavaScript API also includes the following global functions:

    + + {% for module in site.data.docs_tree.rockyjs %} + {% if module.kind == "function" %} +
    +
    + {% include docs/js/function.html child=module global=true %} +
    +
    + {% endif %} + {% endfor %} + +
    +
    +
    +
    +
    +

    + To learn more about how to use this part of the Pebble SDK, check out the + JavaScript Watchface tutorial, + which also includes links to example apps. +

    + {% include docs/js/mozilla.html %} +
    +
    +
    diff --git a/devsite/source/docs/symbols.html b/devsite/source/docs/symbols.html new file mode 100644 index 00000000..1efdeda8 --- /dev/null +++ b/devsite/source/docs/symbols.html @@ -0,0 +1,34 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /feed.xml +layout: docs +permalink: /docs/symbols/ +--- +
    +
    + + {% for symbol in site.data.symbols %} + + + + + + + + {% endfor %} +
    {{ symbol.language }}{{ symbol.name }}
    {{ symbol.summary }}
    +
    +
    diff --git a/devsite/source/docs/symbols.json b/devsite/source/docs/symbols.json new file mode 100644 index 00000000..aa1dc752 --- /dev/null +++ b/devsite/source/docs/symbols.json @@ -0,0 +1,24 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +[ + {% for symbol in site.data.symbols %} + { + "name": "{{ symbol.name }}", + "language": "{{ symbol.language }}", + "url": "{{ symbol.url }}", + "summary": "{{ symbol.summary | escape | newline_to_br | strip_newlines }}" + }{% unless forloop.last %},{% endunless %}{% endfor %} +] diff --git a/devsite/source/docs/tree.md b/devsite/source/docs/tree.md new file mode 100644 index 00000000..bca9a616 --- /dev/null +++ b/devsite/source/docs/tree.md @@ -0,0 +1,28 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: docs +--- + +
    +
      + {% for language in site.data.docs_tree %} +
    • {{ language | first }} + {% assign tree = language[1] %} + {% include docs/tree.html items=tree %} +
    • + {% endfor %} +
    +
    \ No newline at end of file diff --git a/devsite/source/examples/index.html b/devsite/source/examples/index.html new file mode 100644 index 00000000..b66f68f5 --- /dev/null +++ b/devsite/source/examples/index.html @@ -0,0 +1,85 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: default +menu_section: examples +scripts: + - examples +--- +
    +
    + + {% assign examples = site.data.examples | sort: 'featured' %} + {% for example in examples reversed %} +
    +
    + {% if example.screenshot_platform == 'chalk' %} + + {% elsif example.screenshot_platform == 'basalt' %} + + {% else %} + + {% endif %} +
    +
    +

    {{ example.title }}

    +
    + {% for tag in example.tags %}{{ tag }}{% unless forloop.last %} · {% endunless %}{% endfor %} +
    + {{ example.description | markdownify }} +

    + GitHub + {% unless example.no_cloudpebble %} + {% if example.beta_cloudpebble %} + CloudPebble + {% else %} + CloudPebble + {% endif %} + {% endunless %} +

    +
    +
    + {% endfor %} +
    +
    +
    +
    +

    Language

    + {% assign languages = site.data.examples_metadata.languages | hash_sort: 'count' %} + {% for language in languages reversed %} + {{ language | first }} + {% endfor %} +
    +
    +

    Hardware Platform

    + {% assign hardware_platforms = site.data.examples_metadata.hardware_platforms | hash_sort: 'count' %} + {% for hw in hardware_platforms reversed %} + {{ hw | first }} + {% endfor %} +
    +
    +

    Tags

    + {% assign tags = site.data.examples_metadata.tags | hash_sort: 'count' %} + {% for tag in tags reversed %} + {{ tag | first }} + {% endfor %} +
    +
    +
    +
    diff --git a/devsite/source/faqs/index.html b/devsite/source/faqs/index.html new file mode 100644 index 00000000..8987bb86 --- /dev/null +++ b/devsite/source/faqs/index.html @@ -0,0 +1,46 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: more +title: Frequently Asked Questions +menu_subsection: faqs +description: | + A list of all the most frequently asked questions about Pebble with answers +--- + +
    +

    Frequently Asked Questions

    +
    +
    + {% for section in site.data.faqs %} +

    {{ section.title }}

    + {% for item in section.questions %} +

    {{ item.question | markdownify }}

    +

    {{ item.answer | markdownify }}

    + {% endfor %} + {% endfor %} +
    +
    +
    +

    Sections

    + +
    +
    +
    +
    \ No newline at end of file diff --git a/devsite/source/favicon.ico b/devsite/source/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/devsite/source/getting-started/index.html b/devsite/source/getting-started/index.html new file mode 100644 index 00000000..59c793a6 --- /dev/null +++ b/devsite/source/getting-started/index.html @@ -0,0 +1,79 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: default +title: Getting Started +description: Pebble is an open platform. It's easy to develop on Pebble. You can get started in 60 seconds with CloudPebble. Our SDK is available for free and includes iOS and Android libraries. +menu_section: getting-started +--- +
    +
    + +
    +
    +
    +

    Learn To Write Your First Pebble App

    +

    + Welcome, new developer! Whether you are an experienced C programmer or a + beginner, this tutorial series is aimed at helping you get set up and + building with the Pebble SDK as fast as possible. The end result will be + a new watchface for your Pebble that was created by you! +

    + View Tutorial +
    +
    +
    +
    +
    +
    +
    +
    +
    Already Have a Mobile App?
    +

    Integrate With Pebble

    +
    + +
    +

    + iOS Tutorial +
    +
    +
    +
    +
    +
    +
    +
    +
    Allergic to C?
    +

    Try Pebble.js

    + + Pebble.js Tutorial +
    BETA
    +
    +
    +
    +
    +
    diff --git a/devsite/source/guides/toc.html b/devsite/source/guides/toc.html new file mode 100644 index 00000000..0c1b0241 --- /dev/null +++ b/devsite/source/guides/toc.html @@ -0,0 +1,64 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: guides/master +permalink: /guides/toc/ +title: Guides Table of Contents +--- +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    Guides Table of Contents

    + {% for item in site.data.guides %} + {% assign group_name = item[0] %} + {% assign group = item[1] %} + {% assign group_index = forloop.index %} + {% capture grp_url %}/guides/{{ group_name }}/{% endcapture %} +

    {{ group_index }}. {{ group.title }}

    +

    {{ group.description }}

    + {% if group.guides.size %} + {% assign group_guides = group.guides | sort: group.sort_by %} + {% for guide in group_guides %} + {% if guide.menu %} +

    {{ guide.title }}

    +

    {{ guide.summary }}

    + {% endif %} + {% endfor %} + {% endif %} + {% endfor %} +
    +
    +
    +

    Overview

    + +
    +
    +
    diff --git a/devsite/source/index.html b/devsite/source/index.html new file mode 100644 index 00000000..817c018b --- /dev/null +++ b/devsite/source/index.html @@ -0,0 +1,62 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: sidebar_wide +description: Pebble is an open platform. It's easy to develop on Pebble. You can get started in 60 seconds with CloudPebble. Our SDK is available for free and includes iOS and Android libraries. +scripts: + - landing-page +menu_section: none +regenerate: true +--- +
    +
    +
    +
    +

    New To Pebble?

    +

    + Learn the basics of C, build a customized watchface, or create a web-connected watchapp.

    + Great for both new and experienced developers! +

    +

    Tutorials

    +
    +
    +
    +
    +

    Back For More?

    +

    + Enhance your watchapps using our detailed Developer Guides and SDK Documentation. +

    + Guides + Documentation +
    +
    +
    +
    +
    + {% for feature in site.data.features %} +
    +
    +
    +
    +

    {{ feature.title }}

    + {{ feature.button_text }} +
    +
    +
    +
    {% endfor %} +
    +
    +
    diff --git a/devsite/source/inspiration.md b/devsite/source/inspiration.md new file mode 100644 index 00000000..640d85c2 --- /dev/null +++ b/devsite/source/inspiration.md @@ -0,0 +1,226 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: more/markdown +title: App Inspiration +menu_subsection: inspiration +permalink: /inspiration/ +generate_toc: true +page_class: inspiration-page +--- + +We're often asked by developers at hackathons and code days what kinds of apps +they should write for Pebble. It’s a great question and in an attempt to begin +answering it, we decided to publish a list of ideas and use cases that we would +love to see our developers tackling. + +We hope that this list provides you with ideas, inspiration, and a +glimpse into what we hope to see more of in the future. If you have +any suggestions for ideas or categories we should add to this list, +please fill out +[this form](https://docs.google.com/forms/d/1S6_C4JP5HREK9jAolDXjOepEI1mkZpTTBm_GK6XIzg4/viewform) +and let us know what you think! + +If you want to show us what you're working on, [reach out to us](/contact). We'd +love to provide feedback, connect developers to each other, showcase cool +apps in our developer newsletter, and even sponsor some amazing projects. + +## Transportation + +> When you’re on the move, the last thing you want to do is take out your phone. + +We think about this in two categories: Navigation & Public Transit + +### Navigation + +Knowing when to take that left turn usually only requires a few well +timed glances. How can Pebble help you get from point A to point B in a way +that’s simple and efficient? + +### Transit + +Our Pebblers in the SF Bay Area rave about the +[Caltrain app](http://apps.getpebble.com/en_US/application/53eb5caf6743f7a863000201), +which gives us the closest Caltrain station, time until the next train, +and future train schedules. It has simplified our morning commute +dramatically and lets us keep our phones in our pockets at the right time. +How awesome would it be if Pebblers all over the world were able to have +that same luxury? Can we incorporate the Pebble timeline into that experience? + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28486/transportation-apps#latest) + +## Workout Companions + +> A personal trainer on your wrist + +When you’re at the gym, it’s a pain to constantly pull out your phone +to figure out which exercise comes next, track your reps and time, and log +what you did. There are some great timers, stopwatches, running & biking +trackers, and rep counters out there, but we haven’t yet seen the whole +package come together. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28487/workout-companions#latest) + +## Gift Cards, Loyalty, Ticketing + +> Get rid of all those extra cards, apps, and keychains

    + +Aren’t you tired of carrying around a giant wallet or keeping track of +a bunch of paper tickets? As the device that’s always on you (and always on), +Pebble has the potential to replace all that clutter. How cool would it be +to walk into your favorite retailer, concert, sports game, pharmacy, or +gas station and have to do nothing but flash your magic wrist-wand? Pretty +cool. + +Working with [Eventbrite](https://apps.getpebble.com/applications/55b7e74d180264f33f00007e) +made a lot of sense - get events on your Pebble timeline and launch your tickets +right from your wrist. How can we get everyone’s tickets and cards onto +their wrists? + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28498/gift-cards-loyalty-cards-and-ticketing#latest) + +## Local Discovery + +> What's happening in your city tonight? + +Timeline is a great place to see your own events, but what about the +ones you have yet to discover? Imagine all the coolest events in your area +landing right on your timeline. We love using services like Eventbrite, +Meetup, Songkick, and Bandsintown to discover what’s going on near us — +how can we bring this to Pebble? + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28488/local-discovery#latest) + +## Pebble to Pebble Communication + +> Communicate with other Pebblers without taking out your phone + +Simply put - Pebblers are awesome, and awesome people should stick +together. How cool would it be if you could communicate with other +Pebblers without even needing a phone? +[Boopy](https://apps.getpebble.com/applications/556211d49853b8c3f30000b9) +is an awesome start - how far can we take this? + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28489/watch-to-watch-communication#latest) + +## Tasks, Reminders, Todo Lists + +> Pebblers are busy people who get stuff done + +With timeline as a core experience on the Pebble Time and the Voice API +coming soon, we’re excited to see how developers can make Pebble the +ultimate tool for productivity. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28490/tasks-reminders-to-dos#latest) + +## Addictive Games + +> Ever played [Pixel Miner](https://apps.getpebble.com/applications/539e18f21a19dec6ca0000aa)? + That’s one we can never put down…. + +Whether it’s about trivia, flapping birds, or paper planes, we’d love +to see more games in the appstore that endlessly entertain Pebblers and +keep them coming back for more. + +And don’t forget, you’ve got a few tricks up your sleeve to keep them +coming back… (e.g. [timeline pins](/guides/pebble-timeline/), the +[Wakeup API](/guides/events-and-services/wakeups/)). + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28491/addictive-games#latest) + +## Home + +> Forget those remotes, switches, keys, and outlets — control and monitor your home with your watch + +Studies show over 80% of Americans have at some point in their life +misplaced one of the above. With more of your home getting smarter and +more connected, Pebble can become the primary controller and monitor for everything. +[Leaf](https://apps.getpebble.com/applications/52ccd42551a80d792600002c) +is an awesome example -- let’s keep it going. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28492/pebble-for-the-home#latest) + +## Security + +> Pebble unlocks what matters + +[Authenticator](https://apps.getpebble.com/applications/52f1a4c3c4117252f9000bb8) +has shown us the power of Pebble for easy two factor authentication. Whether +it’s for physical or digital spaces, a watch that’s always on you is the perfect +tool to keep what matters safe. Let’s see what else Pebble can unlock... + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28493/pebble-for-security#latest) + +## Enterprise + +> Businesses can't ignore wearables either... + +Wearables provide a new, meaningful way for consumers to interface with +services, retailers, and brands. At the same time, employees and managers +have a newfound ability to notify, organize, coordinate, and learn from +one another. The grass is green and the sky’s the limit. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28494/pebble-for-businesses#latest) + +## On Demand + +> Making 'on-demand' even faster + +People expect food, rides, dates, and just about anything else faster +and on-demand. Pebble is always with you, always on, and always a button +click away from something awesome. + +Let’s give Pebble a few more superpowers… + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28495/pebble-for-on-demand#latest) + +## Stay in the Know + +> Pebblers want to stay on top of what's happening + +The watch is a great place to consume little bits of information. News, +political events, music releases, entertainment, or anything else that +matters - we’d love to see how Pebble can help. + +Let’s keep people in the know about what’s happening in the world, +wherever they are. + +Maybe you even want to find ways to time-travel into +the future? We’re listening. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28496/pebble-and-staying-in-the-know#latest) + +## Time + +> Pebble at its core is a damn good watch + +Time is the essence of everything we do here at Pebble. We’ve seen all +sorts of awesome watchfaces -- dynamic, digital, analog, weather... but +there’s always more that can be done. If you can think up and create new, +innovative ways to tell time, we’re interested. + +[Join the Discussion >{more}](https://forums.getpebble.com/discussion/28497/time#latest) + +--- + +Anything pique your interest? We certainly hope so! Fire up your +emulators, get building, and let’s **#makeawesomehappen** together. + +Don't forget, we have some pretty cool +[design and interaction guides](/guides/design-and-interaction/) +to help you out. + +Off you go! diff --git a/devsite/source/mobilenav.html b/devsite/source/mobilenav.html new file mode 100644 index 00000000..6d9b13ea --- /dev/null +++ b/devsite/source/mobilenav.html @@ -0,0 +1,146 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +
    + +
    diff --git a/devsite/source/more/index.html b/devsite/source/more/index.html new file mode 100644 index 00000000..61ad0744 --- /dev/null +++ b/devsite/source/more/index.html @@ -0,0 +1,47 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: more +title: More +menu_section: more +menu_subsection: none +--- +
    +
    +
    +

    More

    +

    +

    Need Inspiration?

    +

    + Check out this list of ideas we'd love to see developers working on. +

    +

    Examples

    +

    + A link to the Pebble Examples organisation on GitHub. +

    +

    Contact Form

    +

    + A form where you can contact us. +

    +

    Frequently Asked Questions

    +

    + A collection of some of the questions that are most frequently asked by developers. +

    +

    Open Source

    +

    + Check out our open source projects and libraries. +

    +
    +
    diff --git a/devsite/source/open-source.html b/devsite/source/open-source.html new file mode 100644 index 00000000..8fda21d3 --- /dev/null +++ b/devsite/source/open-source.html @@ -0,0 +1,71 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +layout: more +title: Pebble Open Source +menu_section: more +menu_subsection: open-source +permalink: /open-source/ +--- +
    +
    +
    +

    Pebble Open Source

    +

    + Here at Pebble we are big believers in the power of open source. Our + firmware is built upon FreeRTOS, + and all of our mobile and web applications make use of many different + open source libraries. +

    +

    + Whenever possible, we like to give back to the open source community, + so here are just some of our open source projects available for anyone + to use in their own work. +

    + You can also check out our GitHub profile at + https://github.com/pebble/. +

    + {% for group in site.data['open-source'] %} +

    {{ group.name }}

    + {% for project in group.projects %} +

    + {{ project.name }} +

    + {{ project.summary | markdownify }} + {% endfor %} + {% endfor %} +
    +
    +
    +

    Sections

    +
      + {% for group in site.data['open-source'] %} +
    • {{ group.name }}
    • + {% endfor %} +
    +

    Work For Us!

    +

    + As a rapidly growing company, we're always on the lookout for new + talent to grow our various teams. If you or anyone you know has the + technical skills, wearable enthusiasm and a taste for the Valley + lifestyle, have a look at + our jobs page to see a list + of open positions. +

    +
    +
    +
    +
    diff --git a/devsite/source/robots.txt b/devsite/source/robots.txt new file mode 100644 index 00000000..14267e90 --- /dev/null +++ b/devsite/source/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / \ No newline at end of file diff --git a/devsite/source/round/getting-started.md b/devsite/source/round/getting-started.md new file mode 100644 index 00000000..8cb334f5 --- /dev/null +++ b/devsite/source/round/getting-started.md @@ -0,0 +1,101 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Getting Started with Pebble Time Round +description: | + Details on all the new features and APIs available for the Chalk platfom, or + Pebble Time Round. +layout: sdk/markdown +permalink: /round/getting-started/ +generate_toc: true +search_index: true +platform_choice: true +--- + +With the addition of Pebble Time Round to the Pebble hardware family, the Pebble +SDK now targets a third platform called Chalk. Feature-wise, this new hardware +is very similar to the Basalt platform with one major difference - a new display +with a round shape and increased resolution. + +Pebble SDK 3.6 is available to help developers write apps +that are compatible with all hardware platforms. New graphics APIs and UI +component behaviors assist with creating layouts ideally suited for both the +rectangular and round display types. + +^LC^ [Get the SDK >{center,bg-lightblue,fg-white}](/sdk/download/?sdk={{ site.data.sdk.c.version }}) + +^CP^ [Launch CloudPebble >{center,bg-lightblue,fg-white}]({{site.links.cloudpebble}}) + +## New Resources + +To get you started, we have updated the following sections of the Pebble +Developer site with new content and information on designing and developing +for the new Pebble hardware platform: + +* An updated + [*Hardware Comparison*](/guides/tools-and-resources/hardware-information) + chart - See the hardware differences between all Pebble platforms. + +* [*Design in the Round*](/guides/design-and-interaction/in-the-round/) - A new + section of the Design and Interaction guides with design guidance on how to + best take advantage of this new display shape. + +* [*Creating Circular Apps*](/guides/user-interfaces/round-app-ui/) - A new + guide describing how to use new graphics APIs and features. + +* An updated + [*Building for Every Pebble*](/guides/best-practices/building-for-every-pebble/) + guide - New information added on how to keep apps compatible with all three + Pebble hardware platforms. + +* Revised + [*Platform-specific Resources*](/guides/app-resources/platform-specific/) + guide - App resources can now be tagged according to intended usage in more + ways than before to allow tailored resources for different display shapes. + + +## New Examples + +A number of new example apps have been created to help illustrate the round +concept. They are listed below. + + +### Watchfaces + +**Time Dots** + +^LC^ [![time-dots >{pebble-screenshot,pebble-screenshot--time-round-silver-14}](/images/sdk/time-dots.png)]({{site.links.examples_org}}/time-dots/) + +^CP^ [![time-dots >{pebble-screenshot,pebble-screenshot--time-round-silver-14}](/images/sdk/time-dots.png)]({{site.links.cloudpebble}}ide/import/github/pebble-examples/time-dots/) + +**Concentricity** +{% screenshot_viewer %} +{ + "image": "/images/sdk/concentricity.png", + "platforms": [ + {"hw": "aplite", "wrapper": "red"}, + {"hw": "basalt", "wrapper": "time-black"}, + {"hw": "chalk", "wrapper": "time-round-silver-14"} + ] +} +{% endscreenshot_viewer %} + +### Watchapps + +**ContentIndicator Demo** + +^LC^ [![content-indicator-demo >{pebble-screenshot,pebble-screenshot--time-round-silver-14}](/images/sdk/content-indicator-demo.png)]({{site.links.examples_org}}/content-indicator-demo/) + +^CP^ [![content-indicator-demo >{pebble-screenshot,pebble-screenshot--time-round-silver-14}](/images/sdk/content-indicator-demo.png)]({{site.links.cloudpebble}}ide/import/github/pebble-examples/content-indicator-demo/) diff --git a/devsite/source/round/index.html b/devsite/source/round/index.html new file mode 100644 index 00000000..601d564f --- /dev/null +++ b/devsite/source/round/index.html @@ -0,0 +1,38 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sidebar_narrow +title: Pebble Time Round +description: | + Learn more about the new APIs and SDK tools available for Pebble Time Round +sidebar_only: true +menu_section: none +--- + +
    + {% include search.html %} +
    +

    Welcome Developers!

    +

    + Get your hands on the Pebble Time Round SDK and start building the apps + you want to see. Happy coding! +

    + +
    +
    diff --git a/devsite/source/sdk/changelogs.html b/devsite/source/sdk/changelogs.html new file mode 100644 index 00000000..21f0d118 --- /dev/null +++ b/devsite/source/sdk/changelogs.html @@ -0,0 +1,18 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +permalink: /sdk/changelogs/ +--- + diff --git a/devsite/source/sdk/download.md b/devsite/source/sdk/download.md new file mode 100644 index 00000000..10677d88 --- /dev/null +++ b/devsite/source/sdk/download.md @@ -0,0 +1,139 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sdk/markdown +title: Pebble SDK Download +permalink: /sdk/download/ +menu_section: sdk +menu_subsection: download +generate_toc: true +scripts: + - sdk/index +--- + +## Get the Latest Pebble Tool + +The `pebble` tool allows you to quickly switch between different SDK versions. +The instructions to obtain the tool vary depending on your platform. All +specific instructions are shown on this page. + + +## Mac OS X + +The Pebble SDK can be installed automatically using Homebrew, or manually if +preferred. If you already use at least version 4.0 of the `pebble` tool, you can +install the latest SDK by running the following command: + +```bash +$ pebble sdk install latest +``` + + +### With Homebrew + +If you previously used Homebrew to install older Pebble SDKs, run: + +```bash +$ brew update && brew upgrade pebble-sdk +``` + +If you've never used Homebrew to install the Pebble SDK, run: + +```bash +$ brew update && brew install pebble/pebble-sdk/pebble-sdk +``` + + +### Without Homebrew + +If you would prefer to not use Homebrew and would like to manually install the +Pebble SDK: + +1. Download the + [SDK package]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2). + +2. Follow the [Mac manual installation instructions](/sdk/install/mac/). + + +## Linux + +Linux users should install the SDK manually using the instructions below: + +1. Download the relevant package: + [Linux (32-bit)]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) | + [Linux (64-bit)]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2). + +2. Install the SDK by following the + [manual installation instructions](/sdk/install/linux/). + + +## Windows + +Installing the Pebble SDK on Windows is not officially supported at this time. +However, you can choose from alternative strategies to develop watchfaces and +watchapps on Windows, which are detailed below. + + +### Use CloudPebble + +[CloudPebble]({{site.links.cloudpebble}}) is the official online development +environment for writing Pebble apps. It allows you to create, edit, build and +distribute applications in your web browser without installing anything on your +computer. + +**Pebble strongly recommends [CloudPebble]({{site.links.cloudpebble}}) for +Windows users.** + + +### Use a Virtual Machine + +You can also download and run the Pebble SDK in a virtual machine. + + 1. Install a virtual machine manager such as + [VirtualBox](http://www.virtualbox.org) (free) or + [VMWare Workstation](http://www.vmware.com/products/workstation/). + 2. Install [Ubuntu Linux](http://www.ubuntu.com/) in a virtual machine. + 3. Follow the standard [Linux installation instructions](/sdk/install/linux/). + + +## Testing Beta SDKs + +Beta SDKs are released in the run up to stable SDK releases, and give interested +developers a chance to test out new features and APIs and provide feedback. + +You can opt-in to the beta channel to receive beta SDKs. Once the beta period ends, +you will be notified of the update to the final stable version. + +
    +{% markdown %} +**IMPORTANT** + +Apps built with a beta SDK **must not** be uploaded to the developer portal, as +users not yet on the new firmware version will be unable to install them. +{% endmarkdown %} +
    + +Once you have the latest `pebble` tool, you can easily access and try out new +beta SDKs we release from time to time by switching to the 'beta' sdk channel: + +```bash +$ pebble sdk set-channel beta +``` + +Install the latest beta SDK: + +```bash +$ pebble sdk install latest +``` diff --git a/devsite/source/sdk/index.html b/devsite/source/sdk/index.html new file mode 100644 index 00000000..cb40745f --- /dev/null +++ b/devsite/source/sdk/index.html @@ -0,0 +1,103 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: default +title: SDK +scripts: + - sdk/index +menu_section: sdk +--- +
    +
    +
    +
    +
    +
    +
    + +

    CloudPebble

    +
    +
    +
    +
    +
    +
    +

    CloudPebble is our online IDE that lets you develop Pebble apps without any downloads, setup or installation.

    +

    You can access your projects from anywhere, and it keeps your code synced with GitHub automatically.

    + Launch CloudPebble +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    Pebble SDK

    +

    + Current SDK Version: {{ site.data.sdk.c.version }}
    + Current Tool Version: {{ site.data.sdk.pebble_tool.version }} +

    +

    Release Notes

    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Mobile App SDKs

    +

    If you're looking to write an iOS or Android app that works with Pebble, you should check out our native PebbleKit libraries, both of which are available on GitHub.

    +

    + Android + iOS +

    +

    + PebbleKit Android Version: {{ site.data.sdk.pebblekit-android.version }}
    + PebbleKit iOS Version: {{ site.data.sdk.pebblekit-ios.version }} +

    +
    +
    +
    +
    +
    +
    +
    diff --git a/devsite/source/sdk/install/index.md b/devsite/source/sdk/install/index.md new file mode 100644 index 00000000..c885f536 --- /dev/null +++ b/devsite/source/sdk/install/index.md @@ -0,0 +1,51 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sdk/markdown +menu_subsection: install +title: Installing the Pebble SDK +--- + +## Develop Online + +You can use [CloudPebble]({{ site.links.cloudpebble }}) to build, compile +and test Pebble apps entirely in the cloud without any installation needed. + +## Install Through Homebrew + +We recommend Mac OS X users [install the SDK using Homebrew](/sdk/download). + +## Manual Installation + +Once you have [downloaded the Pebble SDK](/sdk/download/), you will need to +follow the instructions for your platform. + +## [Mac OS X](/sdk/install/mac/) | [Linux](/sdk/install/linux/) | [Windows](/sdk/install/windows/) + +### Problems Installing? + +If you need help installing the SDK, feel free to post your comments in the +[SDK Installation Help forum][sdk-install-help]. Please make sure you +provide as many details as you can about the issues +you may have encountered. + +**Tip:** Copying and pasting commands from your Terminal output will help a great deal. + +### What's Next? + +Once you have installed the Pebble SDK, you should check out our +[Tutorials](/tutorials/) section to learn the basics of Pebble development. + +[sdk-install-help]: https://forums.getpebble.com/categories/sdk-install/ diff --git a/devsite/source/sdk/install/linux.md b/devsite/source/sdk/install/linux.md new file mode 100644 index 00000000..fedd2fb8 --- /dev/null +++ b/devsite/source/sdk/install/linux.md @@ -0,0 +1,54 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sdk/markdown +title: Installing the Pebble SDK on Linux +description: Detailed installation instructions for the Pebble SDK on Linux. +menu_subsection: install +menu_platform: linux +generate_toc: true +permalink: /sdk/install/linux/ +--- + +> **Important**: The Pebble SDK is officially supported on +> Ubuntu GNU/Linux 12.04 LTS, Ubuntu 13.04, Ubuntu 13.10 and Ubuntu 14.04 LTS. +> +> The SDK should also work on other distributions with minor adjustments. +> +> **Python version**: the Pebble SDK requires Python 2.7. At this time, the +> Pebble SDK is not compatible with Python 3. However, some newer +> distributions come with both Python 2.7 and Python 3 installed, which can +> cause problems. You can use
    `python --version` to determine which is being +> used. This means you may need to run `pip2` instead of `pip` when prompted to +> do so below. + +## Download and install the Pebble SDK + +{% include sdk/steps_install_sdk.md mac=false %} + +{% include sdk/steps_python.md mac=false %} + +## Install Pebble emulator dependencies + +The Pebble emulator requires some libraries that you may not have installed on +your system. + +```bash +sudo apt-get install libsdl1.2debian libfdt1 libpixman-1-0 +``` + +{% include sdk/steps_getting_started.md %} + +{% include sdk/steps_help.md %} diff --git a/devsite/source/sdk/install/macosx.md b/devsite/source/sdk/install/macosx.md new file mode 100644 index 00000000..92d2af0f --- /dev/null +++ b/devsite/source/sdk/install/macosx.md @@ -0,0 +1,79 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sdk/markdown +title: Installing the Pebble SDK on Mac OS X +description: Detailed installation instructions for the Pebble SDK on Mac OS X. +menu_subsection: install +menu_platform: mac +generate_toc: true +permalink: /sdk/install/mac/ +--- + +These are the manual installation instructions for installing the Pebble SDK +from a download bundle. We recommend you +[install the SDK using Homebrew](/sdk/download) instead, if possible. + +### Compatibility + +> **Python version**: the Pebble SDK requires Python 2.7. At this time, the +> Pebble SDK is not compatible with Python 3. However, some newer +> distributions come with both Python 2.7 and Python 3 installed, which can +> cause problems. You can use
    `python --version` to determine which is being +> used. This means you may need to run `pip2` instead of `pip` when prompted to +> do so below. + +### Download and install the Pebble SDK + +1. Install the [Xcode Command Line Tools][xcode-command-line-tools] from + Apple if you do not have them already. + +{% include sdk/steps_install_sdk.md mac=true %} + +{% include sdk/steps_python.md mac=true %} + +### Pebble SDK, fonts and freetype + +To manipulate and generate fonts, the Pebble SDK requires the freetype library. +If you intend to use custom fonts in your apps, please use +[homebrew][homebrew-install] to install the freetype library. + +```bash +brew install freetype +``` + +### Install Pebble emulator dependencies + +The Pebble emulator requires some libraries that you may not have installed on +your system. + +The easiest way to install these dependencies is to use [homebrew][homebrew-install]. + +```bash +brew update +brew install boost-python +brew install glib +brew install pixman +``` + +> If you have installed Python using Homebrew, you **must** install boost-python +> from source. You can do that with `brew install boost-python --build-from-source` . + +{% include sdk/steps_getting_started.md %} + +{% include sdk/steps_help.md %} + +[xcode-command-line-tools]: https://developer.apple.com/downloads/ +[homebrew-install]: http://brew.sh/ diff --git a/devsite/source/sdk/install/windows.md b/devsite/source/sdk/install/windows.md new file mode 100644 index 00000000..f77c4498 --- /dev/null +++ b/devsite/source/sdk/install/windows.md @@ -0,0 +1,60 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sdk/markdown +title: Installing the Pebble SDK on Windows +description: Detailed installation instructions for the Pebble SDK on Windows. +menu_subsection: install +menu_platform: windows +generate_toc: true +permalink: /sdk/install/windows/ +--- + +Installing the Pebble SDK on Windows is not officially supported at this time. + +However, you can choose from several alternative strategies to develop +watchfaces and watchapps on Windows. + +## Use CloudPebble + +[CloudPebble][cloudpebble] is the official online development environment for +writing Pebble apps. + +It allows you to create, edit, build and distribute applications in your web +browser without installing anything on your computer. + +**Pebble strongly recommends [CloudPebble][cloudpebble] for Windows users.** + +## Use a Virtual Machine + +You can also download and run the Pebble SDK in a virtual machine. + + 1. Install a virtual machine manager such as + [VirtualBox](http://www.virtualbox.org) (free) or + [VMWare Workstation](http://www.vmware.com/products/workstation/). + 2. Install [Ubuntu Linux](http://www.ubuntu.com/) in a virtual machine. + 3. Follow the standard [Linux installation instructions](/sdk/install/linux/). + + +## Need installation help? + +If you need help installing the SDK, feel free to post in the +[SDK Installation Help forum][sdk-install-help]. + +Please make sure you provide as many details as you can about the issue you have +encountered (copy/pasting your terminal output will help a lot). + +[cloudpebble]: {{ site.links.cloudpebble }} +[sdk-install-help]: https://forums.getpebble.com/categories/sdk-install/ \ No newline at end of file diff --git a/devsite/source/sdk4/getting-started.md b/devsite/source/sdk4/getting-started.md new file mode 100644 index 00000000..c2ac5037 --- /dev/null +++ b/devsite/source/sdk4/getting-started.md @@ -0,0 +1,97 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: Getting Started with SDK 4 +description: | + Details on all the new features and APIs available for use in SDK 4 +layout: sdk/markdown +permalink: /sdk4/getting-started/ +search_index: true +platform_choice: true +--- + +Pebble SDK 4 is now available for developers who are interested in using the +new APIs and features. We encourage developers to read +the [Release Notes](/sdk/changelogs/), the [SDK 4 Docs](/docs/c/), and the new +guides listed below to help familiarize themselves with the new functionality. + +## Getting Started + +{% platform local %} +#### Mac OS X (Homebrew) +```bash +$ brew update && brew upgrade pebble-sdk && pebble sdk install latest +```` + + +#### Mac OS X (Manual) +1. Download the + [SDK package]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2). + +2. Follow the [Mac manual installation instructions](/sdk/install/mac/). + +####Linux +Linux users should install the SDK manually using the instructions below: + +1. Download the relevant package: + [Linux (32-bit)]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) | + [Linux (64-bit)]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2). + +2. Install the SDK by following the + [manual installation instructions](/sdk/install/linux/). +{% endplatform %} + +{% platform cloudpebble %} +Launch CloudPebble +{% endplatform %} + +## Blog Posts + +We've published several useful blog posts regarding SDK 4: + +* [Introducing Rocky.js Watchfaces!](/blog/2016/08/15/introducing-rockyjs-watchfaces/) +* [Prime Time is Approaching for OS 4.0](/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/) +* [Announcing Pebble SDK 4.0](/blog/2016/08/30/announcing-pebble-sdk4/) + +## New Resources + +To get you started, we have updated the following sections of the Pebble +Developer site with new content and information on designing and developing +for the new Pebble hardware platform: + +* A 2-part [*Rocky.js tutorial*](/tutorials/js-watchface-tutorial/part1/) - Learn + how to create watchfaces in JavaScript using Rocky.js. + +* An updated + [*Hardware Comparison*](/guides/tools-and-resources/hardware-information) + chart - See the hardware differences between all Pebble platforms. + +* [*AppExitReason API Guide*](/guides/user-interfaces/app-exit-reason/) - A new + guide with information on how to use the `AppExitReason` API. + +* [*AppGlance C API Guide*](/guides/user-interfaces/appglance-c/) - A new + guide describing how to use the AppGlance API to display information in the + system's launcher. + +* [*AppGlance PebbleKit JS API Guide*](/guides/user-interfaces/appglance-pebblekit-js/) - + A new guide describing how to use the AppGlance API to display information + in the system's launcher. + +* [*One Click Action Guide*](/guides/design-and-interaction/one-click-actions/) - + A new guide with information on how to use one-click actions in watchapps. + +* [*UnobstuctedArea API Guide*](/guides/user-interfaces/unobstructed-area) - A + new guide that will demonstrate the basics of the `UnobstructedArea` API, and + how to use it to create watchfaces that respond to Timeline Quick View events. diff --git a/devsite/source/sdk4/index.html b/devsite/source/sdk4/index.html new file mode 100644 index 00000000..12769feb --- /dev/null +++ b/devsite/source/sdk4/index.html @@ -0,0 +1,39 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sidebar_narrow +title: SDK 4 +description: | + Learn more about the new APIs available in SDK 4 +sidebar_only: true +menu_section: none +--- + +
    + {% include search.html %} +
    +

    SDK 4

    +

    + Get your hands on the Pebble SDK 4 and start experimenting with + the latest APIs. Happy coding! +

    + +
    +
    diff --git a/devsite/source/search/index.html b/devsite/source/search/index.html new file mode 100644 index 00000000..f5fd9a93 --- /dev/null +++ b/devsite/source/search/index.html @@ -0,0 +1,23 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: sidebar_wide +scripts: + - search/page +menu_section: none +--- +
    +

    Use the search box above

    +
    diff --git a/devsite/source/tools/index.html b/devsite/source/tools/index.html new file mode 100644 index 00000000..969131b2 --- /dev/null +++ b/devsite/source/tools/index.html @@ -0,0 +1,18 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: utils/redirect_temporary +redirect_to: /tools/color-picker/ +--- diff --git a/devsite/source/tutorials/advanced/index.md b/devsite/source/tutorials/advanced/index.md new file mode 100644 index 00000000..2506d852 --- /dev/null +++ b/devsite/source/tutorials/advanced/index.md @@ -0,0 +1,18 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: utils/redirect_permanent +redirect_to: /tutorials/advanced/vector-animations/ +--- \ No newline at end of file diff --git a/devsite/source/tutorials/advanced/vector-animations.md b/devsite/source/tutorials/advanced/vector-animations.md new file mode 100644 index 00000000..c196a37a --- /dev/null +++ b/devsite/source/tutorials/advanced/vector-animations.md @@ -0,0 +1,466 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: advanced +tutorial_part: 1 + +title: Vector Animations +description: | + How to use vector images in icons and animations. +permalink: /tutorials/advanced/vector-animations/ +generate_toc: true +platform_choice: true +platforms: + - basalt + - chalk + - diorite + - emery +--- + +Some of the best Pebble apps make good use of the ``Animation`` and the +[`Graphics Context`](``Graphics``) to create beautiful and eye-catching user +interfaces that look better than those created with just the standard ``Layer`` +types. + +Taking a good design a step further may involve using the ``Draw Commands`` API +to load vector icons and images, and to animate them on a point-by-point basis +at runtime. An additional capability of the ``Draw Commands`` API is the draw +command sequence, allowing multiple frames to be incorporated into a single +resource and played out frame by frame. + +This tutorial will guide you through the process of using these types of image +files in your own projects. + + +## What Are Vector Images? + +As opposed to bitmaps which contain data for every pixel to be drawn, a vector +file contains only instructions about points contained in the image and how to +draw lines connecting them up. Instructions such as fill color, stroke color, +and stroke width are also included. + +Vector images on Pebble are implemented using the ``Draw Commands`` APIs, which +load and display PDC (Pebble Draw Command) images and sequences that contain +sets of these instructions. An example is the weather icon used in weather +timeline pins. The benefit of using vector graphics for this icon is that is +allows the image to stretch in the familiar manner as it moves between the +timeline view and the pin detail view: + +![weather >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather.png) + +By including two or more vector images in a single file, an animation can be +created to enable fast and detailed animated sequences to be played. Examples +can be seen in the Pebble system UI, such as when an action is completed: + +![action-completed >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/action-completed.gif) + +The main benefits of vectors over bitmaps for simple images and icons are: + +* Smaller resource size - instructions for joining points are less memory + expensive than per-pixel bitmap data. + +* Flexible rendering - vector images can be rendered as intended, or manipulated + at runtime to move the individual points around. This allows icons to appear + more organic and life-like than static PNG images. Scaling and distortion is + also made possible. + +* Longer animations - a side benefit of taking up less space is the ability to + make animations longer. + +However, there are also some drawbacks to choosing vector images in certain +cases: + +* Vector files require more specialized tools to create than bitmaps, and so are + harder to produce. + +* Complicated vector files may take more time to render than if they were simply + drawn per-pixel as a bitmap, depending on the drawing implementation. + + +## Creating Compatible Files + +The file format of vector image files on Pebble is the PDC (Pebble Draw Command) +format, which includes all the instructions necessary to allow drawing of +vectors. These files are created from compatible SVG (Scalar Vector Graphics) +files using the +[`svg2pdc`]({{site.links.examples_org}}/cards-example/blob/master/tools/svg2pdc.py) +tool. + +
    +Pebble Draw Command files can only be used from app resources, and cannot be +created at runtime. +
    + +To convert an SVG file to a PDC image of the same name: + +```bash +$ python svg2pdc.py image.svg +``` + +To create a PDCS (Pebble Draw Command Sequence) from individual SVG frames, +specify the directory containing the frames with the `--sequence` flag when +running `svg2pdc`: + +```bash +$ ls frames/ +1.svg 2.svg 3.svg +4.svg 5.svg + +$ python svg2pdc.py --sequence frames/ +``` + +In the example above, this will create an output file in the `frames` directory +called `frames.pdc` that contains draw command data for the complete animation. + +
    +{% markdown %} +**Limitations** + +The `svg2pdc` tool currently supports SVG files that use **only** the following +elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, `line`, `circle`. + +We recommend using Adobe Illustrator to create compatible SVG icons and images. +{% endmarkdown %} +
    + +For simplicity, compatible image and sequence files will be provided for you to +use in your own project. + + +### PDC icons + +Example PDC image files are available for the icons listed in +[*App Assets*](/guides/app-resources/app-assets/). +These are ideal for use in many common types of apps, such as notification or +weather apps. + +[Download PDC icon files >{center,bg-lightblue,fg-white}]({{ site.links.s3_assets }}/assets/other/pebble-timeline-icons-pdc.zip) + + +## Getting Started + +^CP^ Begin a new [CloudPebble]({{ site.links.cloudpebble }}) project using the +blank template and add code only to push an initial ``Window``, such as the +example below: + +^LC^ Begin a new project using `pebble new-project` and create a simple app that +pushes a blank ``Window``, such as the example below: + +```c +#include + +static Window *s_main_window; + +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + +} + +static void main_window_unload(Window *window) { + +} + +static void init() { + s_main_window = window_create(); + window_set_window_handlers(s_main_window, (WindowHandlers) { + .load = main_window_load, + .unload = main_window_unload, + }); + window_stack_push(s_main_window, true); +} + +static void deinit() { + window_destroy(s_main_window); +} + +int main() { + init(); + app_event_loop(); + deinit(); +} +``` + + +## Drawing a PDC Image + +For this tutorial, use the example +[`weather_image.pdc`](/assets/other/weather_image.pdc) file provided. + +^CP^ Add the PDC file as a project resource using the 'Add new' under +'Resources' on the left-hand side of the CloudPebble editor, with an +'Identifier' of `WEATHER_IMAGE`, and a type of 'raw binary blob'. The file is +assumed to be called `weather_image.pdc`. + +^LC^ Add the PDC file to your project resources in `package.json` as shown +below. Set the 'name' field to `WEATHER_IMAGE`, and the 'type' field to `raw`. +The file is assumed to be called `weather_image.pdc`: + +
    +{% highlight {} %} +"media": [ + { + "type": "raw", + "name": "WEATHER_IMAGE", + "file": "weather_image.pdc" + } +] +{% endhighlight %} +
    + +^LC^ Drawing a Pebble Draw Command image is just as simple as drawing a normal PNG +image to a graphics context, requiring only one draw call. First, load the +`.pdc` file from resources, for example with the `name` defined as +`WEATHER_IMAGE`, as shown below. + +^CP^ Drawing a Pebble Draw Command image is just as simple as drawing a normal +PNG image to a graphics context, requiring only one draw call. First, load the +`.pdc` file from resources, for example with the 'Identifier' defined as +`WEATHER_IMAGE`. This will be available in code as `RESOURCE_ID_WEATHER_IMAGE`, +as shown below. + +Declare a pointer of type ``GDrawCommandImage`` at the top of the file: + +```c +static GDrawCommandImage *s_command_image; +``` + +Create and assign the ``GDrawCommandImage`` in `init()`, before calling +`window_stack_push()`: + +```nc|c +static void init() { + /* ... */ + + // Create the object from resource file + s_command_image = gdraw_command_image_create_with_resource(RESOURCE_ID_WEATHER_IMAGE); + + /* ... */ +} +``` + +Next, define the ``LayerUpdateProc`` that will be used to draw the PDC image: + +```c +static void update_proc(Layer *layer, GContext *ctx) { + // Set the origin offset from the context for drawing the image + GPoint origin = GPoint(10, 20); + + // Draw the GDrawCommandImage to the GContext + gdraw_command_image_draw(ctx, s_command_image, origin); +} +``` + +Next, create a ``Layer`` to display the image: + +```c +static Layer *s_canvas_layer; +``` + +Next, set the ``LayerUpdateProc`` that will do the rendering and add it to the +desired ``Window``: + +```c +static void main_window_load(Window *window) { + + /* ... */ + + // Create the canvas Layer + s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h)); + + // Set the LayerUpdateProc + layer_set_update_proc(s_canvas_layer, update_proc); + + // Add to parent Window + layer_add_child(window_layer, s_canvas_layer); +} +``` + +Finally, don't forget to free the memory used by the ``Window``'s sub-components +in `main_window_unload()`: + +```c +static void main_window_unload(Window *window) { + layer_destroy(s_canvas_layer); + gdraw_command_image_destroy(s_command_image); +} +``` + +When run, the PDC image will be loaded, and rendered in the ``LayerUpdateProc``. +To put the image into contrast, we will finally change the ``Window`` background +color after `window_create()`: + +```c +window_set_background_color(s_main_window, GColorBlueMoon); +``` + +The result will look similar to the example shown below. + +![weather-image >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather-image.png) + + +## Playing a PDC Sequence + +The ``GDrawCommandSequence`` API allows developers to use vector graphics as +individual frames in a larger animation. Just like ``GDrawCommandImage``s, each +``GDrawCommandFrame`` is drawn to a graphics context in a ``LayerUpdateProc``. + +For this tutorial, use the example +[`clock_sequence.pdc`](/assets/other/clock_sequence.pdc) file provided. + +Begin a new app, with a C file containing the [template](#getting-started) provided above. + +^CP^ Next, add the file as a `raw` resource in the same way as for a PDC image, +for example with an `Identifier` specified as `CLOCK_SEQUENCE`. + +^LC^ Next, add the file as a `raw` resource in the same way as for a PDC image, +for example with the `name` field specified in `package.json` as +`CLOCK_SEQUENCE`. + +
    +{% highlight {} %} +"media": [ + { + "type": "raw", + "name": "CLOCK_SEQUENCE", + "file": "clock_sequence.pdc" + } +] +{% endhighlight %} +
    + +Load the PDCS in your app by first declaring a ``GDrawCommandSequence`` pointer: + +```c +static GDrawCommandSequence *s_command_seq; +``` + +Next, initialize the object in `init()` before calling `window_stack_push()`: + +```nc|c +static void init() { + /* ... */ + + // Load the sequence + s_command_seq = gdraw_command_sequence_create_with_resource(RESOURCE_ID_CLOCK_SEQUENCE); + + /* ... */ +} +``` + +Get the next frame and draw it in the ``LayerUpdateProc``. Then register a timer +to draw the next frame: + +```c +// Milliseconds between frames +#define DELTA 13 + +static int s_index = 0; + +/* ... */ + +static void next_frame_handler(void *context) { + // Draw the next frame + layer_mark_dirty(s_canvas_layer); + + // Continue the sequence + app_timer_register(DELTA, next_frame_handler, NULL); +} + +static void update_proc(Layer *layer, GContext *ctx) { + // Get the next frame + GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(s_command_seq, s_index); + + // If another frame was found, draw it + if (frame) { + gdraw_command_frame_draw(ctx, s_command_seq, frame, GPoint(0, 30)); + } + + // Advance to the next frame, wrapping if neccessary + int num_frames = gdraw_command_sequence_get_num_frames(s_command_seq); + s_index++; + if (s_index == num_frames) { + s_index = 0; + } +} +``` + +Next, create a new ``Layer`` to utilize the ``LayerUpdateProc`` and add it to the +desired ``Window``. + +Create the `Window` pointer: + +```c +static Layer *s_canvas_layer; +``` + +Next, create the ``Layer`` and assign it to the new pointer. Set its update +procedure and add it to the ``Window``: + +```c +static void main_window_load(Window *window) { + // Get Window information + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + // Create the canvas Layer + s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h)); + + // Set the LayerUpdateProc + layer_set_update_proc(s_canvas_layer, update_proc); + + // Add to parent Window + layer_add_child(window_layer, s_canvas_layer); +} +``` + +Start the animation loop using a timer at the end of initialization: + +```c +// Start the animation +app_timer_register(DELTA, next_frame_handler, NULL); +``` + +Finally, remember to destroy the ``GDrawCommandSequence`` and ``Layer`` in +`main_window_unload()`: + +```c +static void main_window_unload(Window *window) { + layer_destroy(s_canvas_layer); + gdraw_command_sequence_destroy(s_command_seq); +} +``` + +When run, the animation will be played by the timer at a framerate dictated by +`DELTA`, looking similar to the example shown below: + +![pdcs-example >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/pdcs-example.gif) + + +## What's Next? + +You have now learned how to add vector images and animations to your apps. +Complete examples for these APIs are available under the `pebble-examples` +GitHub organization: + +* [`pdc-image`]({{site.links.examples_org}}/pdc-image) - Example + implementation of a Pebble Draw Command Image. + +* [`pdc-sequence`]({{site.links.examples_org}}/pdc-sequence) - Example + implementation of a Pebble Draw Command Sequence animated icon. + + +More advanced tutorials will be added here in the future, so keep checking back! diff --git a/devsite/source/tutorials/index.html b/devsite/source/tutorials/index.html new file mode 100644 index 00000000..21176197 --- /dev/null +++ b/devsite/source/tutorials/index.html @@ -0,0 +1,106 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: default +title: Tutorials +description: | + Get started with Pebble development! +menu_section: tutorials +--- +
    +
    + +
    +
    + + +
    +
    +
    +

    Build a Watchface

    +

    Learn how to create your first watchface. This tutorial will cover basic Pebble concepts, and is the recommended starting point for new developers.

    + Build with JS + Build with C +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +

    Build a One Click Action

    +

    Learn how to create your first one click action watchapp. This guide explains how to create a watchapp that will makes a web request upon launch and display the result.

    + Build with C +
    +
    +
    + + +
    +
    + +
    + +
    + +
    +
    +
    +

    Learn C with Pebble

    +

    A community driven, open source textbook that teaches the fundamentals of C through the scope of Pebble application development.

    + Read the Book +
    +
    +
    + +
    +
    +
    +

    Publish Your App

    +

    Learn how to publish your watchface or watchapp on Pebble's appstore.

    + Publish an App +
    +
    +
    + +
    +
    +
    +

    Go Beyond

    +

    If you're looking to take the next step with your Pebble development, we encourage you to checkout the following resources:

    +

    +

    +

    +
    +
    +
    + +
    +
    diff --git a/devsite/source/tutorials/js-watchface-tutorial/index.html b/devsite/source/tutorials/js-watchface-tutorial/index.html new file mode 100644 index 00000000..18beed39 --- /dev/null +++ b/devsite/source/tutorials/js-watchface-tutorial/index.html @@ -0,0 +1,18 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: utils/redirect_permanent +redirect_to: /tutorials/js-watchface-tutorial/part1/ +--- \ No newline at end of file diff --git a/devsite/source/tutorials/js-watchface-tutorial/part1.md b/devsite/source/tutorials/js-watchface-tutorial/part1.md new file mode 100644 index 00000000..019ac188 --- /dev/null +++ b/devsite/source/tutorials/js-watchface-tutorial/part1.md @@ -0,0 +1,491 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: js-watchface +tutorial_part: 1 + +title: Build a Watchface in JavaScript using Rocky.js +description: A guide to making a new Pebble watchface with Rocky.js +permalink: /tutorials/js-watchface-tutorial/part1/ +menu_section: tutorials +generate_toc: true +platform_choice: true +--- + +{% include tutorials/rocky-js-warning.html %} + +In this tutorial we'll cover the basics of writing a simple watchface with +Rocky.js, Pebble's JavaScript API. Rocky.js enables developers to create +beautiful and feature-rich watchfaces with a modern programming language. + +Rocky.js should not be confused with Pebble.js which also allowed developers to +write applications in JavaScript. Unlike Pebble.js, Rocky.js runs natively on +the watch and is now the only offically supported method for developing +JavaScript applications for Pebble smartwatches. + +We're going to start with some basics, then create a simple digital watchface +and finally create an analog clock which looks just like this: + +![rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/js-watchface-tutorial/tictoc.png) + +## First Steps + +^CP^ Go to [CloudPebble]({{ site.links.cloudpebble }}) and click +'Get Started' to log in using your Pebble account, or create a new one if you do +not already have one. Once you've logged in, click 'Create' to create a new +project. Give your project a suitable name, such as 'Tutorial 1' and set the +'Project Type' as 'Rocky.js (beta)'. This will create a completely empty +project, so before you continue, you will need to click the 'Add New' button in +the left menu to create a new Rocky.js JavaScript file. + +^CP^ Next we need to change our project from a watchapp to a watchface. Click +'Settings' in the left menu, then change the 'APP KIND' to 'watchface'. + +
    +{% markdown {} %} +If you haven't already, head over the [SDK Page](/sdk/install/) to learn how to +download and install the latest version of the Pebble Tool, and the latest SDK. + +Once you've installed the Pebble Tool and SDK 4.0, you can create a new Rocky.js +project with the following command: + +```nc|text +$ pebble new-project --rocky helloworld +``` + +This will create a new folder called `helloworld` and populate it with the basic +structure required for a basic Rocky.js application. +{% endmarkdown %} +
    + + +## Watchface Basics + +Watchface are essentially long running applications that update the display at +a regular interval (typically once a minute, or when specific events occur). By +minimizing the frequency that the screen is updated, we help to conserve +battery life on the watch. + +^CP^ We'll start by editing the `index.js` file that we created earlier. Click +on the filename in the left menu and it will load, ready for editing. + +^LC^ The main entry point for the watchface is `/src/rocky/index.js`, so we'll +start by editing this file. + +The very first thing we must do is include the Rocky.js library, which gives us +access to the APIs we need to create a Pebble watchface. + +```js +var rocky = require('rocky'); +``` + +Next, the invocation of `rocky.on('minutechange', ...)` registers a callback +method to the `minutechange` event - which is emitted every time the internal +clock's minute changes (and also when the handler is registered). Watchfaces +should invoke the ``requestDraw`` method as part of the `minutechange` event to +redraw the screen. + +```js +rocky.on('minutechange', function(event) { + rocky.requestDraw(); +}); +``` + +> **NOTE**: Watchfaces that need to update more or less frequently can also +> register the `secondchange`, `hourchange` or `daychange` events. + +Next we register a callback method to the `draw` event - which is emitted after +each call to `rocky.requestDraw()`. The `event` parameter passed into the +callback function includes a ``CanvasRenderingContext2D`` object, which is used +to determine the display characteristics and draw text or shapes on the display. + +```js +rocky.on('draw', function(event) { + // Get the CanvasRenderingContext2D object + var ctx = event.context; +}); +``` + +The ``RockyDrawCallback`` is where we render the smartwatch display, using the +methods provided to us through the ``CanvasRenderingContext2D`` object. + +> **NOTE**: The `draw` event may also be emitted at other times, such +as when the handler is first registered. + +## Creating a Digital Watchface + +In order to create a simple digital watchface, we will need to do the following +things: + +- Subscribe to the `minutechange` event. +- Subscribe to the `draw` event, so we can update the display. +- Clear the display each time we draw on the screen. +- Determine the width and height of the available content area of the screen. +- Obtain the current date and time. +- Set the text color to white. +- Center align the text. +- Display the current time, using the width and height to determine the center +point of the screen. + +^CP^ To create our minimal watchface which displays the current time, let's +replace the contents of our `index.js` file with the following code: + +^LC^ To create our minimal watchface which displays the current time, let's +replace the contents of `/src/rocky/index.js` with the following code: + +```js +var rocky = require('rocky'); + +rocky.on('draw', function(event) { + // Get the CanvasRenderingContext2D object + var ctx = event.context; + + // Clear the screen + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + + // Determine the width and height of the display + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + + // Current date/time + var d = new Date(); + + // Set the text color + ctx.fillStyle = 'white'; + + // Center align the text + ctx.textAlign = 'center'; + + // Display the time, in the middle of the screen + ctx.fillText(d.toLocaleTimeString(), w / 2, h / 2, w); +}); + +rocky.on('minutechange', function(event) { + // Display a message in the system logs + console.log("Another minute with your Pebble!"); + + // Request the screen to be redrawn on next pass + rocky.requestDraw(); +}); +``` + +## First Compilation and Installation + +^CP^ To compile the watchface, click the 'PLAY' button on the right hand side +of the screen. This will save your file, compile the project and launch your +watchface in the emulator. + +^CP^ Click the 'VIEW LOGS' button. + +
    +{% markdown {} %} +To compile the watchface, make sure you have saved your project files, then +run the following command from the project's root directory: + +```nc|text +$ pebble build +``` + +After a successful compilation you will see a message reading `'build' finished +successfully`. + +If there are any problems with your code, the compiler will tell you which lines +contain an error, so you can fix them. See +[Troubleshooting and Debugging](#troubleshooting-and-debugging) for further +information. + +Now install the watchapp and view the logs on the emulator by running: + +```nc|text +$ pebble install --logs --emulator basalt +``` +{% endmarkdown %} +
    + +## Congratulations! + +You should see a loading bar as the watchface is loaded, shortly followed by +your watchface running in the emulator. + +![rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/js-watchface-tutorial/rocky-time.png) + +Your logs should also be displaying the message we told it to log with +`console.log()`. + +```nc|text +Another minute with your Pebble! +``` + +> Note: You should prevent execution of the log statements by commenting the +code, if you aren't using them. e.g. `//console.log();` + +## Creating an Analog Watchface + +In order to draw an analog watchface, we will need to do the following things: + +- Subscribe to the `minutechange` event. +- Subscribe to the `draw` event, so we can update the display. +- Obtain the current date and time. +- Clear the display each time we draw on the screen. +- Determine the width and height of the available content area of the screen. +- Use the width and height to determine the center point of the screen. +- Calculate the max length of the watch hands based on the available space. +- Determine the correct angle for minutes and hours. +- Draw the minute and hour hands, outwards from the center point. + +### Drawing the Hands + +We're going to need to draw two lines, one representing the hour hand, and one +representing the minute hand. + +We need to implement a function to draw the hands, to prevent duplicating the +same drawing code for hours and minutes. We're going to use a series of +``CanvasRenderingContext2D`` methods to accomplish the desired effect. + +First we need to find the center point in our display: + +```js +// Determine the available width and height of the display +var w = ctx.canvas.unobstructedWidth; +var h = ctx.canvas.unobstructedHeight; + +// Determine the center point of the display +var cx = w / 2; +var cy = h / 2; +``` + +Now we know the starting point for the hands (`cx`, `cy`), but we still need to +determine the end point. We can do this with a tiny bit of math: + +```js +var x2 = cx + Math.sin(angle) * length; +var y2 = cy - Math.cos(angle) * length; +``` + +Then we'll use the `ctx` parameter and configure the line width and color of +the hand. + +```js +// Configure how we want to draw the hand +ctx.lineWidth = 8; +ctx.strokeStyle = color; +``` + +Finally we draw the hand, starting from the center of the screen, drawing a +straight line outwards. + +```js +// Begin drawing +ctx.beginPath(); + +// Move to the center point, then draw the line +ctx.moveTo(cx, cy); +ctx.lineTo(x2, y2); + +// Stroke the line (output to display) +ctx.stroke(); +``` + +### Putting It All Together + +```js +var rocky = require('rocky'); + +function fractionToRadian(fraction) { + return fraction * 2 * Math.PI; +} + +function drawHand(ctx, cx, cy, angle, length, color) { + // Find the end points + var x2 = cx + Math.sin(angle) * length; + var y2 = cy - Math.cos(angle) * length; + + // Configure how we want to draw the hand + ctx.lineWidth = 8; + ctx.strokeStyle = color; + + // Begin drawing + ctx.beginPath(); + + // Move to the center point, then draw the line + ctx.moveTo(cx, cy); + ctx.lineTo(x2, y2); + + // Stroke the line (output to display) + ctx.stroke(); +} + +rocky.on('draw', function(event) { + var ctx = event.context; + var d = new Date(); + + // Clear the screen + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + + // Determine the width and height of the display + var w = ctx.canvas.unobstructedWidth; + var h = ctx.canvas.unobstructedHeight; + + // Determine the center point of the display + // and the max size of watch hands + var cx = w / 2; + var cy = h / 2; + + // -20 so we're inset 10px on each side + var maxLength = (Math.min(w, h) - 20) / 2; + + // Calculate the minute hand angle + var minuteFraction = (d.getMinutes()) / 60; + var minuteAngle = fractionToRadian(minuteFraction); + + // Draw the minute hand + drawHand(ctx, cx, cy, minuteAngle, maxLength, "white"); + + // Calculate the hour hand angle + var hourFraction = (d.getHours() % 12 + minuteFraction) / 12; + var hourAngle = fractionToRadian(hourFraction); + + // Draw the hour hand + drawHand(ctx, cx, cy, hourAngle, maxLength * 0.6, "lightblue"); +}); + +rocky.on('minutechange', function(event) { + // Request the screen to be redrawn on next pass + rocky.requestDraw(); +}); +``` + +Now compile and run your project in the emulator to see the results! + + +## Troubleshooting and Debugging + +If your build didn't work, you'll see the error message: `Build Failed`. Let's +take a look at some of the common types of errors: + + +### Rocky.js Linter + +As part of the build process, your Rocky `index.js` file is automatically +checked for errors using a process called +['linting'](https://en.wikipedia.org/wiki/Lint_%28software%29). + +The first thing to check is the 'Lint Results' section of the build output. + +```nc|text +========== Lint Results: index.js ========== + +src/rocky/index.js(7,39): error TS1005: ',' expected. +src/rocky/index.js(9,8): error TS1005: ':' expected. +src/rocky/index.js(9,37): error TS1005: ',' expected. +src/rocky/index.js(7,1): warning TS2346: Supplied parameters do not match any signature of call target. +src/rocky/index.js(7,24): warning TS2304: Cannot find name 'funtion'. + +Errors: 3, Warnings: 2 +Please fix the issues marked with 'error' above. +``` + +In the error messages above, we see the filename which contains the error, +followed by the line number and column number where the error occurs. For +example: + +```nc|text +Filename: src/rocky/index.js +Line number: 7 +Character: 24 +Description: Cannot find name 'funtion'. +``` + +```javascript +rocky.on('minutechange', funtion(event) { + // ... +}); +``` + +As we can see, this error relates to a typo, 'funtion' should be 'function'. +Once this error has been fixed and you run `pebble build` again, you should +see: + +```nc|text +========== Lint Results: index.js ========== + +Everything looks AWESOME! +``` + +### Locating Errors Using Logging + +So what do we do when the build is successful, but our code isn't functioning as +expected? Logging! + +Scatter a breadcrumb trail through your application code, that you can follow as +your application is running. This will help to narrow down the location of +the problem. + +```javascript +rocky.on('minutechange', function(event) { + console.log('minutechange fired!'); + // ... +}); +``` +Once you've added your logging statements, rebuild the application and view the +logs: + +^CP^ Click the 'PLAY' button on the right hand side of the screen, then click +the 'VIEW LOGS' button. + +
    +{% markdown {} %} +```nc|text +$ pebble build && pebble install --emulator basalt --logs +``` +{% endmarkdown %} +
    + +If you find that one of your logging statements hasn't appeared in the log +output, it probably means there is an issue in the preceding code. + +### I'm still having problems! + +If you've tried the steps above and you're still having problems, there are +plenty of places to get help. You can post your question and code on the +[Pebble Forums](https://forums.pebble.com/c/development) or join our +[Discord Server]({{ site.links.discord_invite }}) and ask for assistance. + + +## Conclusion + +So there we have it, the basic process required to create a brand new Pebble +watchface using JavaScript! To do this we: + +1. Created a new Rocky.js project. +2. Included the `'rocky'` library. +3. Subscribed to the `minutechange` event. +4. Subscribed to the `draw` event. +5. Used drawing commands to draw text and lines on the display. + +If you have problems with your code, check it against the sample source code +provided using the button below. + +[View Source Code >{center,bg-lightblue,fg-white}](https://github.com/pebble-examples/rocky-watchface-tutorial-part1) + +## What's Next + +If you successfully built and run your application, you should have seen a very +basic watchface that closely mimics the built-in TicToc. In the next tutorial, +we'll use `postMessage` to pass information to the mobile device, and +request weather data from the web. + +[Go to Part 2 → >{wide,bg-dark-red,fg-white}](/tutorials/js-watchface-tutorial/part2/) diff --git a/devsite/source/tutorials/js-watchface-tutorial/part2.md b/devsite/source/tutorials/js-watchface-tutorial/part2.md new file mode 100644 index 00000000..1e459cf9 --- /dev/null +++ b/devsite/source/tutorials/js-watchface-tutorial/part2.md @@ -0,0 +1,380 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: js-watchface +tutorial_part: 2 + +title: Adding Web Content to a Rocky.js JavaScript Watchface +description: A guide to adding web content to a JavaScript watchface +permalink: /tutorials/js-watchface-tutorial/part2/ +menu_section: tutorials +generate_toc: true +platform_choice: true +--- + +{% include tutorials/rocky-js-warning.html %} + +In the [previous tutorial](/tutorials/js-watchface-tutorial/part1), we looked +at the process of creating a basic watchface using Pebble's new JavaScript API. + +In this tutorial, we'll extend the example to add weather conditions from the +Internet to our watchface. + +![rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/js-watchface-tutorial/tictoc-weather.png) + +We'll be using the JavaScript component `pkjs`, which runs on the user's mobile +device using [PebbleKit JS](/docs/pebblekit-js). This `pkjs` component can be +used to access information from the Internet and process it on the phone. This +`pkjs` environment does not have the same the hardware and memory constraints of +the Pebble. + +## First Steps + +^CP^ The first thing we'll need to do is add a new JavaScript file to the +project we created in [Part 1](/tutorials/js-watchface-tutorial/part1). Click +'Add New' in the left menu, set the filename to `index.js` and the 'TARGET' to +'PebbleKit JS'. + +^LC^ The first thing we'll need to do is edit a file from the project we +created in [Part 1](/tutorials/js-watchface-tutorial/part1). The file is +called `/src/pkjs/index.js` and it is the entry point for the `pkjs` portion +of the application. + +This `pkjs` component of our application is capable of sending and receiving +messages with the smartwatch, accessing the user's location, making web +requests, and an assortment of other tasks that are all documented in the +[PebbleKit JS](/docs/pebblekit-js) documentation. + +> Although Rocky.js (watch) and `pkjs` (phone) both use JavaScript, they +> have separate APIs and purposes. It is important to understand the differences +> and not attempt to run your code within the wrong component. + +## Sending and Receiving Messages + +Before we get onto the example, it's important to understand how to send and +receive messages between the Rocky.js component on the smartwatch, and the +`pkjs` component on the mobile device. + +### Sending Messages + +To send a message from the smartwatch to the mobile device, use the +``rocky.postMessage`` method, which allows you to send an arbitrary JSON +object: + +```js +// rocky index.js +var rocky = require('rocky'); + +// Send a message from the smartwatch +rocky.postMessage({'test': 'hello from smartwatch'}); +``` + +To send a message from the mobile device to the smartwatch, use the +``Pebble.postMessage`` method: + +```js +// pkjs index.js + +// Send a message from the mobile device +Pebble.postMessage({'test': 'hello from mobile device'}); +``` + +### Message Listeners + +We can create a message listener in our smartwatch code using the ``rocky.on`` +method: + +```js +// rocky index.js + +// On the smartwatch, begin listening for a message from the mobile device +rocky.on('message', function(event) { + // Get the message that was passed + console.log(JSON.stringify(event.data)); +}); +``` + +We can also create a message listener in our `pkjs` code using the ``Pebble.on`` +method: + +```js +// pkjs index.js + +// On the phone, begin listening for a message from the smartwatch +Pebble.on('message', function(event) { + // Get the message that was passed + console.log(JSON.stringify(event.data)); +}); +``` + +## Requesting Location + +Our `pkjs` component can access to the location of the user's smartphone. The +Rocky.js component cannot access location information directly, it must request +it from `pkjs`. + +^CP^ In order to use this functionality, you must change your project settings +in CloudPebble. Click 'SETTINGS' in the left menu, then tick 'USES LOCATION'. + +
    +{% markdown {} %} +In order to use this functionality, your application must include the +`location` flag in the +[`pebble.capabilities`](/guides/tools-and-resources/app-metadata/) +array of your `package.json` file. + + +```js +// file: package.json +// ... + "pebble": { + "capabilities": ["location"] + } +// ... +``` +{% endmarkdown %} +
    + +Once we've added the `location` flag, we can access GPS coordinates using the +[Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation). +In this example, we're going to request the user's location when we receive the +"fetch" message from the smartwatch. + +```js +// pkjs index.js + +Pebble.on('message', function(event) { + // Get the message that was passed + var message = event.data; + + if (message.fetch) { + navigator.geolocation.getCurrentPosition(function(pos) { + // TODO: fetch weather + }, function(err) { + console.error('Error getting location'); + }, + { timeout: 15000, maximumAge: 60000 }); + } +}); +``` + +## Web Service Calls + +The `pkjs` side of our application can also access the +[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) +object. Using this object, developers are able to interact with external web +services. + +In this tutorial, we will interface with +[Open Weather Map](http://openweathermap.org/) – a common weather API used by +the [Pebble Developer Community](https://forums.pebble.com/c/development). + +The `XMLHttpRequest` object is quite powerful, but can be intimidating to get +started with. To make things a bit simpler, we'll wrap the object with a helper +function which makes the request, then raises a callback: + +```js +// pkjs index.js + +function request(url, type, callback) { + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + callback(this.responseText); + }; + xhr.open(type, url); + xhr.send(); +} +``` + +The three arguments we have to provide when calling our `request()` method are +the URL, the type of request (`GET` or `POST`) and a callback for when the +response is received. + +### Fetching Weather Data + +The URL is specified on the +[OpenWeatherMap API page](http://openweathermap.org/current), and contains the +coordinates supplied by `getCurrentPosition()` (latitude and longitude), +followed by the API key: + +{% include guides/owm-api-key-notice.html %} + +```js +var myAPIKey = '1234567'; +var url = 'http://api.openweathermap.org/data/2.5/weather' + + '?lat=' + pos.coords.latitude + + '&lon=' + pos.coords.longitude + + '&appid=' + myAPIKey; +``` + +All together, our message handler should now look like the following: + +```js +// pkjs index.js + +var myAPIKey = '1234567'; + +Pebble.on('message', function(event) { + // Get the message that was passed + var message = event.data; + + if (message.fetch) { + navigator.geolocation.getCurrentPosition(function(pos) { + var url = 'http://api.openweathermap.org/data/2.5/weather' + + '?lat=' + pos.coords.latitude + + '&lon=' + pos.coords.longitude + + '&appid=' + myAPIKey; + + request(url, 'GET', function(respText) { + var weatherData = JSON.parse(respText); + //TODO: Send weather to smartwatch + }); + }, function(err) { + console.error('Error getting location'); + }, + { timeout: 15000, maximumAge: 60000 }); + } +}); +``` + +## Finishing Up + +Once we receive the weather data from OpenWeatherMap, we need to send it to the +smartwatch using ``Pebble.postMessage``: + +```js +// pkjs index.js + +// ... +request(url, 'GET', function(respText) { + var weatherData = JSON.parse(respText); + + Pebble.postMessage({ + 'weather': { + // Convert from Kelvin + 'celcius': Math.round(weatherData.main.temp - 273.15), + 'fahrenheit': Math.round((weatherData.main.temp - 273.15) * 9 / 5 + 32), + 'desc': weatherData.weather[0].main + } + }); +}); +``` + +On the smartwatch, we'll need to create a message handler to listen for a +`weather` message, and store the information so it can be drawn on screen. + +```js +// rocky index.js +var rocky = require('rocky'); + +// Global object to store weather data +var weather; + +rocky.on('message', function(event) { + // Receive a message from the mobile device (pkjs) + var message = event.data; + + if (message.weather) { + // Save the weather data + weather = message.weather; + + // Request a redraw so we see the information + rocky.requestDraw(); + } +}); +``` + +We also need to send the 'fetch' command from the smartwatch to ask for weather +data when the application starts, then every hour: + +```js +// rocky index.js + +// ... + +rocky.on('hourchange', function(event) { + // Send a message to fetch the weather information (on startup and every hour) + rocky.postMessage({'fetch': true}); +}); +``` + +Finally, we'll need some new code in our Rocky `draw` handler to display the +temperature and conditions: + +```js +// rocky index.js +var rocky = require('rocky'); + +// ... + +function drawWeather(ctx, weather) { + // Create a string describing the weather + //var weatherString = weather.celcius + 'ºC, ' + weather.desc; + var weatherString = weather.fahrenheit + 'ºF, ' + weather.desc; + + // Draw the text, top center + ctx.fillStyle = 'lightgray'; + ctx.textAlign = 'center'; + ctx.font = '14px Gothic'; + ctx.fillText(weatherString, ctx.canvas.unobstructedWidth / 2, 2); +} + +rocky.on('draw', function(event) { + var ctx = event.context; + var d = new Date(); + + // Clear the screen + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + + // Draw the conditions (before clock hands, so it's drawn underneath them) + if (weather) { + drawWeather(ctx, weather); + } + + // ... +}); +``` + +## Conclusion + +So there we have it, we successfully added web content to our JavaScript +watchface! To do this we: + +1. Enabled `location` in our `package.json`. +2. Added a `Pebble.on('message', function() {...});` listener in `pkjs`. +3. Retrieved the users current GPS coordinates in `pkjs`. +4. Used `XMLHttpRequest` to query OpenWeatherMap API. +5. Sent the current weather conditions from the mobile device, to the +smartwatch, using `Pebble.postMessage()`. +6. On the smartwatch, we created a `rocky.on('message', function() {...});` +listener to receive the weather data from `pkjs`. +7. We subscribed to the `hourchange` event, to send a message to `pkjs` to +request the weather data when the application starts and every hour. +8. Then finally we drew the weather conditions on the screen as text. + +If you have problems with your code, check it against the sample source code +provided using the button below. + +[View Source Code >{center,bg-lightblue,fg-white}](https://github.com/pebble-examples/rocky-watchface-tutorial-part2) + +## What's Next + +We hope you enjoyed this tutorial and that it inspires you to make something +awesome! + +Why not let us know what you've created by tweeting +[@pebbledev](https://twitter.com/pebbledev), or join our epic developer +community on [Discord]({{ site.links.discord_invite }}). diff --git a/devsite/source/tutorials/watchface-tutorial/index.md b/devsite/source/tutorials/watchface-tutorial/index.md new file mode 100644 index 00000000..8258791e --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/index.md @@ -0,0 +1,18 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: utils/redirect_permanent +redirect_to: /tutorials/watchface-tutorial/part1/ +--- \ No newline at end of file diff --git a/devsite/source/tutorials/watchface-tutorial/part1.md b/devsite/source/tutorials/watchface-tutorial/part1.md new file mode 100644 index 00000000..d8af5283 --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/part1.md @@ -0,0 +1,475 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: watchface +tutorial_part: 1 + +title: Build Your Own Watchface in C +description: A guide to making a new Pebble watchface with the Pebble C API +permalink: /tutorials/watchface-tutorial/part1/ +menu_section: tutorials +generate_toc: true +platform_choice: true +--- + +In this tutorial we'll cover the basics of writing a simple watchface with +Pebble's C API. Customizability is at the heart of the Pebble philosophy, so +we'll be sure to add some exciting features for the user! + +When we are done this section of the tutorial, you should end up with a brand +new basic watchface looking something like this: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/1-time.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## First Steps + +So, let's get started! + +^CP^ Go to [CloudPebble]({{ site.links.cloudpebble }}) and click 'Get Started' +to log in using your Pebble account, or create a new one if you do not already +have one. Next, click 'Create' to create a new project. Give your project a +suitable name, such as 'Tutorial 1' and leave the 'Project Type' as 'Pebble C +SDK', with a 'Template' of 'Empty project', as we will be starting from scratch +to help maximize your understanding as we go. + +^LC^ Before you can start the tutorial you will need to have the Pebble SDK +installed. If you haven't done this yet, go to our [download page](/sdk) to grab +the SDK and follow the instructions to install it on your machine. Once you've +done that you can come back here and carry on where you left off. + +^LC^ Once you have installed the SDK, navigate to a directory of your choosing +and run `pebble new-project watchface` (where 'watchface' is the name of your +new project) to start a new project and set up all the relevant files. + +^CP^ Click 'Create' and you will see the main CloudPebble project screen. The +left menu shows all the relevant links you will need to create your watchface. +Click on 'Settings' and you will see the name you just supplied, along with +several other options. As we are creating a watchface, change the 'App Kind' to +'Watchface'. + +^LC^ In an SDK project, all the information about how an app is configured (its +name, author, capabilities and resource listings etc) is stored in a file in the +project root directory called `package.json`. Since this project will be a +watchface, you will need to modify the `watchapp` object in this file to reflect +this: + +
    +{% highlight {} %} +"watchapp": { + "watchface": true +} +{% endhighlight %} +
    + +The main difference between the two kinds are that watchfaces serve as the +default display on the watch, with the Up and Down buttons allowing use of the +Pebble timeline. This means that these buttons are not available for custom +behavior (Back and Select are also not available to watchfaces). In contrast, +watchapps are launched from the Pebble system menu. These have more capabilities +such as button clicks and menu elements, but we will come to those later. + +^CP^ Finally, set your 'Company Name' and we can start to write some code! + +^LC^ Finally, set a value for `companyName` and we can start to write some code! + + +## Watchface Basics + +^CP^ Create the first source file by clicking 'Add New' on the left menu, +selecting 'C file' as the type and choosing a suitable name such as 'main.c'. +Click 'Create' and you will be shown the main editor screen. + +^LC^ Our first source file is already created for you by the `pebble` command +line tool and lives in the project's `src` directory. By default, this file +contains sample code which you can safely remove, since we will be starting from +scratch. Alternatively, you can avoid this by using the `--simple` flag when +creating the project. + +Let's add the basic code segments which are required by every watchapp. The +first of these is the main directive to use the Pebble SDK at the top of the +file like so: + +```c +#include +``` + +After this first line, we must begin with the recommended app structure, +specifically a standard C `main()` function and two other functions to help us +organize the creation and destruction of all the Pebble SDK elements. This helps +make the task of managing memory allocation and deallocation as simple as +possible. Additionally, `main()` also calls ``app_event_loop()``, which lets the +watchapp wait for system events until it exits. + +^CP^ The recommended structure is shown below, and you can use it as the basis +for your own watchface file by copying it into CloudPebble: + +^LC^ The recommended structure is shown below, and you can use it as the basis +for your main C file: + +```c +#include + +static void init() { + +} + +static void deinit() { + +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} +``` + +To add the first ``Window``, we first declare a static pointer to a ``Window`` +variable, so that we can access it wherever we need to, chiefly in the `init()` +and `deinit()` functions. Add this declaration below `#include`, prefixed with +`s_` to denote its `static` nature (`static` here means it is accessible only +within this file): + +```c +static Window *s_main_window; +``` + +The next step is to create an instance of ``Window`` to assign to this pointer, +which we will do in `init()` using the appropriate Pebble SDK functions. In this +process we also assign two handler functions that provide an additional layer of +abstraction to manage the subsequent creation of the ``Window``'s sub-elements, +in a similar way to how `init()` and `deinit()` perform this task for the +watchapp as a whole. These two functions should be created above `init()` and +must match the following signatures (the names may differ, however): + +```c +static void main_window_load(Window *window) { + +} + +static void main_window_unload(Window *window) { + +} +``` + +With this done, we can complete the creation of the ``Window`` element, making +reference to these two new handler functions that are called by the system +whenever the ``Window`` is being constructed. This process is shown below, and +takes place in `init()`: + +```c +static void init() { + // Create main Window element and assign to pointer + s_main_window = window_create(); + + // Set handlers to manage the elements inside the Window + window_set_window_handlers(s_main_window, (WindowHandlers) { + .load = main_window_load, + .unload = main_window_unload + }); + + // Show the Window on the watch, with animated=true + window_stack_push(s_main_window, true); +} +``` + +A good best-practice to learn at this early stage is to match every Pebble SDK +`_create()` function call with the equivalent `_destroy()` function to make sure +all memory used is given back to the system when the app exits. Let's do this +now in `deinit()` for our main ``Window`` element: + +```c +static void deinit() { + // Destroy Window + window_destroy(s_main_window); +} +``` + +We can now compile and run this watchface, but it will not show anything +interesting yet. It is also a good practice to check that our code is still +valid after each iterative change, so let's do this now. + + +## First Compilation and Installation + +^CP^ To compile the watchface, make sure you have saved your C file by clicking +the 'Save' icon on the right of the editor screen and then proceed to the +'Compilation' screen by clicking the appropriate link on the left of the screen. +Click 'Run Build' to start the compilation process and wait for the result. +Hopefully the status should become 'Succeeded', meaning the code is valid and +can be run on the watch. + +^LC^ To compile the watchface, make sure you have saved your project files and +then run `pebble build` from the project's root directory. The installable +`.pbw` file will be deposited in the `build` directory. After a successful +compile you will see a message reading `'build' finished successfully`. If there +are any problems with your code, the compiler will tell you which lines are in +error so you can fix them. + +In order to install your watchface on your Pebble, first +[setup the Pebble Developer Connection](/guides/tools-and-resources/developer-connection/). +Make sure you are using the latest version of the Pebble app. + +^CP^ Click 'Install and Run' and wait for the app to install. + +^LC^ Install the watchapp by running `pebble install`, supplying your phone's IP +address with the `--phone` flag. For example: `pebble install +--phone 192.168.1.78`. + +
    +{% markdown {} %} +> Instead of using the --phone flag every time you install, set the PEBBLE_PHONE environment variable: +> `export PEBBLE_PHONE=192.168.1.78` and simply use `pebble install`. +{% endmarkdown %} +
    + +Congratulations! You should see that you have a new item in the watchface menu, +but it is entirely blank! + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/1-blank.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +Let's change that with the next stage towards a basic watchface - the +``TextLayer`` element. + + +## Showing Some Text + +^CP^ Navigate back to the CloudPebble code editor and open your main C file to +continue adding code. + +^LC^ Re-open your main C file to continue adding code. + +The best way to show some text on a watchface or watchapp +is to use a ``TextLayer`` element. The first step in doing this is to follow a +similar procedure to that used for setting up the ``Window`` with a pointer, +ideally added below `s_main_window`: + +```c +static TextLayer *s_time_layer; +``` + +This will be the first element added to our ``Window``, so we will make the +Pebble SDK function calls to create it in `main_window_load()`. After calling +``text_layer_create()``, we call other functions with plain English names that +describe exactly what they do, which is to help setup layout properties for the +text shown in the ``TextLayer`` including colors, alignment and font size. We +also include a call to ``text_layer_set_text()`` with "00:00" so that we can +verify that the ``TextLayer`` is set up correctly. + +The layout parameters will vary depending on the shape of the display. To easily +specify which value of the vertical position is used on each of the round and +rectangular display shapes we use ``PBL_IF_ROUND_ELSE()``. Thus +`main_window_load()` becomes: + +```c +static void main_window_load(Window *window) { + // Get information about the Window + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + // Create the TextLayer with specific bounds + s_time_layer = text_layer_create( + GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50)); + + // Improve the layout to be more like a watchface + text_layer_set_background_color(s_time_layer, GColorClear); + text_layer_set_text_color(s_time_layer, GColorBlack); + text_layer_set_text(s_time_layer, "00:00"); + text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD)); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + + // Add it as a child layer to the Window's root layer + layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); +} +``` + +Note the use of SDK values such as ``GColorBlack`` and `FONT_KEY_BITHAM_42_BOLD` +which allow use of built-in features and behavior. These examples here are the +color black and a built in system font. Later we will discuss loading a custom +font file, which can be used to replace this value. + +Just like with ``Window``, we must be sure to destroy each element we create. We +will do this in `main_window_unload()`, to keep the management of the +``TextLayer`` completely within the loading and unloading of the ``Window`` it +is associated with. This function should now look like this: + +```c +static void main_window_unload(Window *window) { + // Destroy TextLayer + text_layer_destroy(s_time_layer); +} +``` + +^CP^ This completes the setup of the basic watchface layout. If you return to +'Compilation' and install a new build, you should now see the following: + +^LC^ This completes the setup of the basic watchface layout. If you run `pebble +build && pebble install` (with your phone's IP address) for the new build, you +should now see the following: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/1-textlayer-test.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +The final step is to get the current time and display it using the +``TextLayer``. This is done with the ``TickTimerService``. + + +## Telling the Time + +The ``TickTimerService`` is an Event Service that allows access to the current +time by subscribing a function to be run whenever the time changes. Normally +this may be every minute, but can also be every hour, or every second. However, +the latter will incur extra battery costs, so use it sparingly. We can do this +by calling ``tick_timer_service_subscribe()``, but first we must create a +function to give the service to call whenever the time changes, and must match +this signature: + +```c +static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { + +} +``` + +This means that whenever the time changes, we are provided with a data structure +of type `struct tm` containing the current time +[in various forms](http://www.cplusplus.com/reference/ctime/tm/), as well as a +constant ``TimeUnits`` value that tells us which unit changed, to allow +filtering of behaviour. With our ``TickHandler`` created, we can register it +with the Event Service in `init()` like so: + +```c +// Register with TickTimerService +tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); +``` + +The logic to update the time ``TextLayer`` will be created in a function called +`update_time()`, enabling us to call it both from the ``TickHandler`` as well as +`main_window_load()` to ensure it is showing a time from the very beginning. + +This function will use `strftime()` +([See here for formatting](http://www.cplusplus.com/reference/ctime/strftime/)) +to extract the hours and minutes from the `struct tm` data structure and write +it into a character buffer. This buffer is required by ``TextLayer`` to be +long-lived as long as the text is to be displayed, as it is not copied into the +``TextLayer``, but merely referenced. We achieve this by making the buffer +`static`, so it persists across multiple calls to `update_time()`. Therefore +this function should be created before `main_window_load()` and look like this: + +```c +static void update_time() { + // Get a tm structure + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + + // Write the current hours and minutes into a buffer + static char s_buffer[8]; + strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ? + "%H:%M" : "%I:%M", tick_time); + + // Display this time on the TextLayer + text_layer_set_text(s_time_layer, s_buffer); +} +``` + +Our ``TickHandler`` follows the correct function signature and contains only a +single call to `update_time()` to do just that: + +```c +static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { + update_time(); +} +``` + +Lastly, `init()` should be modified include a call to +`update_time()` after ``window_stack_push()`` to ensure the time is displayed +correctly when the watchface loads: + +```c +// Make sure the time is displayed from the start +update_time(); +``` + +Since we can now display the time we can remove the call to +``text_layer_set_text()`` in `main_window_load()`, as it is no longer needed to +test the layout. + +Re-compile and re-install the watchface on your Pebble, and it should look like +this: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/1-time.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## Conclusion + +So there we have it, the basic process required to create a brand new Pebble +watchface! To do this we: + +1. Created a new Pebble project. +2. Setup basic app structure. +3. Setup a main ``Window``. +4. Setup a ``TextLayer`` to display the time. +5. Subscribed to ``TickTimerService`` to get updates on the time, and wrote + these to a buffer for display in the ``TextLayer``. + +If you have problems with your code, check it against the sample source code +provided using the button below. + +^CP^ [Edit in CloudPebble >{center,bg-lightblue,fg-white}]({{ site.links.cloudpebble }}ide/gist/9b9d50b990d742a3ae34) + +^LC^ [View Source Code >{center,bg-lightblue,fg-white}](https://gist.github.com/9b9d50b990d742a3ae34) + +## What's Next? + +The next section of the tutorial will introduce adding custom fonts and bitmap +images to your watchface. + +[Go to Part 2 → >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part2/) diff --git a/devsite/source/tutorials/watchface-tutorial/part2.md b/devsite/source/tutorials/watchface-tutorial/part2.md new file mode 100644 index 00000000..5e0582e9 --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/part2.md @@ -0,0 +1,300 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: watchface +tutorial_part: 2 + +title: Customizing Your Watchface +description: A guide to personalizing your new Pebble watchface +permalink: /tutorials/watchface-tutorial/part2/ +generate_toc: true +platform_choice: true +--- + +In the previous page of the tutorial, you learned how to create a new Pebble +project, set it up as a basic watchface and use ``TickTimerService`` to display +the current time. However, the design was pretty basic, so let's improve it with +some customization! + +In order to do this we will be using some new Pebble SDK concepts, including: + +- Resource management +- Custom fonts (using ``GFont``) +- Images (using ``GBitmap`` and ``BitmapLayer``) + +These will allow us to completely change the look and feel of the watchface. We +will provide some sample materials to use, but once you understand the process +be sure to replace these with your own to truly make it your own! Once we're +done, you should end up with a watchface looking like this: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/2-final.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +## First Steps + +To continue from the last part, you can either modify your existing Pebble +project or create a new one, using the code from that project's main `.c` file +as a starting template. For reference, that should look +[something like this](https://gist.github.com/pebble-gists/9b9d50b990d742a3ae34). + +^CP^ You can create a new CloudPebble project from this template by +[clicking here]({{ site.links.cloudpebble }}ide/gist/9b9d50b990d742a3ae34). + +The result of the first part should look something like this - a basic time +display: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/1-time.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +Let's improve it! + +## Adding a Custom Font + +^CP^ To add a custom font resource to use for the time display ``TextLayer``, +click 'Add New' on the left of the CloudPebble editor. Set the 'Resource Type' +to 'TrueType font' and upload a font file. Choose an 'Identifier', which is the +value we will use to refer to the font resource in the `.c` file. This must end +with the desired font size, which must be small enough to show a wide time such +as '23:50' in the ``TextLayer``. If it does not fit, you can always return here +to try another size. Click save and the font will be added to your project. + +^LC^ App resources (fonts and images etc.) are managed in the `package.json` +file in the project's root directory, as detailed in +[*App Resources*](/guides/app-resources/). All image files and fonts must +reside in subfolders of the `/resources` folder of your project. Below is an +example entry in the `media` array: + +
    +{% highlight {} %} +"media": [ + { + "type": "font", + "name": "FONT_PERFECT_DOS_48", + "file": "fonts/perfect-dos-vga.ttf", + "compatibility":"2.7" + } +] +{% endhighlight %} +
    + +^LC^ In the example above, we would place our `perfect-dos-vga.ttf` file in the +`/resources/fonts/` folder of our project. + +A custom font file must be a +[TrueType](http://en.wikipedia.org/wiki/TrueType) font in the `.ttf` file format. +[Here is an example font to use]({{ site.asset_path }}/fonts/getting-started/watchface-tutorial/perfect-dos-vga.ttf) +([source](http://www.dafont.com/perfect-dos-vga-437.font)). + +Now we will substitute the system font used before (`FONT_KEY_BITHAM_42_BOLD`) +for our newly imported one. + +To do this, we will declare a ``GFont`` globally. + +```c +// Declare globally +static GFont s_time_font; +``` + +Next, we add the creation and substitution of the new ``GFont`` in the existing +call to ``text_layer_set_font()`` in `main_window_load()`. Shown here is an +example identifier used when uploading the font earlier, `FONT_PERFECT_DOS_48`, +which is always pre-fixed with `RESOURCE_ID_`: + +```c +void main_window_load() { + // ... + // Create GFont + s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48)); + + // Apply to TextLayer + text_layer_set_font(s_time_layer, s_time_font); + // ... +} +``` + +And finally, safe destruction of the ``GFont`` in `main_window_unload()`: + +```c +void main_window_unload() { + // ... + // Unload GFont + fonts_unload_custom_font(s_time_font); + // ... +} +``` + +^CP^ After re-compiling and re-installing (either by using the green 'Play' +button to the top right of the CloudPebble editor, or by clicking 'Run Build' +and 'Install and Run' on the 'Compilation' screen), the watchface should feature +a much more interesting font. + +^LC^ After re-compiling and re-installing with `pebble build && pebble install`, +the watchface should feature a much more interesting font. + +An example screenshot is shown below: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/2-custom-font.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## Adding a Bitmap + +The Pebble SDK also allows you to use a 2-color (black and white) bitmap image +in your watchface project. You can ensure that you meet this requirement by +checking the export settings in your graphics package, or by purely using only +white (`#FFFFFF`) and black (`#000000`) in the image's creation. Another +alternative is to use a dithering tool such as +[HyperDither](http://2002-2010.tinrocket.com/software/hyperdither/index.html). +This will be loaded from the watchface's resources into a ``GBitmap`` data +structure before being displayed using a ``BitmapLayer`` element. These two +behave in a similar fashion to ``GFont`` and ``TextLayer``, so let's get +started. + +^CP^ The first step is the same as using a custom font; import the bitmap into +CloudPebble as a resource by clicking 'Add New' next to 'Resources' on the left +of the CloudPebble project screen. Ensure the 'Resource Type' is 'Bitmap image', +choose an identifier for the resource and upload your file. + +^LC^ You add a bitmap to the `package.json` file in the +[same way](/guides/app-resources/fonts) as a font, except the new `media` array +object will have a `type` of `bitmap`. Below is an example: + +
    +{% highlight {} %} +{ + "type": "bitmap", + "name": "IMAGE_BACKGROUND", + "file": "images/background.png" +} +{% endhighlight %} +
    + +As before, here is an example bitmap we have created for you to use, which looks +like this: + +[![background](/images/getting-started/watchface-tutorial/background.png "background")]({{ site.asset_path }}/images/getting-started/watchface-tutorial/background.png) + +Once this has been added to the project, return to your `.c` file and declare +two more pointers, one each of ``GBitmap`` and ``BitmapLayer`` near the top of +the file: + +```c +static BitmapLayer *s_background_layer; +static GBitmap *s_background_bitmap; +``` + +Now we will create both of these in `main_window_load()`. After both elements +are created, we set the ``BitmapLayer`` to use our ``GBitmap`` and then add it +as a child of the main ``Window`` as we did for the ``TextLayer``. + +However, is should be noted that the ``BitmapLayer`` must be added to the +``Window`` before the ``TextLayer``. This will ensure that the text is drawn *on +top of* the image. Otherwise, the text will be drawn behind the image and remain +invisible to us. Here is that process in full, to be as clear as possible: + +```c +// Create GBitmap +s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BACKGROUND); + +// Create BitmapLayer to display the GBitmap +s_background_layer = bitmap_layer_create(bounds); + +// Set the bitmap onto the layer and add to the window +bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap); +layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer)); +``` + +As always, the final step should be to ensure we free up the memory consumed by +these new elements in `main_window_unload()`: + +```c +// Destroy GBitmap +gbitmap_destroy(s_background_bitmap); + +// Destroy BitmapLayer +bitmap_layer_destroy(s_background_layer); +``` + +The final step is to set the background color of the main ``Window`` to match +the background image. Do this in `init()`: + +```c +window_set_background_color(s_main_window, GColorBlack); +``` + +With all this in place, the example background image should nicely frame the +time and match the style of the new custom font. Of course, if you have used +your own font and bitmap (highly recommended!) then your watchface will not look +exactly like this. + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/2-final.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## Conclusion + +After adding a custom font and a background image, our new watchface now looks +much nicer. If you want to go a bit further, try adding a new ``TextLayer`` in +the same way as the time display one to show the current date (hint: look at the +[formatting options](http://www.cplusplus.com/reference/ctime/strftime/) +available for `strftime()`!) + +As with last time, you can compare your own code to the example source code +using the button below. + +^CP^ [Edit in CloudPebble >{center,bg-lightblue,fg-white}]({{ site.links.cloudpebble }}ide/gist/d216d9e0b840ed296539) + +^LC^ [View Source Code >{center,bg-lightblue,fg-white}](https://gist.github.com/d216d9e0b840ed296539) + + +## What's Next? + +The next section of the tutorial will introduce PebbleKit JS for adding +web-based content to your watchface. + +[Go to Part 3 → >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part3/) diff --git a/devsite/source/tutorials/watchface-tutorial/part3.md b/devsite/source/tutorials/watchface-tutorial/part3.md new file mode 100644 index 00000000..fc4b327e --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/part3.md @@ -0,0 +1,613 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: watchface +tutorial_part: 3 + +title: Adding Web Content +description: A guide to adding web-based content your Pebble watchface +permalink: /tutorials/watchface-tutorial/part3/ +generate_toc: true +platform_choice: true +--- + +In the previous tutorial parts, we created a simple watchface to tell the time +and then improved it with a custom font and background bitmap. There's a lot you +can do with those elements, such as add more bitmaps, an extra ``TextLayer`` +showing the date, but let's aim even higher. This part is longer than the last, +so make sure you have a nice cup of your favourite hot beverage on hand before +embarking! + +In this tutorial we will add some extra content to the watchface that is fetched +from the web using [PebbleKit JS](/guides/communication/using-pebblekit-js/). +This part of the SDK allows you to use JavaScript to access the web as well as +the phone's location services and storage. It even allows you to display a +configuration screen to give users options over how they want your watchface or +app to look and run. + +By the end of this tutorial we will arrive at a watchface like the one below, in +all its customized glory: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/3-final.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +To continue from the last part, you can either modify your existing Pebble +project or create a new one, using the code from that project's main `.c` file +as a starting template. For reference, that should look +[something like this](https://gist.github.com/pebble-gists/d216d9e0b840ed296539). + +^CP^ You can create a new CloudPebble project from this template by +[clicking here]({{ site.links.cloudpebble }}ide/gist/d216d9e0b840ed296539). + +## Preparing the Watchface Layout + +The content we will be fetching will be the current weather conditions and +temperature from [OpenWeatherMap](http://openweathermap.org). We will need a new +``TextLayer`` to show this extra content. Let's do that now at the top of the C +file, as we did before: + +```c +static TextLayer *s_weather_layer; +``` + +As usual, we then create it properly in `main_window_load()` after the existing +elements. Here is the ``TextLayer`` setup; this should all be familiar to you +from the previous two tutorial parts: + +```c +// Create temperature Layer +s_weather_layer = text_layer_create( + GRect(0, PBL_IF_ROUND_ELSE(125, 120), bounds.size.w, 25)); + +// Style the text +text_layer_set_background_color(s_weather_layer, GColorClear); +text_layer_set_text_color(s_weather_layer, GColorWhite); +text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter); +text_layer_set_text(s_weather_layer, "Loading..."); +``` + +We will be using the same font as the time display, but at a reduced font size. + +^CP^ To do this, we return to our uploaded font resource and click 'Another +Font. The second font that appears below should be given an 'Identifier' with +`_20` at the end, signifying we now want font size 20 (suitable for the example +font provided). + +^LC^ You can add another font in `package.json` by duplicating the first font's +entry in the `media` array and changing the font size indicated in the `name` +field to `_20` or similar. Below is an example showing both fonts: + +
    +{% highlight {} %} +"media": [ + { + "type":"font", + "name":"FONT_PERFECT_DOS_48", + "file":"perfect-dos-vga.ttf", + "compatibility": "2.7" + }, + { + "type":"font", + "name":"FONT_PERFECT_DOS_20", + "file":"perfect-dos-vga.ttf", + "compatibility": "2.7" + }, +] +{% endhighlight %} +
    + +Now we will load and apply that font as we did last time, beginning with a new +``GFont`` declared at the top of the file: + +```c +static GFont s_weather_font; +``` + +Next, we load the resource and apply it to the new ``TextLayer`` and then add +that as a child layer to the main ``Window``: + +```c +// Create second custom font, apply it and add to Window +s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_20)); +text_layer_set_font(s_weather_layer, s_weather_font); +layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer)); +``` + +Finally, as usual, we add the same destruction calls in `main_window_unload()` +as for everything else: + +```c +// Destroy weather elements +text_layer_destroy(s_weather_layer); +fonts_unload_custom_font(s_weather_font); +``` + +After compiling and installing, your watchface should look something like this: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/3-loading.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + + +## Preparing AppMessage + +The primary method of communication for all Pebble watchapps and watchfaces is +the ``AppMessage`` API. This allows the construction of key-value dictionaries +for transmission between the watch and connected phone. The standard procedure +we will be following for enabling this communication is as follows: + +1. Create ``AppMessage`` callback functions to process incoming messages and + errors. +2. Register this callback with the system. +3. Open ``AppMessage`` to allow app communication. + +After this process is performed any incoming messages will cause a call to the +``AppMessageInboxReceived`` callback and allow us to react to its contents. +Let's get started! + +The callbacks should be placed before they are referred to in the code file, so +a good place is above `init()` where we will be registering them. The function +signature for ``AppMessageInboxReceived`` is shown below: + +```c +static void inbox_received_callback(DictionaryIterator *iterator, void *context) { + +} +``` + +We will also create and register three other callbacks so we can see all +outcomes and any errors that may occur, such as dropped messages. These are +reported with calls to ``APP_LOG`` for now, but more detail +[can be gotten from them](http://stackoverflow.com/questions/21150193/logging-enums-on-the-pebble-watch): + +```c +static void inbox_dropped_callback(AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!"); +} + +static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!"); +} + +static void outbox_sent_callback(DictionaryIterator *iterator, void *context) { + APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!"); +} +``` + +With this in place, we will now register the callbacks with the system in +`init()`: + +```c +// Register callbacks +app_message_register_inbox_received(inbox_received_callback); +app_message_register_inbox_dropped(inbox_dropped_callback); +app_message_register_outbox_failed(outbox_failed_callback); +app_message_register_outbox_sent(outbox_sent_callback); +``` + +And finally the third step, opening ``AppMessage`` to allow the watchface to +receive incoming messages, directly below +``app_message_register_inbox_received()``. It is considered best practice to +register callbacks before opening ``AppMessage`` to ensure that no messages are +missed. The code snippet below shows this process using two variables to specify +the inbox and outbox size (in bytes): + +```c +// Open AppMessage +const int inbox_size = 128; +const int outbox_size = 128; +app_message_open(inbox_size, outbox_size); +``` + +> Read +> [*Buffer Sizes*](/guides/pebble-apps/communications/appmessage/#buffer-sizes) +> to learn about using correct buffer sizes for your app. + +## Preparing PebbleKit JS + +The weather data itself will be downloaded by the JavaScript component of the +watchface, and runs on the connected phone whenever the watchface is opened. + +^CP^ To begin using PebbleKit JS, click 'Add New' in the CloudPebble editor, +next to 'Source Files'. Select 'JavaScript file' and choose a file name. +CloudPebble allows any normally valid file name, such as `weather.js`. + +^LC^ To begin using PebbleKit JS, add a new file to your project at +`src/pkjs/index.js` to contain your JavaScript code. + +To get off to a quick start, we will provide a basic template for using the +PebbleKit JS SDK. This template features two basic event listeners. One is for +the 'ready' event, which fires when the JS environment on the phone is first +available after launch. The second is for the 'appmessage' event, which fires +when an AppMessage is sent from the watch to the phone. + +This template is shown below for you to start your JS file: + +```js +// Listen for when the watchface is opened +Pebble.addEventListener('ready', + function(e) { + console.log('PebbleKit JS ready!'); + } +); + +// Listen for when an AppMessage is received +Pebble.addEventListener('appmessage', + function(e) { + console.log('AppMessage received!'); + } +); +``` + +After compiling and installing the watchface, open the app logs. + +^CP^ Click the 'View Logs' button on the confirmation dialogue or the +'Compilation' screen if it was already dismissed. + +^LC^ You can listen for app logs by running `pebble logs`, supplying your +phone's IP address with the `--phone` switch. For example: + +
    +{% highlight {} %} +pebble logs --phone 192.168.1.78 +{% endhighlight %} +
    + +^LC^ You can also combine these two commands into one: + +
    +{% highlight {} %} +pebble install --logs --phone 192.168.1.78 +{% endhighlight %} +
    + +You should see a message matching that set to appear using `console.log()` in +the JS console in the snippet above! This is where any information sent using +``APP_LOG`` in the C file or `console.log()` in the JS file will be shown, and +is very useful for debugging! + + +## Getting Weather Information + +To download weather information from +[OpenWeatherMap.org](http://openweathermap.org), we will perform three steps in +our JS file: + +1. Request the user's location from the phone. +2. Perform a call to the OpenWeatherMap API using an `XMLHttpRequest` object, + supplying the location given to us from step 1. +3. Send the information we want from the XHR request response to the watch for + display on our watchface. + +^CP^ Firstly, go to 'Settings' and check the 'Uses Location' box at the bottom +of the page. This will allow the watchapp to access the phone's location +services. + +^LC^ You will need to add `location` to the `capabilities` array in the +`package.json` file. This will allow the watchapp to access the phone's location +services. This is shown in the code segment below: + +
    +{% highlight {} %} +"capabilities": ["location"] +{% endhighlight %} +
    + +The next step is simple to perform, and is shown in full below. The method we +are using requires two other functions to use as callbacks for the success and +failure conditions after requesting the user's location. It also requires two +other pieces of information: `timeout` of the request and the `maximumAge` of +the data: + +```js +function locationSuccess(pos) { + // We will request the weather here +} + +function locationError(err) { + console.log('Error requesting location!'); +} + +function getWeather() { + navigator.geolocation.getCurrentPosition( + locationSuccess, + locationError, + {timeout: 15000, maximumAge: 60000} + ); +} + +// Listen for when the watchface is opened +Pebble.addEventListener('ready', + function(e) { + console.log('PebbleKit JS ready!'); + + // Get the initial weather + getWeather(); + } +); +``` + +Notice that when the `ready` event occurs, `getWeather()` is called, which in +turn calls `getCurrentPosition()`. When this is successful, `locationSuccess()` +is called and provides us with a single argument: `pos`, which contains the +location information we require to make the weather info request. Let's do that +now. + +The next step is to assemble and send an `XMLHttpRequest` object to make the +request to OpenWeatherMap.org. To make this easier, we will provide a function +that simplifies its usage. Place this before `locationSuccess()`: + +```js +var xhrRequest = function (url, type, callback) { + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + callback(this.responseText); + }; + xhr.open(type, url); + xhr.send(); +}; +``` + +The three arguments we have to provide when calling `xhrRequest()` are the URL, +the type of request (`GET` or `POST`, for example) and a callback for when the +response is received. The URL is specified on the OpenWeatherMap API page, and +contains the coordinates supplied by `getCurrentPosition()`, the latitude and +longitude encoded at the end: + +{% include guides/owm-api-key-notice.html %} + +```js +var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' + + pos.coords.latitude + '&lon=' + pos.coords.longitude + '&appid=' + myAPIKey; +``` + +The type of the XHR will be a 'GET' request, to *get* information from the +service. We will incorporate the callback into the function call for +readability, and the full code snippet is shown below: + +```js +function locationSuccess(pos) { + // Construct URL + var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' + + pos.coords.latitude + '&lon=' + pos.coords.longitude + '&appid=' + myAPIKey; + + // Send request to OpenWeatherMap + xhrRequest(url, 'GET', + function(responseText) { + // responseText contains a JSON object with weather info + var json = JSON.parse(responseText); + + // Temperature in Kelvin requires adjustment + var temperature = Math.round(json.main.temp - 273.15); + console.log('Temperature is ' + temperature); + + // Conditions + var conditions = json.weather[0].main; + console.log('Conditions are ' + conditions); + } + ); +} +``` + +Thus when the location is successfully obtained, `xhrRequest()` is called. When +the response arrives, the JSON object is parsed and the temperature and weather +conditions obtained. To discover the structure of the JSON object we can use +`console.log(responseText)` to see its contents. + +To see how we arrived at some of the statements above, such as +`json.weather[0].main`, here is an +[example response](https://gist.github.com/pebble-gists/216e6d5a0f0bd2328509#file-example-response-json) +for London, UK. We can see that by following the JSON structure from our +variable called `json` (which represents the root of the structure) we can +access any of the data items. So to get the wind speed we would access +`json.wind.speed`, and so on. + +## Showing Weather on Pebble + +The final JS step is to send the weather data back to the watch. To do this we must +pick some appmessage keys to send back. Since we want to display the temperature +and current conditions, we'll create one key for each of those. + +^CP^ Firstly, go to the 'Settings' screen, find the 'PebbleKit JS Message Keys' +section and enter some names, like "TEMPERATURE" and "CONDITIONS": + +^LC^ You can add your ``AppMessage`` keys in the `messageKeys` object in +`package.json` as shown below for the example keys: + +
    +{% highlight {} %} +"messageKeys": [ + "TEMPERATURE", + "CONDITIONS", +] +{% endhighlight %} +
    + +To send the data, we call `Pebble.sendAppMessage()` after assembling the weather +info variables `temperature` and `conditions` into a dictionary. We can +optionally also supply two functions as success and failure callbacks: + +```js +// Assemble dictionary using our keys +var dictionary = { + 'TEMPERATURE': temperature, + 'CONDITIONS': conditions +}; + +// Send to Pebble +Pebble.sendAppMessage(dictionary, + function(e) { + console.log('Weather info sent to Pebble successfully!'); + }, + function(e) { + console.log('Error sending weather info to Pebble!'); + } +); +``` + +While we are here, let's add another call to `getWeather()` in the `appmessage` +event listener for when we want updates later, and will send an ``AppMessage`` +from the watch to achieve this: + +```js +// Listen for when an AppMessage is received +Pebble.addEventListener('appmessage', + function(e) { + console.log('AppMessage received!'); + getWeather(); + } +); +``` + +The final step on the Pebble side is to act on the information received from +PebbleKit JS and show the weather data in the ``TextLayer`` we created for this +very purpose. To do this, go back to your C code file and find your +``AppMessageInboxReceived`` implementation (such as our +`inbox_received_callback()` earlier). This will now be modified to process the +received data. When the watch receives an ``AppMessage`` message from the JS +part of the watchface, this callback will be called and we will be provided a +dictionary of data in the form of a `DictionaryIterator` object, as seen in the +callback signature. `MESSAGE_KEY_TEMPERATURE` and `MESSAGE_KEY_CONDITIONS` +will be automatically provided as we specified them in `package.json`. + +Before examining the dictionary we add three character +buffers; one each for the temperature and conditions and the other for us to +assemble the entire string. Remember to be generous with the buffer sizes to +prevent overruns: + +```c +// Store incoming information +static char temperature_buffer[8]; +static char conditions_buffer[32]; +static char weather_layer_buffer[32]; +``` + +We then store the incoming information by reading the appropriate `Tuple`s to +the two buffers using `snprintf()`: + +```c +// Read tuples for data +Tuple *temp_tuple = dict_find(iterator, MESSAGE_KEY_TEMPERATURE); +Tuple *conditions_tuple = dict_find(iterator, MESSAGE_KEY_CONDITIONS); + +// If all data is available, use it +if(temp_tuple && conditions_tuple) { + snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)temp_tuple->value->int32); + snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", conditions_tuple->value->cstring); +} +``` + +Lastly within this `if` statement, we assemble the complete string and instruct +the ``TextLayer`` to display it: + +```c +// Assemble full string and display +snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer); +text_layer_set_text(s_weather_layer, weather_layer_buffer); +``` + +After re-compiling and re-installing you should be presented with a watchface +that looks similar to the one shown below: + +{% screenshot_viewer %} +{ + "image": "/images/getting-started/watchface-tutorial/3-final.png", + "platforms": [ + {"hw": "aplite", "wrapper": "steel-black"}, + {"hw": "basalt", "wrapper": "time-red"}, + {"hw": "chalk", "wrapper": "time-round-rosegold-14"} + ] +} +{% endscreenshot_viewer %} + +^CP^ Remember, if the text is too large for the screen, you can reduce the font +size in the 'Resources' section of the CloudPebble editor. Don't forget to +change the constants in the `.c` file to match the new 'Identifier'. + +^LC^ Remember, if the text is too large for the screen, you can reduce the font +size in `package.json` for that resource's entry in the `media` array. Don't +forget to change the constants in the `.c` file to match the new resource's +`name`. + +An extra step we will perform is to modify the C code to obtain regular weather +updates, in addition to whenever the watchface is loaded. To do this we will +take advantage of a timer source we already have - the ``TickHandler`` +implementation, which we have called `tick_handler()`. Let's modify this to get +weather updates every 30 minutes by adding the following code to the end of +`tick_handler()` in our main `.c` file: + +```c +// Get weather update every 30 minutes +if(tick_time->tm_min % 30 == 0) { + // Begin dictionary + DictionaryIterator *iter; + app_message_outbox_begin(&iter); + + // Add a key-value pair + dict_write_uint8(iter, 0, 0); + + // Send the message! + app_message_outbox_send(); +} +``` + +Thanks to us adding a call to `getWeather()` in the `appmessage` JS event +handler earlier, this message send in the ``TickHandler`` will result in new +weather data being downloaded and sent to the watch. Job done! + +## Conclusion + +Whew! That was quite a long tutorial, but here's all you've learned: + +1. Managing multiple font sizes. +2. Preparing and opening ``AppMessage``. +3. Setting up PebbleKit JS for interaction with the web. +4. Getting the user's current location with `navigator.getCurrentPosition()`. +5. Extracting information from a JSON response. +6. Sending ``AppMessage`` to and from the watch. + +Using all this it is possible to `GET` and `POST` to a huge number of web +services to display data and control these services. + +As usual, you can compare your code to the example code provided using the button +below. + +^CP^ [Edit in CloudPebble >{center,bg-lightblue,fg-white}]({{ site.links.cloudpebble }}ide/gist/216e6d5a0f0bd2328509) + +^LC^ [View Source Code >{center,bg-lightblue,fg-white}](https://gist.github.com/216e6d5a0f0bd2328509) + + +## What's Next? + +The next section of the tutorial will introduce the Battery Service, and +demonstrate how to add a battery bar to your watchface. + +[Go to Part 4 → >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part4/) diff --git a/devsite/source/tutorials/watchface-tutorial/part4.md b/devsite/source/tutorials/watchface-tutorial/part4.md new file mode 100644 index 00000000..10fd9b16 --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/part4.md @@ -0,0 +1,157 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: watchface +tutorial_part: 4 + +title: Adding a Battery Bar +description: | + How to add a battery level meter to your watchface. +permalink: /tutorials/watchface-tutorial/part4/ +generate_toc: true +--- + +Another popular feature added to a lot of watchfaces is a battery meter, +enabling users to see the state of their Pebble's battery charge level at a +glance. This is typically implemented as the classic 'battery icon' that fills +up according to the current charge level, but some watchfaces favor the more +minimal approach, which will be implemented here. + +This section continues from +[*Part 3*](/tutorials/watchface-tutorial/part3/), so be sure to re-use +your code or start with that finished project. + +The state of the battery is obtained using the ``BatteryStateService``. This +service offers two modes of usage - 'peeking' at the current level, or +subscribing to events that take place when the battery state changes. The latter +approach will be adopted here. The battery level percentage will be stored in an +integer at the top of the file: + +```c +static int s_battery_level; +``` + +As with all the Event Services, to receive an event when new battery information +is available, a callback must be registered. Create this callback using the +signature of ``BatteryStateHandler``, and use the provided +``BatteryChargeState`` parameter to store the current charge percentage: + +```c +static void battery_callback(BatteryChargeState state) { + // Record the new battery level + s_battery_level = state.charge_percent; +} +``` + +To enable this function to be called when the battery level changes, subscribe +to updates in `init()`: + +```c +// Register for battery level updates +battery_state_service_subscribe(battery_callback); +``` + +With the subscription in place, the UI can be created. This will take the form +of a ``Layer`` with a ``LayerUpdateProc`` that uses the battery level to draw a +thin, minimalist white meter along the top of the time display. + +Create the ``LayerUpdateProc`` that will be used to draw the battery meter: + +```c +static void battery_update_proc(Layer *layer, GContext *ctx) { + +} +``` + +Declare this new ``Layer`` at the top of the file: + +```c +static Layer *s_battery_layer; +``` + +Allocate the ``Layer`` in `main_window_load()`, assign it the ``LayerUpdateProc`` that will draw it, and +add it as a child of the main ``Window`` to make it visible: + +```c +// Create battery meter Layer +s_battery_layer = layer_create(GRect(14, 54, 115, 2)); +layer_set_update_proc(s_battery_layer, battery_update_proc); + +// Add to Window +layer_add_child(window_get_root_layer(window), s_battery_layer); +``` + +To ensure the battery meter is updated every time the charge level changes, mark +it 'dirty' (to ask the system to re-render it at the next opportunity) within +`battery_callback()`: + +```c +// Update meter +layer_mark_dirty(s_battery_layer); +``` + +The final piece of the puzzle is the actual drawing of the battery meter, which +takes place within the ``LayerUpdateProc``. The background of the meter is drawn +to 'paint over' the background image, before the width of the meter's 'bar' is +calculated using the current value as a percentage of the bar's total width +(114px). + +The finished version of the update procedure is shown below: + +```c +static void battery_update_proc(Layer *layer, GContext *ctx) { + GRect bounds = layer_get_bounds(layer); + + // Find the width of the bar (total width = 114px) + int width = (s_battery_level * 114) / 100; + + // Draw the background + graphics_context_set_fill_color(ctx, GColorBlack); + graphics_fill_rect(ctx, bounds, 0, GCornerNone); + + // Draw the bar + graphics_context_set_fill_color(ctx, GColorWhite); + graphics_fill_rect(ctx, GRect(0, 0, width, bounds.size.h), 0, GCornerNone); +} +``` + +Lastly, as with the ``TickTimerService``, the ``BatteryStateHandler`` can be +called manually in `init()` to display an inital value: + +```c +// Ensure battery level is displayed from the start +battery_callback(battery_state_service_peek()); +``` + +Don't forget to free the memory used by the new battery meter: + +```c +layer_destroy(s_battery_layer); +``` + +With this new feature in place, the watchface will now display the watch's +battery charge level in a minimalist fashion that integrates well with the +existing design style. + +![battery-level >{pebble-screenshot,pebble-screenshot--steel-black}](/images/tutorials/intermediate/battery-level.png) + + +## What's Next? + +In the next, and final, section of this tutorial, we'll use the Connection Service +to notify the user when their Pebble smartwatch disconnects from their phone. + +[Go to Part 5 → >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part5/) diff --git a/devsite/source/tutorials/watchface-tutorial/part5.md b/devsite/source/tutorials/watchface-tutorial/part5.md new file mode 100644 index 00000000..cb8c7f6c --- /dev/null +++ b/devsite/source/tutorials/watchface-tutorial/part5.md @@ -0,0 +1,159 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: tutorials/tutorial +tutorial: watchface +tutorial_part: 5 + +title: Vibrate on Disconnect +description: | + How to add bluetooth connection alerts to your watchface. +permalink: /tutorials/watchface-tutorial/part5/ +generate_toc: true +platform_choice: true +--- + +The final popular watchface addition explored in this tutorial series +is the concept of using the Bluetooth connection service to alert the user +when their watch connects or disconnects. This can be useful to know when the +watch is out of range and notifications will not be received, or to let the user +know that they might have walked off somewhere without their phone. + +This section continues from +[*Part 4*](/tutorials/watchface-tutorial/part4), so be sure to +re-use your code or start with that finished project. + +In a similar manner to both the ``TickTimerService`` and +``BatteryStateService``, the events associated with the Bluetooth connection are +given to developers via subscriptions, which requires an additional callback - +the ``ConnectionHandler``. Create one of these in the format given below: + +```c +static void bluetooth_callback(bool connected) { + +} +``` + +The subscription to Bluetooth-related events is added in `init()`: + +```c +// Register for Bluetooth connection updates +connection_service_subscribe((ConnectionHandlers) { + .pebble_app_connection_handler = bluetooth_callback +}); +``` + +The indicator itself will take the form of the following 'Bluetooth +disconnected' icon that will be displayed when the watch is disconnected, and +hidden when reconnected. Save the image below for use in this project: + + + + +{% platform cloudpebble %} +Add this icon to your project by clicking 'Add New' under 'Resources' in +the left hand side of the editor. Specify the 'Resource Type' as 'Bitmap Image', +upload the file for the 'File' field. Give it an 'Identifier' such as +`IMAGE_BT_ICON` before clicking 'Save'. +{% endplatform %} + +{% platform local %} +Add this icon to your project by copying the above icon image to the `resources` +project directory, and adding a new JSON object to the `media` array in +`package.json` such as the following: + +```js +{ + "type": "bitmap", + "name": "IMAGE_BT_ICON", + "file": "bt-icon.png" +}, +``` +{% endplatform %} + +This icon will be loaded into the app as a ``GBitmap`` for display in a +``BitmapLayer`` above the time display. Declare both of these as pointers at the +top of the file, in addition to the existing variables of these types: + +```c +static BitmapLayer *s_background_layer, *s_bt_icon_layer; +static GBitmap *s_background_bitmap, *s_bt_icon_bitmap; +``` + +Allocate both of the new objects in `main_window_load()`, then set the +``BitmapLayer``'s bitmap as the new icon ``GBitmap``: + +```c +// Create the Bluetooth icon GBitmap +s_bt_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BT_ICON); + +// Create the BitmapLayer to display the GBitmap +s_bt_icon_layer = bitmap_layer_create(GRect(59, 12, 30, 30)); +bitmap_layer_set_bitmap(s_bt_icon_layer, s_bt_icon_bitmap); +layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_bt_icon_layer)); +``` + +As usual, ensure that the memory allocated to create these objects is also freed +in `main_window_unload()`: + +```c +gbitmap_destroy(s_bt_icon_bitmap); +bitmap_layer_destroy(s_bt_icon_layer); +``` + +With the UI in place, the implementation of the ``BluetoothConnectionHandler`` +can be finished. Depending on the state of the connection when an event takes +place, the indicator icon is hidden or unhidden as required. A distinct +vibration is also triggered if the watch becomes disconnected, to differentiate +the feedback from that of a notification or phone call: + +```c +static void bluetooth_callback(bool connected) { + // Show icon if disconnected + layer_set_hidden(bitmap_layer_get_layer(s_bt_icon_layer), connected); + + if(!connected) { + // Issue a vibrating alert + vibes_double_pulse(); + } +} +``` + +Upon initialization, the app will display the icon unless a re-connection event +occurs, and the current state is evaluated. Manually call the handler in +`main_window_load()` to display the correct initial state: + +```c +// Show the correct state of the BT connection from the start +bluetooth_callback(connection_service_peek_pebble_app_connection()); +``` + +With this last feature in place, running the app and disconnecting the Bluetooth +connection will cause the new indicator to appear, and the watch to vibrate +twice. + +![bt >{pebble-screenshot,pebble-screenshot--steel-black}](/images/tutorials/intermediate/bt.png) + +^CP^ You can create a new CloudPebble project from the completed project by +[clicking here]({{ site.links.cloudpebble }}ide/gist/ddd15cbe8b0986fda407). + +^LC^ You can see the finished project source code in +[this GitHub Gist](https://gist.github.com/pebble-gists/ddd15cbe8b0986fda407). + + +## What's Next? + +Now that you've successfully built a feature rich watchface, it's time to +[publish it](/guides/appstore-publishing/publishing-an-app/)! diff --git a/devsite/source/tutorials/watchface-tutorial/resources/images/background.png b/devsite/source/tutorials/watchface-tutorial/resources/images/background.png new file mode 100644 index 00000000..1b5e78c6 Binary files /dev/null and b/devsite/source/tutorials/watchface-tutorial/resources/images/background.png differ diff --git a/devsite/source/videos.html b/devsite/source/videos.html new file mode 100644 index 00000000..8b2699af --- /dev/null +++ b/devsite/source/videos.html @@ -0,0 +1,84 @@ +--- +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +layout: more +title: Videos +menu_subsection: videos +permalink: /videos/ +scripts: + - videos +--- +
    +
    +
    +

    Videos

    +
    +
    +
    +
    +

    General Videos

    +

    Introduction to Pebble Development

    + +

    Build a Watchface

    + +
    + +
    +
    +

    Contents

    +
      +
    • +
    +
    +
    +
    diff --git a/devsite/spec/docs.rb b/devsite/spec/docs.rb new file mode 100644 index 00000000..2b437832 --- /dev/null +++ b/devsite/spec/docs.rb @@ -0,0 +1,73 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'open-uri' +require 'jekyll' + +shared_context 'docs' do + def load_config + config = File.join(File.dirname(__FILE__), '../source/_data/docs.yaml') + YAML.load(File.read(config)) + end + + def valid_branch(branch) + expect(branch[:name]).to respond_to(:to_s) + expect(branch[:url]).to respond_to(:to_s) + expect(branch[:children]).to respond_to(:to_a) + branch[:children].to_a.each { |child| valid_branch(child) } + end + + def valid_branch2(branch) + expect(branch[:name]).to respond_to(:to_s) + expect(branch[:url]).to respond_to(:to_s) + expect(branch[:children]).to respond_to(:to_a) + branch[:children].to_a.each { |child| valid_branch2(child) } + end + + # rubocop:disable Metrics/MethodLength + def fake_site + config = Jekyll::Configuration::DEFAULTS + config['source'] = './source/' + Jekyll::Site.new(config) + end + # rubocop:enable Metrics/MethodLength + + # Iterate through all of the symbols and make sure that there is no other + # symbol with the same name. + def clashing_symbols(symbols) + symbols.any? do |symbol| + symbols.any? do |sym| + return false if sym == symbol + return true if sym[:name] == symbol[:name] || sym[:url] == symbol[:url] + false + end + end + end + + # Iterate through all of symbols and make sure that we have a matching + # page that the symbol is linking to. + def symbol_to_page_completeness(symbols, pages) + symbols.each do |symbol| + file = symbol[:url][/^([^#]*)/].gsub('%2B', '+') + has_page = pages.any? do |page| + page.url == file || page.url == File.join(file, 'index.html') || page.url == file + '/' + end + expect(has_page).to be true + end + end + + def find_symbol(symbols, name) + symbols.index { |symbol| symbol[:name] == name } + end +end diff --git a/devsite/spec/filter_assetify_spec.rb b/devsite/spec/filter_assetify_spec.rb new file mode 100644 index 00000000..0fb75ade --- /dev/null +++ b/devsite/spec/filter_assetify_spec.rb @@ -0,0 +1,39 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'liquid' +require_relative '../plugins/filter_assetify' + +describe FilterAssetify, '#assetify' do + + let(:liquid) { Class.new.extend(FilterAssetify) } + + before do + site = double(Class) + allow(site).to receive_messages(:config => { 'asset_path' => 'ASSET_PATH' }) + context = double(Liquid::Context) + allow(context).to receive_messages(:registers => { :site => site }) + liquid.instance_variable_set("@context", context) + end + + it 'prepends the provided asset_path when needed' do + expect(liquid.assetify('/images/foo.png')).to eql('ASSET_PATH/images/foo.png') + end + + it 'does not prepend the provided asset_path when not needed' do + expect(liquid.assetify('//images/foo.png')).to eql('//images/foo.png') + expect(liquid.assetify('images/foo.png')).to eql('images/foo.png') + end + +end diff --git a/devsite/spec/fixtures/js.json b/devsite/spec/fixtures/js.json new file mode 100644 index 00000000..c461ab0d --- /dev/null +++ b/devsite/spec/fixtures/js.json @@ -0,0 +1,7653 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The Pebble namespace is where all of the Pebble specific methods and\nproperties exist. This class contains methods belonging to PebbleKit JS and\nallows bi-directional communication with a C watchapp, as well as managing\nthe user's timeline subscriptions and obtaining information about their\nwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 7, + "offset": 298 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 7, + "offset": 298 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 7, + "offset": 298 + } + } + }, + "tags": [ + { + "title": "namespace", + "description": null, + "lineNumber": 1, + "type": null, + "name": "Pebble" + }, + { + "title": "desc", + "description": "The Pebble namespace is where all of the Pebble specific methods and\nproperties exist. This class contains methods belonging to PebbleKit JS and\nallows bi-directional communication with a C watchapp, as well as managing\nthe user's timeline subscriptions and obtaining information about their\nwatch.", + "lineNumber": 3 + } + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 24 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "kind": "namespace", + "name": "Pebble", + "members": { + "instance": [], + "static": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Adds a listener for Pebble JS events, such as when an ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 1, + "column": 55, + "offset": 54 + }, + "end": { + "line": 1, + "column": 69, + "offset": 68 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is\n received or the configuration view is opened or closed.", + "position": { + "start": { + "line": 1, + "column": 69, + "offset": 68 + }, + "end": { + "line": 2, + "column": 60, + "offset": 131 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 131 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "heading", + "depth": 4, + "children": [ + { + "type": "inlineCode", + "value": "event", + "position": { + "start": { + "line": 4, + "column": 8, + "offset": 140 + }, + "end": { + "line": 4, + "column": 15, + "offset": 147 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " Options", + "position": { + "start": { + "line": 4, + "column": 15, + "offset": 147 + }, + "end": { + "line": 4, + "column": 23, + "offset": 155 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 133 + }, + "end": { + "line": 4, + "column": 23, + "offset": 155 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " Possible values:", + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 157 + }, + "end": { + "line": 6, + "column": 19, + "offset": 175 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 157 + }, + "end": { + "line": 6, + "column": 19, + "offset": 175 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "ready", + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 181 + }, + "end": { + "line": 8, + "column": 12, + "offset": 188 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The watchapp has been launched and the PebbleKit JS component\nis now ready to receive events.", + "position": { + "start": { + "line": 8, + "column": 12, + "offset": 188 + }, + "end": { + "line": 9, + "column": 36, + "offset": 288 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 5, + "offset": 181 + }, + "end": { + "line": 9, + "column": 36, + "offset": 288 + }, + "indent": [ + 5 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 177 + }, + "end": { + "line": 9, + "column": 36, + "offset": 288 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "appmessage", + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 293 + }, + "end": { + "line": 10, + "column": 17, + "offset": 305 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The watch sent an ", + "position": { + "start": { + "line": 10, + "column": 17, + "offset": 305 + }, + "end": { + "line": 10, + "column": 38, + "offset": 326 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 10, + "column": 38, + "offset": 326 + }, + "end": { + "line": 10, + "column": 52, + "offset": 340 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " to PebbleKit JS. The\nAppMessage ", + "position": { + "start": { + "line": 10, + "column": 52, + "offset": 340 + }, + "end": { + "line": 11, + "column": 16, + "offset": 377 + }, + "indent": [ + 5 + ] + } + }, + { + "type": "inlineCode", + "value": "Dictionary", + "position": { + "start": { + "line": 11, + "column": 16, + "offset": 377 + }, + "end": { + "line": 11, + "column": 30, + "offset": 391 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " is contained in the payload property (i.e:\nevent.payload). The payload consists of key-value pairs, where the keys\nare strings containing integers (e.g: \"0\"), or aliases for keys defined\nin package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\nor byte arrays (arrays of characters).", + "position": { + "start": { + "line": 11, + "column": 30, + "offset": 391 + }, + "end": { + "line": 15, + "column": 43, + "offset": 706 + }, + "indent": [ + 5, + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 5, + "offset": 293 + }, + "end": { + "line": 15, + "column": 43, + "offset": 706 + }, + "indent": [ + 5, + 5, + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1, + "offset": 289 + }, + "end": { + "line": 15, + "column": 43, + "offset": 706 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "showConfiguration", + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 711 + }, + "end": { + "line": 16, + "column": 24, + "offset": 730 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The user has requested the app's configuration\nwebview to be displayed. This can occur either upon the app's initial\ninstall or when the user taps 'Settings' in the 'My Pebble' view withtin\nthe phone app.", + "position": { + "start": { + "line": 16, + "column": 24, + "offset": 730 + }, + "end": { + "line": 19, + "column": 19, + "offset": 949 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 5, + "offset": 711 + }, + "end": { + "line": 19, + "column": 19, + "offset": 949 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1, + "offset": 707 + }, + "end": { + "line": 19, + "column": 19, + "offset": 949 + }, + "indent": [ + 1, + 1, + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "webviewclosed", + "position": { + "start": { + "line": 20, + "column": 5, + "offset": 954 + }, + "end": { + "line": 20, + "column": 20, + "offset": 969 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The configuration webview was closed by the user. If\nthe webview had a response, it will be contained in the response property\n(i.e: event.response). This response can be used to feed back user\npreferences to the watchapp.", + "position": { + "start": { + "line": 20, + "column": 20, + "offset": 969 + }, + "end": { + "line": 23, + "column": 33, + "offset": 1206 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 20, + "column": 5, + "offset": 954 + }, + "end": { + "line": 23, + "column": 33, + "offset": 1206 + }, + "indent": [ + 5, + 5, + 5 + ] + } + } + ], + "position": { + "start": { + "line": 20, + "column": 1, + "offset": 950 + }, + "end": { + "line": 23, + "column": 33, + "offset": 1206 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 1, + "offset": 177 + }, + "end": { + "line": 23, + "column": 33, + "offset": 1206 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 23, + "column": 33, + "offset": 1206 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n received or the configuration view is opened or closed.\n\n #### `event` Options\n\n Possible values:\n\n * `ready` - The watchapp has been launched and the PebbleKit JS component\n is now ready to receive events.\n * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n AppMessage ``Dictionary`` is contained in the payload property (i.e:\n event.payload). The payload consists of key-value pairs, where the keys\n are strings containing integers (e.g: \"0\"), or aliases for keys defined\n in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n or byte arrays (arrays of characters).\n * `showConfiguration` - The user has requested the app's configuration\n webview to be displayed. This can occur either upon the app's initial\n install or when the user taps 'Settings' in the 'My Pebble' view withtin\n the phone app.\n * `webviewclosed` - The configuration webview was closed by the user. If\n the webview had a response, it will be contained in the response property\n (i.e: event.response). This response can be used to feed back user\n preferences to the watchapp.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event, from the list described above.", + "lineNumber": 25, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "event" + }, + { + "title": "param", + "description": "The developer defined {@link #EventCallback EventCallback}\n to receive any events of the type specified that occur.", + "lineNumber": 26, + "type": { + "type": "NameExpression", + "name": "EventCallback" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 41, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 42, + "column": 0 + }, + "end": { + "line": 42, + "column": 56 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "event", + "lineNumber": 25, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event, from the list described above.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 54, + "offset": 53 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 26, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 23, + "offset": 22 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#EventCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "EventCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 23, + "offset": 22 + }, + "end": { + "line": 1, + "column": 59, + "offset": 58 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n to receive any events of the type specified that occur.", + "position": { + "start": { + "line": 1, + "column": 59, + "offset": 58 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 58, + "offset": 116 + } + } + }, + "type": { + "type": "NameExpression", + "name": "EventCallback" + } + } + ], + "name": "addEventListener", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "addEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.addEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Remove an existing event listener previously registered with\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 63 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 63 + }, + "end": { + "line": 2, + "column": 32, + "offset": 92 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 32, + "offset": 92 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 93 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Remove an existing event listener previously registered with\n ``Pebble.addEventListener()``.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The type of the event listener to be removed. See\n ``Pebble.addEventListener()`` for a list of available types.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "type" + }, + { + "title": "param", + "description": "The existing developer-defined function that was\n previously registered.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "callback" + } + ], + "loc": { + "start": { + "line": 44, + "column": 0 + }, + "end": { + "line": 52, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 53, + "column": 0 + }, + "end": { + "line": 53, + "column": 58 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "type", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The type of the event listener to be removed. See\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 3, + "offset": 52 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 3, + "offset": 52 + }, + "end": { + "line": 2, + "column": 32, + "offset": 81 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " for a list of available types.", + "position": { + "start": { + "line": 2, + "column": 32, + "offset": 81 + }, + "end": { + "line": 2, + "column": 63, + "offset": 112 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 112 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 112 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "callback", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The existing developer-defined function that was\n previously registered.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 73 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 73 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 25, + "offset": 73 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "removeEventListener", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "removeEventListener", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.removeEventListener" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Show a simple modal notification on the connected watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 57, + "offset": 56 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Show a simple modal notification on the connected watch.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The title of the notification", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "title" + }, + { + "title": "param", + "description": "The main content of the notification", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "body" + } + ], + "loc": { + "start": { + "line": 55, + "column": 0 + }, + "end": { + "line": 60, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 61, + "column": 0 + }, + "end": { + "line": 61, + "column": 66 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "title", + "lineNumber": 3, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The title of the notification", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 30, + "offset": 29 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "body", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The main content of the notification", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 37, + "offset": 36 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "showSimpleNotificationOnPebble", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "showSimpleNotificationOnPebble", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.showSimpleNotificationOnPebble" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Send an AppMessage to the app running on the watch. Messages should be\n in the form of JSON objects containing key-value pairs. See\n Pebble.sendAppMessage() for valid key and value data types.\n Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 70, + "offset": 268 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 70, + "offset": 268 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 70, + "offset": 268 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Send an AppMessage to the app running on the watch. Messages should be\n in the form of JSON objects containing key-value pairs. See\n Pebble.sendAppMessage() for valid key and value data types.\n Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "The transaction id for this message", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Number" + } + }, + { + "title": "param", + "description": "A JSON object containing key-value pairs to send to\n the watch. Values in arrays that are greater then 255 will be mod 255\n before sending.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + }, + { + "title": "param", + "description": "A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n callback to run if the watch acknowledges (ACK) this message.", + "lineNumber": 11, + "type": { + "type": "NameExpression", + "name": "AppMessageAckCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n callback to run if the watch does not acknowledges (NACK) this message.", + "lineNumber": 13, + "type": { + "type": "NameExpression", + "name": "AppMessageOnFailure" + }, + "name": "onSuccess" + } + ], + "loc": { + "start": { + "line": 63, + "column": 0 + }, + "end": { + "line": 78, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 79, + "column": 0 + }, + "end": { + "line": 79, + "column": 65 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The transaction id for this message", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 36, + "offset": 35 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Number" + } + } + ], + "params": [ + { + "name": "data", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A JSON object containing key-value pairs to send to\n the watch. Values in arrays that are greater then 255 will be mod 255\n before sending.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 145 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 145 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 20, + "offset": 145 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + }, + { + "name": "onSuccess", + "lineNumber": 11, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppMessageAckCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppMessageAckCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 73, + "offset": 72 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n callback to run if the watch acknowledges (ACK) this message.", + "position": { + "start": { + "line": 1, + "column": 73, + "offset": 72 + }, + "end": { + "line": 2, + "column": 66, + "offset": 138 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 138 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 138 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppMessageAckCallback" + } + }, + { + "name": "onSuccess", + "lineNumber": 13, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppMessageNackCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppMessageNackCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 75, + "offset": 74 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n callback to run if the watch does not acknowledges (NACK) this message.", + "position": { + "start": { + "line": 1, + "column": 75, + "offset": 74 + }, + "end": { + "line": 2, + "column": 76, + "offset": 150 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 76, + "offset": 150 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 76, + "offset": 150 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppMessageOnFailure" + } + }, + { + "title": "param", + "name": "onFailure", + "lineNumber": 79 + } + ], + "name": "sendAppMessage", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "sendAppMessage", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.sendAppMessage" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Get the user's timeline token for this app. This is a string and is\n unique per user per app. Note: In order for timeline tokens to be\n available, the app must be submitted to the Pebble appstore, but does not\n need to be public. Read more in the\n ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 5, + "offset": 260 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + }, + { + "type": "link", + "url": "/guides/pebble-timeline/timeline-js/", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "timeline guides" + } + ], + "position": { + "start": { + "line": 5, + "column": 5, + "offset": 260 + }, + "end": { + "line": 5, + "column": 65, + "offset": 320 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 5, + "column": 65, + "offset": 320 + }, + "end": { + "line": 5, + "column": 66, + "offset": 321 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 66, + "offset": 321 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 66, + "offset": 321 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Get the user's timeline token for this app. This is a string and is\n unique per user per app. Note: In order for timeline tokens to be\n available, the app must be submitted to the Pebble appstore, but does not\n need to be public. Read more in the\n {@link /guides/pebble-timeline/timeline-js/ timeline guides}.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n callback to handle a successful attempt to get the timeline token.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "TimelineTokenCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a\n failed attempt to get the timeline token.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 82, + "column": 0 + }, + "end": { + "line": 93, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 93, + "column": 3 + }, + "end": { + "line": 93, + "column": 64 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "onSuccess", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 21, + "offset": 20 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#TimelineTokenCallback", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "TimelineTokenCallback" + } + ], + "position": { + "start": { + "line": 1, + "column": 21, + "offset": 20 + }, + "end": { + "line": 1, + "column": 73, + "offset": 72 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\n callback to handle a successful attempt to get the timeline token.", + "position": { + "start": { + "line": 1, + "column": 73, + "offset": 72 + }, + "end": { + "line": 2, + "column": 71, + "offset": 143 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 71, + "offset": 143 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 71, + "offset": 143 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TimelineTokenCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a\n failed attempt to get the timeline token.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 86 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 86 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 46, + "offset": 86 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "getTimelineToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getTimelineToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getTimelineToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Subscribe the user to a timeline topic for your app. This can be used\n to filter the different pins a user could receive according to their\n preferences, as well as maintain groups of users.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 196 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 196 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 54, + "offset": 196 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Subscribe the user to a timeline topic for your app. This can be used\n to filter the different pins a user could receive according to their\n preferences, as well as maintain groups of users.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The desired topic to be subscribed to. Users will\n receive all pins pushed to this topic.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "topic" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a\n successful subscription attempt.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a\n failed subscription attempt.", + "lineNumber": 9, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 95, + "column": 0 + }, + "end": { + "line": 106, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 107, + "column": 0 + }, + "end": { + "line": 107, + "column": 69 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "topic", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The desired topic to be subscribed to. Users will\n receive all pins pushed to this topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 92 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 92 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "onSuccess", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a\n successful subscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 77 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 77 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 37, + "offset": 77 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "onFailure", + "lineNumber": 9, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a\n failed subscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 73 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 73 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 73 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineSubscribe", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineSubscribe", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineSubscribe" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Unsubscribe the user from a timeline topic for your app. Once\n unsubscribed, the user will no longer receive any pins pushed to this\n topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 146 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 146 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 11, + "offset": 146 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Unsubscribe the user from a timeline topic for your app. Once\n unsubscribed, the user will no longer receive any pins pushed to this\n topic.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The desired topic to be unsubscribed from.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "topic" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a\n successful unsubscription attempt.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "A developer-defined callback to handle a\n failed unsubscription attempt.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 109, + "column": 0 + }, + "end": { + "line": 119, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 120, + "column": 0 + }, + "end": { + "line": 120, + "column": 71 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "topic", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The desired topic to be unsubscribed from.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "onSuccess", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a\n successful unsubscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 79 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 79 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 39, + "offset": 79 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + }, + { + "name": "onFailure", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A developer-defined callback to handle a\n failed unsubscription attempt.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 75 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 75 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 75 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineUnsubscribe", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineUnsubscribe", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineUnsubscribe" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Obtain a list of topics that the user is currently subscribed to. The\n length of the list should be checked to determine whether the user is\n subscribed to at least one topic.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 38, + "offset": 181 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 38, + "offset": 181 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 38, + "offset": 181 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Obtain a list of topics that the user is currently subscribed to. The\n length of the list should be checked to determine whether the user is\n subscribed to at least one topic.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "The developer-defined function to process the\n retuned list of topic strings.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "TimelineTopicsCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "The developer-defined function to gracefully\n handle any errors in obtaining the user's subscriptions.", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 122, + "column": 0 + }, + "end": { + "line": 131, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 132, + "column": 0 + }, + "end": { + "line": 132, + "column": 65 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "onSuccess", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined function to process the\n retuned list of topic strings.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 80 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 80 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 35, + "offset": 80 + } + } + }, + "type": { + "type": "NameExpression", + "name": "TimelineTopicsCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined function to gracefully\n handle any errors in obtaining the user's subscriptions.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 105 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 105 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 61, + "offset": 105 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Function" + } + } + ], + "name": "timelineSubscription", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "timelineSubscription", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.timelineSubscription" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Obtain an object containing information on the currently connected\n Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 23, + "offset": 89 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 23, + "offset": 89 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "code", + "lang": null, + "value": "**Note:** This function is only available when using the Pebble Time\nsmartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\nfor details on how to use this function.", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 91 + }, + "end": { + "line": 6, + "column": 45, + "offset": 326 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 45, + "offset": 326 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Obtain an object containing information on the currently connected\n Pebble smartwatch.\n\n **Note:** This function is only available when using the Pebble Time\n smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n for details on how to use this function.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A {@link #WatchInfo WatchInfo} object detailing the\n currently connected Pebble watch.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "WatchInfo" + } + } + ], + "loc": { + "start": { + "line": 135, + "column": 0 + }, + "end": { + "line": 145, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 146, + "column": 0 + }, + "end": { + "line": 146, + "column": 43 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#WatchInfo", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "WatchInfo" + } + ], + "position": { + "start": { + "line": 1, + "column": 3, + "offset": 2 + }, + "end": { + "line": 1, + "column": 31, + "offset": 30 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object detailing the\n currently connected Pebble watch.", + "position": { + "start": { + "line": 1, + "column": 31, + "offset": 30 + }, + "end": { + "line": 2, + "column": 38, + "offset": 89 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 89 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 89 + } + } + }, + "type": { + "type": "NameExpression", + "name": "WatchInfo" + } + } + ], + "name": "getActiveWatchInfo", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getActiveWatchInfo", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getActiveWatchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns a unique account token that is associated with the Pebble\n account of the current user.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 98 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 33, + "offset": 98 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "code", + "lang": null, + "value": "**Note:** The behavior of this changed slightly in SDK 3.0. Read the\n{@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\ndetails and how to adapt older tokens.", + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 100 + }, + "end": { + "line": 6, + "column": 43, + "offset": 293 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 43, + "offset": 293 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Returns a unique account token that is associated with the Pebble\n account of the current user.\n\n **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n details and how to adapt older tokens.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A string that is guaranteed to be identical across devices\n if the user owns several Pebble or several mobile devices. From the\n developer's perspective, the account token of a user is identical across\n platforms and across all the developer's watchapps. If the user is not\n logged in, this function will return an empty string ('').", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "loc": { + "start": { + "line": 148, + "column": 0 + }, + "end": { + "line": 161, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 163, + "column": 0 + }, + "end": { + "line": 163, + "column": 40 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A string that is guaranteed to be identical across devices\n if the user owns several Pebble or several mobile devices. From the\n developer's perspective, the account token of a user is identical across\n platforms and across all the developer's watchapps. If the user is not\n logged in, this function will return an empty string ('').", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 345 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 345 + }, + "indent": [ + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 5, + "column": 63, + "offset": 345 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "getAccountToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getAccountToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getAccountToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Returns a a unique token that can be used to identify a Pebble device.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 71, + "offset": 70 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Returns a a unique token that can be used to identify a Pebble device.", + "lineNumber": 1 + }, + { + "title": "returns", + "description": "A string that is is guaranteed to be identical for each\n Pebble device for the same app across different mobile devices. The token\n is unique to your app and cannot be used to track Pebble devices across\n applications.", + "lineNumber": 3, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "loc": { + "start": { + "line": 165, + "column": 0 + }, + "end": { + "line": 172, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 173, + "column": 0 + }, + "end": { + "line": 173, + "column": 38 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "returns": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "A string that is is guaranteed to be identical for each\n Pebble device for the same app across different mobile devices. The token\n is unique to your app and cannot be used to track Pebble devices across\n applications.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 227 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 227 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 4, + "column": 18, + "offset": 227 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "name": "getWatchToken", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "getWatchToken", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.getWatchToken" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Triggers a reload of the app glance which first clears any existing \n slices and then adds the provided slices.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 111 + } + } + }, + "tags": [ + { + "title": "desc", + "description": "Triggers a reload of the app glance which first clears any existing \n slices and then adds the provided slices.", + "lineNumber": 1 + }, + { + "title": "param", + "description": "{@link #AppGlanceSlice AppGlanceSlice} \n JSON objects to add to the app glance.", + "lineNumber": 4, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "appGlanceSlices" + }, + { + "title": "param", + "description": "The developer-defined \n callback which is called if the reload operation succeeds.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadSuccessCallback" + }, + "name": "onSuccess" + }, + { + "title": "param", + "description": "The developer-defined \n callback which is called if the reload operation fails.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadFailureCallback" + }, + "name": "onFailure" + } + ], + "loc": { + "start": { + "line": 176, + "column": 0 + }, + "end": { + "line": 186, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js", + "code": "/**\n * @namespace Pebble\n *\n * @desc The Pebble namespace is where all of the Pebble specific methods and\n * properties exist. This class contains methods belonging to PebbleKit JS and\n * allows bi-directional communication with a C watchapp, as well as managing\n * the user's timeline subscriptions and obtaining information about their\n * watch.\n */\nvar Pebble = new Object;\n\n\n/**\n * @desc Adds a listener for Pebble JS events, such as when an ``AppMessage`` is\n * received or the configuration view is opened or closed.\n *\n * #### `event` Options\n *\n * Possible values:\n *\n * * `ready` - The watchapp has been launched and the PebbleKit JS component\n * is now ready to receive events.\n * * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The\n * AppMessage ``Dictionary`` is contained in the payload property (i.e:\n * event.payload). The payload consists of key-value pairs, where the keys\n * are strings containing integers (e.g: \"0\"), or aliases for keys defined\n * in package.json (e.g: \"KEY_EXAMPLE\"). Values should be integers, strings\n * or byte arrays (arrays of characters).\n * * `showConfiguration` - The user has requested the app's configuration\n * webview to be displayed. This can occur either upon the app's initial\n * install or when the user taps 'Settings' in the 'My Pebble' view withtin\n * the phone app.\n * * `webviewclosed` - The configuration webview was closed by the user. If\n * the webview had a response, it will be contained in the response property\n * (i.e: event.response). This response can be used to feed back user\n * preferences to the watchapp.\n *\n * @param {String} event - The type of the event, from the list described above.\n * @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}\n * to receive any events of the type specified that occur.\n*/\nPebble.addEventListener = function(event, callback) { };\n\n/**\n * @desc Remove an existing event listener previously registered with\n * ``Pebble.addEventListener()``.\n *\n * @param {String} type - The type of the event listener to be removed. See\n * ``Pebble.addEventListener()`` for a list of available types.\n * @param {Function} callback - The existing developer-defined function that was\n * previously registered.\n */\nPebble.removeEventListener = function(type, callback) { };\n\n/**\n * @desc Show a simple modal notification on the connected watch.\n *\n * @param {String} title - The title of the notification\n * @param {String} body - The main content of the notification\n */\nPebble.showSimpleNotificationOnPebble = function(title, body) { };\n\n/**\n * @desc Send an AppMessage to the app running on the watch. Messages should be\n * in the form of JSON objects containing key-value pairs. See\n * Pebble.sendAppMessage() for valid key and value data types.\n * Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n *\n * @returns {Number} The transaction id for this message\n *\n * @param {Object} data - A JSON object containing key-value pairs to send to\n * the watch. Values in arrays that are greater then 255 will be mod 255\n * before sending.\n * @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}\n * callback to run if the watch acknowledges (ACK) this message.\n * @param {AppMessageOnFailure} onSuccess - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}\n * callback to run if the watch does not acknowledges (NACK) this message.\n */\nPebble.sendAppMessage = function(data, onSuccess, onFailure) { };\n\n\n/**\n * @desc Get the user's timeline token for this app. This is a string and is\n * unique per user per app. Note: In order for timeline tokens to be\n * available, the app must be submitted to the Pebble appstore, but does not\n * need to be public. Read more in the\n * {@link /guides/pebble-timeline/timeline-js/ timeline guides}.\n *\n * @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}\n * callback to handle a successful attempt to get the timeline token.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed attempt to get the timeline token.\n */Pebble.getTimelineToken = function(onSuccess, onFailure) { };\n\n/**\n * @desc Subscribe the user to a timeline topic for your app. This can be used\n * to filter the different pins a user could receive according to their\n * preferences, as well as maintain groups of users.\n *\n * @param {String} topic - The desired topic to be subscribed to. Users will\n * receive all pins pushed to this topic.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful subscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed subscription attempt.\n */\nPebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Unsubscribe the user from a timeline topic for your app. Once\n * unsubscribed, the user will no longer receive any pins pushed to this\n * topic.\n *\n * @param {String} topic - The desired topic to be unsubscribed from.\n * @param {Function} onSuccess - A developer-defined callback to handle a\n * successful unsubscription attempt.\n * @param {Function} onFailure - A developer-defined callback to handle a\n * failed unsubscription attempt.\n */\nPebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };\n\n/**\n * @desc Obtain a list of topics that the user is currently subscribed to. The\n * length of the list should be checked to determine whether the user is\n * subscribed to at least one topic.\n *\n * @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the\n * retuned list of topic strings.\n * @param {Function} onFailure - The developer-defined function to gracefully\n * handle any errors in obtaining the user's subscriptions.\n */\nPebble.timelineSubscription = function(onSuccess, onFailure) { };\n\n\n/**\n * @desc Obtain an object containing information on the currently connected\n * Pebble smartwatch.\n *\n * **Note:** This function is only available when using the Pebble Time\n * smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}\n * for details on how to use this function.\n *\n * @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the\n * currently connected Pebble watch.\n */\nPebble.getActiveWatchInfo = function() { };\n\n/**\n * @desc Returns a unique account token that is associated with the Pebble\n * account of the current user.\n *\n * **Note:** The behavior of this changed slightly in SDK 3.0. Read the\n * {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the\n * details and how to adapt older tokens.\n *\n * @returns {String} A string that is guaranteed to be identical across devices\n * if the user owns several Pebble or several mobile devices. From the\n * developer's perspective, the account token of a user is identical across\n * platforms and across all the developer's watchapps. If the user is not\n * logged in, this function will return an empty string ('').\n */\n\nPebble.getAccountToken = function() { };\n\n/**\n * @desc Returns a a unique token that can be used to identify a Pebble device.\n *\n * @returns {String} A string that is is guaranteed to be identical for each\n * Pebble device for the same app across different mobile devices. The token\n * is unique to your app and cannot be used to track Pebble devices across\n * applications.\n */\nPebble.getWatchToken = function() { };\n\n\n/**\n * @desc Triggers a reload of the app glance which first clears any existing \n * slices and then adds the provided slices.\n *\n * @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice} \n * JSON objects to add to the app glance.\n * @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined \n * callback which is called if the reload operation succeeds.\n * @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined \n * callback which is called if the reload operation fails.\n */\nPebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };\n\n/**\n * @typedef {Function} AppGlanceReloadSuccessCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload is successful.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n /**\n * @typedef {Function} AppGlanceReloadFailureCallback\n * @memberof Pebble\n *\n * @desc Called when AppGlanceReload has failed.\n * @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object \n * containing the app glance slices.\n */\n\n/**\n * @typedef {Function} AppMessageAckCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message.\n */\n\n/**\n * @typedef {Function} AppMessageNackCallback\n * @memberof Pebble\n *\n * @desc Called when an AppMessage is not acknowledged by the watch.\n * @param {Object} data - An object containing the callback data. This contains\n * the `transactionId` which is the transaction ID of the message\n * @param {String} error - The error message\n */\n\n/**\n * @typedef {Function} EventCallback\n * @memberof Pebble\n *\n * @desc Called when an event of any type previously registered occurs. The\n * parameters are different depending on the type of event, shown in\n * brackets for each parameter listed here.\n * @param {Object} event - An object containing the event information, including:\n * * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * * `payload` - The dictionary sent over ``AppMessage`` consisting of\n * key-value pairs. *This field only exists for `appmessage` events.*\n * * `response` - The contents of the URL navigated to when the\n * configuration page was closed, after the anchor. This may be encoded,\n * which will require use of decodeURIComponent() before reading as an\n * object. *This field only exists for for `webviewclosed` events.*\n */\n\n/**\n * @typedef {Function} TimelineTokenCallback\n * @memberof Pebble\n *\n * @desc Called when the user's timeline token is available.\n * @param {String} token - The user's token.\n */\n\n/**\n * @typedef {Function} TimelineTopicsCallback\n * @memberof Pebble\n *\n * @desc Called when the user's list of subscriptions is available for processing by the developer.\n * @param {[String]} List of topic strings that the user is subscribed to\n */\n\n/**\n * @typedef {Object} WatchInfo\n * @memberof Pebble\n *\n * @desc Provides information about the connected Pebble smartwatch.\n *\n * @property {String} platform - The hardware platform, such as `basalt` or `emery`.\n * @property {String} model - The watch model, such as `pebble_black`\n * @property {String} language - The user's currently selected language on\n * this watch.\n * @property {Object} firmware - An object containing information about the\n * watch's firmware version, including:\n * * `major` - The major version\n * * `minor` - The minor version\n * * `patch` - The patch version\n * * `suffix` - Any additional version information, such as `beta3`\n*/\n\n/**\n * @typedef {Object} AppGlanceSlice\n * @memberof Pebble\n *\n * @desc The structure of an app glance.\n *\n * @property {String} expirationTime - Optional ISO date-time string of when \n the entry should expire and no longer be shown in the app glance.\n * @property {Object} layout - An object containing:\n * * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.\n*/\n" + }, + "params": [ + { + "name": "appGlanceSlices", + "lineNumber": 4, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 39, + "offset": 38 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " \n JSON objects to add to the app glance.", + "position": { + "start": { + "line": 1, + "column": 39, + "offset": 38 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 43, + "offset": 82 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + }, + { + "name": "onSuccess", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined \n callback which is called if the reload operation succeeds.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 63, + "offset": 85 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadSuccessCallback" + } + }, + { + "name": "onFailure", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The developer-defined \n callback which is called if the reload operation fails.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 60, + "offset": 82 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceReloadFailureCallback" + } + } + ], + "name": "appGlanceReload", + "kind": "function", + "memberof": "Pebble", + "scope": "static", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "appGlanceReload", + "kind": "function", + "scope": "static" + } + ], + "namespace": "Pebble.appGlanceReload" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when AppGlanceReload is successful.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 43, + "offset": 42 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppGlanceReloadSuccessCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when AppGlanceReload is successful.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An {@link #AppGlanceSlice AppGlanceSlice} object \n containing the app glance slices.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "AppGlanceSlices" + } + ], + "loc": { + "start": { + "line": 189, + "column": 0 + }, + "end": { + "line": 196, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceReloadSuccessCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "AppGlanceSlices", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 4, + "offset": 3 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 4, + "offset": 3 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object \n containing the app glance slices.", + "position": { + "start": { + "line": 1, + "column": 42, + "offset": 41 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceReloadSuccessCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceReloadSuccessCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when AppGlanceReload has failed.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppGlanceReloadFailureCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when AppGlanceReload has failed.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An {@link #AppGlanceSlice AppGlanceSlice} object \n containing the app glance slices.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + }, + "name": "AppGlanceSlices" + } + ], + "loc": { + "start": { + "line": 198, + "column": 1 + }, + "end": { + "line": 205, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceReloadFailureCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "AppGlanceSlices", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 4, + "offset": 3 + }, + "indent": [] + } + }, + { + "type": "link", + "url": "#AppGlanceSlice", + "title": null, + "jsdoc": true, + "children": [ + { + "type": "text", + "value": "AppGlanceSlice" + } + ], + "position": { + "start": { + "line": 1, + "column": 4, + "offset": 3 + }, + "end": { + "line": 1, + "column": 42, + "offset": 41 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " object \n containing the app glance slices.", + "position": { + "start": { + "line": 1, + "column": 42, + "offset": 41 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 38, + "offset": 87 + } + } + }, + "type": { + "type": "NameExpression", + "name": "AppGlanceSlice" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceReloadFailureCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceReloadFailureCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an AppMessage is acknowledged by the watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 56, + "offset": 55 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppMessageAckCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an AppMessage is acknowledged by the watch.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the callback data. This contains\n the `transactionId` which is the transaction ID of the message.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + } + ], + "loc": { + "start": { + "line": 207, + "column": 0 + }, + "end": { + "line": 214, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppMessageAckCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "data", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the callback data. This contains\n the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 7, + "offset": 60 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transactionId", + "position": { + "start": { + "line": 2, + "column": 7, + "offset": 60 + }, + "end": { + "line": 2, + "column": 22, + "offset": 75 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " which is the transaction ID of the message.", + "position": { + "start": { + "line": 2, + "column": 22, + "offset": 75 + }, + "end": { + "line": 2, + "column": 66, + "offset": 119 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 119 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 119 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppMessageAckCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppMessageAckCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an AppMessage is not acknowledged by the watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "AppMessageNackCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an AppMessage is not acknowledged by the watch.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the callback data. This contains\n the `transactionId` which is the transaction ID of the message", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "data" + }, + { + "title": "param", + "description": "The error message", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "error" + } + ], + "loc": { + "start": { + "line": 216, + "column": 0 + }, + "end": { + "line": 224, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppMessageNackCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "data", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the callback data. This contains\n the ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 7, + "offset": 60 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "inlineCode", + "value": "transactionId", + "position": { + "start": { + "line": 2, + "column": 7, + "offset": 60 + }, + "end": { + "line": 2, + "column": 22, + "offset": 75 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " which is the transaction ID of the message", + "position": { + "start": { + "line": 2, + "column": 22, + "offset": 75 + }, + "end": { + "line": 2, + "column": 65, + "offset": 118 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 118 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 65, + "offset": 118 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + }, + { + "name": "error", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The error message", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppMessageNackCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleAppMessageNackCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when an event of any type previously registered occurs. The\n parameters are different depending on the type of event, shown in\n brackets for each parameter listed here.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 181 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 181 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 45, + "offset": 181 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "EventCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when an event of any type previously registered occurs. The\n parameters are different depending on the type of event, shown in\n brackets for each parameter listed here.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "An object containing the event information, including:\n * `type` - The type of event fired, from the list in the description of ``Pebble.addEventListener()``.\n * `payload` - The dictionary sent over ``AppMessage`` consisting of\n key-value pairs. *This field only exists for `appmessage` events.*\n * `response` - The contents of the URL navigated to when the\n configuration page was closed, after the anchor. This may be encoded,\n which will require use of decodeURIComponent() before reading as an\n object. *This field only exists for for `webviewclosed` events.*", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "event" + } + ], + "loc": { + "start": { + "line": 226, + "column": 0 + }, + "end": { + "line": 241, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "EventCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "event", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing the event information, including:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 55, + "offset": 54 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "type", + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 60 + }, + "end": { + "line": 2, + "column": 12, + "offset": 66 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The type of event fired, from the list in the description of ", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 66 + }, + "end": { + "line": 2, + "column": 76, + "offset": 130 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "Pebble.addEventListener()", + "position": { + "start": { + "line": 2, + "column": 76, + "offset": 130 + }, + "end": { + "line": 2, + "column": 105, + "offset": 159 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 2, + "column": 105, + "offset": 159 + }, + "end": { + "line": 2, + "column": 106, + "offset": 160 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 60 + }, + "end": { + "line": 2, + "column": 106, + "offset": 160 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 55 + }, + "end": { + "line": 2, + "column": 106, + "offset": 160 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "payload", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 166 + }, + "end": { + "line": 3, + "column": 15, + "offset": 175 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The dictionary sent over ", + "position": { + "start": { + "line": 3, + "column": 15, + "offset": 175 + }, + "end": { + "line": 3, + "column": 43, + "offset": 203 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "AppMessage", + "position": { + "start": { + "line": 3, + "column": 43, + "offset": 203 + }, + "end": { + "line": 3, + "column": 57, + "offset": 217 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " consisting of\nkey-value pairs. ", + "position": { + "start": { + "line": 3, + "column": 57, + "offset": 217 + }, + "end": { + "line": 4, + "column": 23, + "offset": 254 + }, + "indent": [ + 6 + ] + } + }, + { + "type": "emphasis", + "children": [ + { + "type": "text", + "value": "This field only exists for ", + "position": { + "start": { + "line": 4, + "column": 24, + "offset": 255 + }, + "end": { + "line": 4, + "column": 51, + "offset": 282 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "appmessage", + "position": { + "start": { + "line": 4, + "column": 51, + "offset": 282 + }, + "end": { + "line": 4, + "column": 63, + "offset": 294 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " events.", + "position": { + "start": { + "line": 4, + "column": 63, + "offset": 294 + }, + "end": { + "line": 4, + "column": 71, + "offset": 302 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 23, + "offset": 254 + }, + "end": { + "line": 4, + "column": 72, + "offset": 303 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 166 + }, + "end": { + "line": 4, + "column": 72, + "offset": 303 + }, + "indent": [ + 6 + ] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 161 + }, + "end": { + "line": 4, + "column": 72, + "offset": 303 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "response", + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 309 + }, + "end": { + "line": 5, + "column": 16, + "offset": 319 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The contents of the URL navigated to when the\nconfiguration page was closed, after the anchor. This may be encoded,\nwhich will require use of decodeURIComponent() before reading as an\nobject. ", + "position": { + "start": { + "line": 5, + "column": 16, + "offset": 319 + }, + "end": { + "line": 8, + "column": 14, + "offset": 529 + }, + "indent": [ + 6, + 6, + 6 + ] + } + }, + { + "type": "emphasis", + "children": [ + { + "type": "text", + "value": "This field only exists for for ", + "position": { + "start": { + "line": 8, + "column": 15, + "offset": 530 + }, + "end": { + "line": 8, + "column": 46, + "offset": 561 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "webviewclosed", + "position": { + "start": { + "line": 8, + "column": 46, + "offset": 561 + }, + "end": { + "line": 8, + "column": 61, + "offset": 576 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " events.", + "position": { + "start": { + "line": 8, + "column": 61, + "offset": 576 + }, + "end": { + "line": 8, + "column": 69, + "offset": 584 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 8, + "column": 14, + "offset": 529 + }, + "end": { + "line": 8, + "column": 70, + "offset": 585 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 309 + }, + "end": { + "line": 8, + "column": 70, + "offset": 585 + }, + "indent": [ + 6, + 6, + 6 + ] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 304 + }, + "end": { + "line": 8, + "column": 70, + "offset": 585 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 55 + }, + "end": { + "line": 8, + "column": 70, + "offset": 585 + }, + "indent": [ + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 8, + "column": 70, + "offset": 585 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "EventCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleEventCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when the user's timeline token is available.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "TimelineTokenCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when the user's timeline token is available.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "The user's token.", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "token" + } + ], + "loc": { + "start": { + "line": 243, + "column": 0 + }, + "end": { + "line": 249, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "TimelineTokenCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "token", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The user's token.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 18, + "offset": 17 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "TimelineTokenCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleTimelineTokenCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Called when the user's list of subscriptions is available for processing by the developer.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 91, + "offset": 90 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Function" + }, + "name": "TimelineTopicsCallback" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Called when the user's list of subscriptions is available for processing by the developer.", + "lineNumber": 4 + }, + { + "title": "param", + "description": "of topic strings that the user is subscribed to", + "lineNumber": 5, + "type": { + "type": "ArrayType", + "elements": [ + { + "type": "NameExpression", + "name": "String" + } + ] + }, + "name": "List" + } + ], + "loc": { + "start": { + "line": 251, + "column": 0 + }, + "end": { + "line": 257, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "TimelineTopicsCallback", + "type": { + "type": "NameExpression", + "name": "Function" + }, + "memberof": "Pebble", + "params": [ + { + "name": "List", + "lineNumber": 5, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "of topic strings that the user is subscribed to", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 48, + "offset": 47 + } + } + }, + "type": { + "type": "ArrayType", + "elements": [ + { + "type": "NameExpression", + "name": "String" + } + ] + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "TimelineTopicsCallback", + "kind": "typedef" + } + ], + "namespace": "PebbleTimelineTopicsCallback" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Provides information about the connected Pebble smartwatch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 60, + "offset": 59 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "WatchInfo" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "Provides information about the connected Pebble smartwatch.", + "lineNumber": 4 + }, + { + "title": "property", + "description": "The hardware platform, such as `basalt` or `emery`.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "platform" + }, + { + "title": "property", + "description": "The watch model, such as `pebble_black`", + "lineNumber": 7, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "model" + }, + { + "title": "property", + "description": "The user's currently selected language on\n this watch.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "language" + }, + { + "title": "property", + "description": "An object containing information about the\n watch's firmware version, including:\n * `major` - The major version\n * `minor` - The minor version\n * `patch` - The patch version\n * `suffix` - Any additional version information, such as `beta3`", + "lineNumber": 10, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "firmware" + } + ], + "loc": { + "start": { + "line": 259, + "column": 0 + }, + "end": { + "line": 275, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "WatchInfo", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "memberof": "Pebble", + "properties": [ + { + "name": "platform", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The hardware platform, such as ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "basalt", + "position": { + "start": { + "line": 1, + "column": 32, + "offset": 31 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " or ", + "position": { + "start": { + "line": 1, + "column": 40, + "offset": 39 + }, + "end": { + "line": 1, + "column": 44, + "offset": 43 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "emery", + "position": { + "start": { + "line": 1, + "column": 44, + "offset": 43 + }, + "end": { + "line": 1, + "column": 51, + "offset": 50 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 51, + "offset": 50 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 52, + "offset": 51 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "model", + "lineNumber": 7, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The watch model, such as ", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 26, + "offset": 25 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "pebble_black", + "position": { + "start": { + "line": 1, + "column": 26, + "offset": 25 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 40, + "offset": 39 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "language", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The user's currently selected language on\n this watch.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 16, + "offset": 57 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "firmware", + "lineNumber": 10, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing information about the\n watch's firmware version, including:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 83 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 41, + "offset": 83 + }, + "indent": [ + 1 + ] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "major", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 89 + }, + "end": { + "line": 3, + "column": 13, + "offset": 96 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The major version", + "position": { + "start": { + "line": 3, + "column": 13, + "offset": 96 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 89 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 84 + }, + "end": { + "line": 3, + "column": 33, + "offset": 116 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "minor", + "position": { + "start": { + "line": 4, + "column": 6, + "offset": 122 + }, + "end": { + "line": 4, + "column": 13, + "offset": 129 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The minor version", + "position": { + "start": { + "line": 4, + "column": 13, + "offset": 129 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 6, + "offset": 122 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1, + "offset": 117 + }, + "end": { + "line": 4, + "column": 33, + "offset": 149 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "patch", + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 155 + }, + "end": { + "line": 5, + "column": 13, + "offset": 162 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - The patch version", + "position": { + "start": { + "line": 5, + "column": 13, + "offset": 162 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 6, + "offset": 155 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1, + "offset": 150 + }, + "end": { + "line": 5, + "column": 33, + "offset": 182 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "suffix", + "position": { + "start": { + "line": 6, + "column": 6, + "offset": 188 + }, + "end": { + "line": 6, + "column": 14, + "offset": 196 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Any additional version information, such as ", + "position": { + "start": { + "line": 6, + "column": 14, + "offset": 196 + }, + "end": { + "line": 6, + "column": 61, + "offset": 243 + }, + "indent": [] + } + }, + { + "type": "inlineCode", + "value": "beta3", + "position": { + "start": { + "line": 6, + "column": 61, + "offset": 243 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 6, + "offset": 188 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 6, + "column": 1, + "offset": 183 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 84 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + }, + "indent": [ + 1, + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 6, + "column": 68, + "offset": 250 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "WatchInfo", + "kind": "typedef" + } + ], + "namespace": "PebbleWatchInfo" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The structure of an app glance.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + } + } + }, + "tags": [ + { + "title": "typedef", + "description": null, + "lineNumber": 1, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "AppGlanceSlice" + }, + { + "title": "memberof", + "description": "Pebble", + "lineNumber": 2 + }, + { + "title": "desc", + "description": "The structure of an app glance.", + "lineNumber": 4 + }, + { + "title": "property", + "description": "Optional ISO date-time string of when \nthe entry should expire and no longer be shown in the app glance.", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "String" + }, + "name": "expirationTime" + }, + { + "title": "property", + "description": "An object containing:\n * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.\n * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.", + "lineNumber": 8, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "layout" + } + ], + "loc": { + "start": { + "line": 277, + "column": 0 + }, + "end": { + "line": 288, + "column": 2 + } + }, + "context": { + "loc": { + "start": { + "line": 187, + "column": 0 + }, + "end": { + "line": 187, + "column": 77 + } + }, + "file": "/Users/orviwan/Pebble/developer.getpebble.com/js-docs/pkjs/Pebble.js" + }, + "kind": "typedef", + "name": "AppGlanceSlice", + "type": { + "type": "NameExpression", + "name": "Object" + }, + "memberof": "Pebble", + "properties": [ + { + "name": "expirationTime", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Optional ISO date-time string of when \nthe entry should expire and no longer be shown in the app glance.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 2, + "column": 66, + "offset": 104 + } + } + }, + "type": { + "type": "NameExpression", + "name": "String" + } + }, + { + "name": "layout", + "lineNumber": 8, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "An object containing:", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + }, + { + "type": "list", + "ordered": false, + "start": null, + "loose": false, + "children": [ + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "icon", + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 27 + }, + "end": { + "line": 2, + "column": 12, + "offset": 33 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.", + "position": { + "start": { + "line": 2, + "column": 12, + "offset": 33 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 6, + "offset": 27 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 22 + }, + "end": { + "line": 2, + "column": 101, + "offset": 122 + }, + "indent": [] + } + }, + { + "type": "listItem", + "loose": false, + "checked": null, + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "subtitleTemplateString", + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 128 + }, + "end": { + "line": 3, + "column": 30, + "offset": 152 + }, + "indent": [] + } + }, + { + "type": "text", + "value": " - Template string that will be displayed in the subtitle of the app glance.", + "position": { + "start": { + "line": 3, + "column": 30, + "offset": 152 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 6, + "offset": 128 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1, + "offset": 123 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 2, + "column": 1, + "offset": 22 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + }, + "indent": [ + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 3, + "column": 106, + "offset": 228 + } + } + }, + "type": { + "type": "NameExpression", + "name": "Object" + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + }, + { + "name": "AppGlanceSlice", + "kind": "typedef" + } + ], + "namespace": "PebbleAppGlanceSlice" + } + ] + }, + "path": [ + { + "name": "Pebble", + "kind": "namespace" + } + ], + "namespace": "Pebble" + } +] \ No newline at end of file diff --git a/devsite/spec/pebble_documentation_android_spec.rb b/devsite/spec/pebble_documentation_android_spec.rb new file mode 100644 index 00000000..38c5d3e2 --- /dev/null +++ b/devsite/spec/pebble_documentation_android_spec.rb @@ -0,0 +1,115 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative './spec_helper' +require 'jekyll' +require_relative '../lib/pebble_documentation' +require_relative '../lib/pebble_documentation_pebblekit_android' + +require_relative './docs' + +describe Pebble::DocumentationPebbleKitAndroid do + include_context 'docs' + + before do + @docs_config = load_config + @site = fake_site + allow(File).to receive(:read).and_return('') + source = ENV['DOCS_URL'] + @docs_config['pebblekit_android'] + @doc = Pebble::DocumentationPebbleKitAndroid.new(@site, source) + end + + describe '#load_symbols' do + before do + @symbols = [] + @doc.load_symbols(@symbols) + end + + it 'should add symbols to the list' do + expect(@symbols.size).to be > 0 + end + + it 'should contain some known symbols' do + symbols = %w( + com.getpebble.android.kit.Constants + com.getpebble.android.kit.PebbleKit.registerReceivedDataHandler + com.getpebble.android.kit.util.PebbleDictionary.remove + ) + symbols.each { |name| expect(find_symbol(@symbols, name)).to_not be(nil) } + end + + it 'should tag all symbols with the correct language' do + expect(@symbols.any? { |symbol| symbol[:language] != 'pebblekit_android' }) + .to be(false) + end + + it 'should create symbols with correct URLS' do + wrong_prefix = @symbols.any? do |symbol| + !symbol[:url].start_with?('/docs/pebblekit-android/') + end + expect(wrong_prefix).to be(false) + end + + it 'should not create two symbols that clash' do + expect(clashing_symbols(@symbols)).to be false + end + end + + describe '#create_pages' do + before do + @pages = [] + @doc.create_pages(@pages) + end + + it 'should add some pages to the list' do + expect(@pages.size).to be > 0 + end + + it 'should create pages with contents and group exposed' do + page = @pages[0] + expect(page.contents).to_not be(nil) + expect(page.group).to_not be(nil) + end + end + + describe '#build_tree' do + before do + @tree = [] + @doc.build_tree(@tree) + end + + it 'should populate the tree' do + expect(@tree.size).to be > 0 + end + + it 'should create tree objects formatted properly' do + @tree.each { |branch| valid_branch(branch) } + end + end + + describe 'completeness' do + before do + @tree = [] + @symbols = [] + @pages = [] + @doc.build_tree(@tree) + @doc.create_pages(@pages) + @doc.load_symbols(@symbols) + end + + it 'should have a page for every symbol' do + symbol_to_page_completeness(@symbols, @pages) + end + end +end diff --git a/devsite/spec/pebble_documentation_c_spec.rb b/devsite/spec/pebble_documentation_c_spec.rb new file mode 100644 index 00000000..52c421bf --- /dev/null +++ b/devsite/spec/pebble_documentation_c_spec.rb @@ -0,0 +1,124 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative './spec_helper' +require 'jekyll' +require_relative '../lib/pebble_documentation' +require_relative '../lib/pebble_documentation_c' +require_relative '../plugins/generator_redirects' + +require_relative './docs' + +describe Pebble::DocumentationC do + include_context 'docs' + + before do + @docs_config = load_config + @site = fake_site + allow(File).to receive(:read) do | path | + if path.start_with?('/_layouts/') + '' + else + File.open(path) do |f| + f.read + end + end + end + source = ENV['DOCS_URL'] + @docs_config['c'] + @doc = Pebble::DocumentationC.new(@site, source, '/docs/c/') + end + + describe '#load_symbols' do + before do + @symbols = [] + @doc.load_symbols(@symbols) + end + + it 'should add symbols to the list' do + expect(@symbols.size).to be > 0 + end + + it 'should contain some known symbols' do + symbols = %w( + Window + text_layer_create + GBitmap + GColorBlack + GBitmapSequence + ) + symbols.each { |name| expect(find_symbol(@symbols, name)).to_not be(nil), "Could not find symbol #{name}" } + end + + it 'should tag all symbols with the correct language' do + expect(@symbols.any? { |symbol| symbol[:language] != 'c' }).to be(false) + end + + it 'should create symbols with correct URL prefix' do + wrong_prefix = @symbols.any? do |symbol| + !symbol[:url].start_with?('/docs/c/') + end + expect(wrong_prefix).to be(false) + end + + it 'should not create two symbols that clash' do + expect(clashing_symbols(@symbols)).to be false + end + end + + describe '#create_pages' do + before do + @pages = [] + @doc.create_pages(@pages) + end + + it 'should add some pages to the list' do + expect(@pages.size).to be > 0 + end + + it 'should create pages with the group exposed' do + page = @pages[0] + expect(page.group).to_not be(nil) + end + end + + describe '#build_tree' do + before do + @tree = [] + @doc.build_tree(@tree) + end + + it 'should populate the tree' do + expect(@tree.size).to be > 0 + end + + it 'should create tree objects formatted properly' do + @tree.each { |branch| valid_branch2(branch) } + end + end + + describe 'completeness' do + before do + @tree = [] + @symbols = [] + @pages = [] + @doc.build_tree(@tree) + @doc.create_pages(@pages) + @doc.load_symbols(@symbols) + end + + it 'should have a page for every symbol' do + symbol_to_page_completeness(@symbols, @pages) + end + end +end diff --git a/devsite/spec/pebble_documentation_ios_spec.rb b/devsite/spec/pebble_documentation_ios_spec.rb new file mode 100644 index 00000000..b2521e18 --- /dev/null +++ b/devsite/spec/pebble_documentation_ios_spec.rb @@ -0,0 +1,128 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative './spec_helper' +require 'jekyll' +require_relative '../lib/pebble_documentation' +require_relative '../lib/pebble_documentation_pebblekit_ios' + +require_relative './docs' + +describe Pebble::DocumentationPebbleKitIos do + include_context 'docs' + + before do + @docs_config = load_config + @site = fake_site + allow(File).to receive(:read).and_return('') + source = ENV['DOCS_URL'] + @docs_config['pebblekit_ios'] + @doc = Pebble::DocumentationPebbleKitIos.new(@site, source, '/docs/pebblekit-ios/') + end + + describe '#load_symbols' do + before do + @symbols = [] + @doc.load_symbols(@symbols) + end + + it 'should add symbols to the list' do + expect(@symbols.size).to be > 0 + end + + it 'should contain some known symbols' do + symbols = [ + 'pb_pebbleDictionaryData:', + 'isMobileAppInstalled', + 'PBWatch', + 'dataLoggingService:hasSInt8s:numberOfItems:forDataLoggingSession:' + ] + symbols.each { |name| expect(find_symbol(@symbols, name)).to_not be(nil) } + end + + it 'should tag all symbols with the correct language' do + expect(@symbols.any? { |symbol| symbol[:language] != 'pebblekit_ios' }).to be(false) + end + + it 'should create symbols with correct URLS' do + wrong_prefix = @symbols.any? do |symbol| + !symbol[:url].start_with?('/docs/pebblekit-ios/') + end + expect(wrong_prefix).to be(false) + end + + it 'should not create two symbols that clash' + + it 'should trim whitespace from summary' do + not_trimmed = @symbols.any? do |symbol| + !symbol[:summary].nil? && symbol[:summary].strip != symbol[:summary] + end + + expect(not_trimmed).to be(false) + end + + it 'should URLencode the symbol URLS' do + bad_urls = @symbols.any? do |symbol| + symbol[:url].include?('+') + end + expect(bad_urls).to be(false) + end + end + + describe '#create_pages' do + before do + @pages = [] + @doc.create_pages(@pages) + end + + it 'should add some pages to the list' do + expect(@pages.size).to be > 0 + end + + it 'should create pages with contents and group exposed' do + page = @pages[0] + expect(page.contents).to_not be(nil) + expect(page.group).to_not be(nil) + end + end + + describe '#build_tree' do + before do + @tree = [] + @doc.build_tree(@tree) + end + + it 'should populate the tree' do + expect(@tree.size).to be > 0 + end + + it 'should create tree objects formatted properly' do + @tree.each { |branch| valid_branch(branch) } + end + end + + describe 'completeness' do + before do + @tree = [] + @symbols = [] + @pages = [] + @doc.build_tree(@tree) + @doc.create_pages(@pages) + @doc.load_symbols(@symbols) + end + + it 'should have a page for every symbol' do + symbol_to_page_completeness(@symbols, @pages) + end + end +end diff --git a/devsite/spec/pebble_documentation_js_spec.rb b/devsite/spec/pebble_documentation_js_spec.rb new file mode 100644 index 00000000..a197ddb6 --- /dev/null +++ b/devsite/spec/pebble_documentation_js_spec.rb @@ -0,0 +1,107 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative './spec_helper' +require 'jekyll' +require_relative '../lib/pebble_documentation' +require_relative '../lib/pebble_documentation_js' +require_relative '../plugins/pebble_markdown_parser' + +require_relative './docs' + +describe Pebble::DocumentationJs do + include_context 'docs' + + before do + @docs_config = load_config + @site = fake_site + @site.data['js'] = JSON.parse(File.read('spec/fixtures/js.json')) + @doc = Pebble::DocumentationJs.new(@site, 'js', '/docs/js/', 'js') + end + + describe '#load_symbols' do + before do + @symbols = [] + @doc.load_symbols(@symbols) + end + + it 'should add symbols to the list' do + expect(@symbols.size).to be > 0 + end + + it 'should contain some known symbols' do + expect(find_symbol(@symbols, 'addEventListener')).to_not be(nil) + expect(find_symbol(@symbols, 'removeEventListener')).to_not be(nil) + end + + it 'should tag all symbols with the correct language' do + expect(@symbols.any? { |symbol| symbol[:language] != 'js' }).to be(false) + end + + it 'should create symbols with correct URLS' do + expect(@symbols.any? { |symbol| !symbol[:url].start_with?('/docs/js/') }) + .to be(false) + end + + it 'should not create two symbols that clash' do + expect(clashing_symbols(@symbols)).to be false + end + end + + describe '#create_pages' do + before do + @pages = [] + @doc.create_pages(@pages) + end + + it 'should add some pages to the list' do + expect(@pages.size).to be > 0 + end + + it 'should create pages with module details exposed' do + page = @pages[0] + expect(page.js_module).to_not be(nil) + end + end + + describe '#build_tree' do + before do + @tree = [] + @doc.build_tree(@tree) + end + + it 'should populate the tree' do + expect(@tree.size).to be > 0 + end + + it 'should create tree objects formatted properly' do + @tree.each { |branch| valid_branch(branch) } + end + end + + describe 'completeness' do + before do + @tree = [] + @symbols = [] + @pages = [] + @doc.build_tree(@tree) + @doc.create_pages(@pages) + @doc.load_symbols(@symbols) + end + + it 'should have a page for every symbol' do + symbol_to_page_completeness(@symbols, @pages) + end + end +end diff --git a/devsite/spec/pebble_markdown_parser_spec.rb b/devsite/spec/pebble_markdown_parser_spec.rb new file mode 100644 index 00000000..1c0e7a15 --- /dev/null +++ b/devsite/spec/pebble_markdown_parser_spec.rb @@ -0,0 +1,258 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative './spec_helper' +require 'redcarpet' +require 'jekyll' +require 'nokogiri' + +require_relative '../plugins/pebble_markdown_parser' + +describe Jekyll::Converters::Markdown::PebbleMarkdownParser, '#convert' do + it 'renders normal markdown properly' do + expect(parser.convert('**Bold Text**').strip) + .to eql('

    Bold Text

    ') + end + + it 'adds anchors to headers' do + doc = md2doc("# Header 1\n## Header 2") + expect(doc.at_css('h1').attribute('id').value).to eql('header-1') + expect(doc.at_css('h1').content).to eql('Header 1') + expect(doc.at_css('h2').attribute('id').value).to eql('header-2') + expect(doc.at_css('h2').content).to eql('Header 2') + end + + describe 'links' do + it 'prepends the site base URL to absolute links' do + links = [ + ['/link1/', '/BASEURL/link1/'], + ['link2/', 'link2/'], + ['//link3/', '//link3/'] + ] + doc = md2doc(links.map { |link| "[link](#{link[0]})" }.join('\n')) + links.each_with_index do |link, index| + expect(doc.at_css("a:nth-child(#{index + 1})").attribute('href').value) + .to eql(link[1]) + end + end + + describe 'buttons' do + it 'can generate a simple button' do + doc = md2doc('[button >](http://google.com)') + expect(doc.at_css('a').attribute('class').value) + .to eql('btn btn--markdown') + expect(doc.at_css('a').content).to eql('button') + end + + it 'can generate a button with additional classes' do + doc = md2doc('[button >{large,pink}](http://google.com)') + expect(doc.at_css('a').attribute('class').value) + .to eql('btn btn--markdown btn--large btn--pink') + expect(doc.at_css('a').content).to eql('button') + end + end + + describe 'data attributes' do + it 'can add a single data attribute to a link' do + doc = md2doc('[link](http://google.com "title >{item:foo}")') + expect(doc.at_css('a').attribute('data-item').value).to eql('foo') + expect(doc.at_css('a').attribute('title').value).to eql('title') + end + + it 'can add a multiple data attributes to a link' do + doc = md2doc('[link](http://google.com "title >{item:foo,item2:bar}")') + expect(doc.at_css('a').attribute('data-item').value).to eql('foo') + expect(doc.at_css('a').attribute('data-item2').value).to eql('bar') + expect(doc.at_css('a').attribute('title').value).to eql('title') + end + + it 'can add data attributes without a title' do + doc = md2doc('[link](http://google.com " >{item:foo}")') + expect(doc.at_css('a').attribute('data-item').value).to eql('foo') + expect(doc.at_css('a').attribute('title').value).to eql('') + end + end + + describe 'embeds' do + it 'generates YouTube video embeds' do + id = 'dQw4w9WgXcQ' + doc = md2doc("[EMBED](https://www.youtube.com/watch?v=#{id})") + expect(doc.at_css('iframe').attribute('src').value) + .to eql("//www.youtube.com/embed/#{id}?rel=0") + doc = md2doc("[EMBED](https://www.youtube.com/v/#{id})") + expect(doc.at_css('iframe').attribute('src').value) + .to eql("//www.youtube.com/embed/#{id}?rel=0") + doc = md2doc("[EMBED](//www.youtube.com/v/#{id})") + expect(doc.at_css('iframe').attribute('src').value) + .to eql("//www.youtube.com/embed/#{id}?rel=0") + end + + it 'generates YouTube playlist embeds' do + id = 'PLDPHNsf1sb4-EXiIUOGqsX81etZepB7C0' + doc = md2doc("[EMBED](https://www.youtube.com/playlist?list=#{id})") + expect(doc.at_css('iframe').attribute('src').value) + .to eql("//www.youtube.com/embed/videoseries?list=#{id}") + end + + it 'generates Vimeo video embeds' do + id = '0581081381803' + doc = md2doc("[EMBED](//player.vimeo.com/video/#{id}?title=0&byline=0)") + expect(doc.at_css('iframe').attribute('src').value) + .to eql("//player.vimeo.com/video/#{id}") + end + + it 'generated Gist embeds' do + id = '57a8c2b8670b9a6b7119' + doc = md2doc("[EMBED](https://gist.github.com/matthewtole/#{id})") + expect(doc.at_css('script').attribute('src').value) + .to eql("//gist.github.com/matthewtole/#{id}.js") + end + end + end + + describe 'images' do + it 'prepends the asset path for absolute paths' do + images = [ + ['/img1.png', '//ASSETS/img1.png'], + ['img2.png', 'img2.png'], + ['//img3.png', '//img3.png'] + ] + doc = md2doc(images.map { |img| "![](#{img[0]})" }.join(' ')) + images.each_with_index do |img, index| + expect(doc.at_css("img:nth-child(#{index + 1})").attribute('src').value) + .to eql(img[1]) + end + end + + describe 'size' do + it 'can specify the width of an image' do + doc = md2doc('![Alt Text](image_url =300)') + expect(doc.at_css('img').attribute('width').value).to eql('300') + end + + it 'can specify the width and height of an image' do + doc = md2doc('![Alt Text](image_url =300x200)') + expect(doc.at_css('img').attribute('width').value).to eql('300') + expect(doc.at_css('img').attribute('height').value).to eql('200') + end + end + end + + describe 'paragraphs' do + it 'adds a data attribute for sdk platforms' do + doc = md2doc("^LC^ Local SDK instructions\n\n^CP^CloudPebble instructions\n\nRegular old paragraph") + expect(doc.at_css('p:nth-child(1)').attribute('data-sdk-platform').value).to eql('local') + expect(doc.at_css('p:nth-child(1)').content).to eql('Local SDK instructions') + expect(doc.at_css('p:nth-child(1)')['class']).to include('platform-specific') + expect(doc.at_css('p:nth-child(2)').attribute('data-sdk-platform').value).to eql('cloudpebble') + expect(doc.at_css('p:nth-child(2)').content).to eql('CloudPebble instructions') + expect(doc.at_css('p:nth-child(2)')['class']).to include('platform-specific') + expect(doc.at_css('p:nth-child(3)').attribute('data-sdk-platform')).to eql(nil) + expect(doc.at_css('p:nth-child(3)').content).to eql('Regular old paragraph') + end + end + + describe 'block code' do + it 'processes code blocks with Pygments' do + doc = md2doc("```\nvar a = 1;\n```") + expect(doc.at_css('div.highlight')).to_not be_nil + expect(doc.css('div.highlight span').size).to be > 0 + end + + it 'skips highlighting if language is text' do + doc = md2doc("```text\nvar a = 1;\n```") + expect(doc.at_css('div.highlight > pre')).to_not be_nil + expect(doc.css('div.highlight span').size).to be(0) + end + + it 'adds no-copy to classes if nc prefix' do + doc = md2doc("```nc|js\nvar a = 1;\n```") + div = doc.at_css('div.highlight') + expect(div['class']).to include('no-copy') + end + end + + describe 'documentation auto-linking' do + before do + allow(Jekyll.logger).to receive(:warn) + end + it 'should convert double backticks into documentation links' do + doc = md2doc("``Window``") + link = doc.at_css('a') + expect(link).to_not be_nil + expect(link.attribute('href').value).to eql('/BASEURL/docs/c/Window/') + expect(link['class']).to include('link--docs') + expect(link.at_css('code').content).to eql('Window') + end + + it 'should use the language prefix where available' do + doc = md2doc("``pebblejs:Window``") + link = doc.at_css('a') + expect(link).to_not be_nil + expect(link.attribute('href').value).to eql('/BASEURL/docs/pebblejs/Window/') + expect(link['class']).to include('link--docs') + expect(link.at_css('code').content).to eql('Window') + end + + it 'should print a warning if symbol not found' do + expect(Jekyll.logger).to receive(:warn).once + doc = md2doc("``pebblejs:NotASymbol``") + end + + it 'should remove the prefix if symbol not found' do + doc = md2doc("``pebblejs:NotASymbol``") + code = doc.at_css('code') + expect(code.content).to eql('NotASymbol') + end + + it 'should convert double backticks from inside links' do + doc = md2doc("[LinkTitle](``window``)") + link = doc.at_css('a') + expect(link).to_not be_nil + expect(link.attribute('href').value).to eql('/BASEURL/docs/c/Window/') + expect(link['class']).to include('link--docs') + expect(link.content).to eql('LinkTitle') + end + + it 'should handle missing link uses' do + doc = md2doc("[LinkTitle](``DoesNotExist``)") + code = doc.at_css('p') + expect(code.content).to eql('LinkTitle') + end + + end + + def md2doc(markdown) + html = parser.convert(markdown) + Nokogiri::HTML.fragment(html) + end + + def parser + site = { + 'baseurl' => '/BASEURL', + 'asset_path' => '//ASSETS', + docs: { + symbols: fake_symbols + } + } + Jekyll::Converters::Markdown::PebbleMarkdownParser.new(site) + end + + def fake_symbols + [ + { name: 'Window', url: '/docs/c/Window/', language: 'c' }, + { name: 'Window', url: '/docs/pebblejs/Window/', language: 'pebblejs' } + ] + end +end diff --git a/devsite/spec/spec_helper.rb b/devsite/spec/spec_helper.rb new file mode 100644 index 00000000..f0c54dc7 --- /dev/null +++ b/devsite/spec/spec_helper.rb @@ -0,0 +1,32 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'simplecov' +SimpleCov.start + +require 'dotenv' +Dotenv.load + +require 'open-uri' + +if OpenURI::Buffer.const_defined?('StringMax') + OpenURI::Buffer.send :remove_const, 'StringMax' +end +OpenURI::Buffer.const_set 'StringMax', 0 + +RSpec.configure do |config| + config.fail_fast = true + config.formatter = :documentation + config.color = true +end diff --git a/devsite/spec/toc_generator_spec.rb b/devsite/spec/toc_generator_spec.rb new file mode 100644 index 00000000..75a9b669 --- /dev/null +++ b/devsite/spec/toc_generator_spec.rb @@ -0,0 +1,60 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'redcarpet' +require 'jekyll' + +require_relative '../lib/toc_generator' + +describe Pebble::TocGenerator do + + describe '#generate' do + + before do + @toc = Pebble::TocGenerator.new + end + + it 'returns empty array on blank document' do + expect(@toc.generate('')).to eql([]) + end + + it 'returns array of top level headers' do + document = "# Header 1\n\n#Header 2" + contents = @toc.generate(document) + expect(contents.size).to eql(2) + expect(contents[0][1]).to eql('Header 1') + expect(contents[1][0]).to eql('header-2') + expect(contents[1][2]).to eql(1) + end + + it 'works for any depth of header' do + document = "# Header 1\n\n#### Header 2\n\n###### Header 3" + contents = @toc.generate(document) + expect(contents.size).to eql(3) + expect(contents[0][2]).to eql(1) + expect(contents[1][2]).to eql(4) + expect(contents[2][2]).to eql(6) + end + + it 'normalises the headers so the smallest is 1' do + document = "#### Header 1\n\n###### Header 2" + contents = @toc.generate(document) + expect(contents.size).to eql(2) + expect(contents[0][2]).to eql(1) + expect(contents[1][2]).to eql(3) + end + + end + +end diff --git a/third_party/algolia/LICENSE b/third_party/algolia/LICENSE new file mode 100644 index 00000000..dafd2a7c --- /dev/null +++ b/third_party/algolia/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2013 Algolia +http://www.algolia.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third_party/algolia/algoliasearch.js b/third_party/algolia/algoliasearch.js new file mode 100644 index 00000000..64dd1be4 --- /dev/null +++ b/third_party/algolia/algoliasearch.js @@ -0,0 +1,2364 @@ +/* + * Copyright (c) 2013 Algolia + * http://www.algolia.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var ALGOLIA_VERSION = '2.8.6'; + +/* + * Copyright (c) 2013 Algolia + * http://www.algolia.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Algolia Search library initialization + * @param applicationID the application ID you have in your admin interface + * @param apiKey a valid API key for the service + * @param methodOrOptions the hash of parameters for initialization. It can contains: + * - method (optional) specify if the protocol used is http or https (http by default to make the first search query faster). + * You need to use https is you are doing something else than just search queries. + * - hosts (optional) the list of hosts that you have received for the service + * - dsn (optional) set to true if your account has the Distributed Search Option + * - dsnHost (optional) override the automatic computation of dsn hostname + */ +var AlgoliaSearch = function(applicationID, apiKey, methodOrOptions, resolveDNS, hosts) { + var self = this; + this.applicationID = applicationID; + this.apiKey = apiKey; + this.dsn = true; + this.dsnHost = null; + this.hosts = []; + this.currentHostIndex = 0; + this.requestTimeoutInMs = 2000; + this.extraHeaders = []; + this.jsonp = null; + + var method; + var tld = 'net'; + if (typeof methodOrOptions === 'string') { // Old initialization + method = methodOrOptions; + } else { + // Take all option from the hash + var options = methodOrOptions || {}; + if (!this._isUndefined(options.method)) { + method = options.method; + } + if (!this._isUndefined(options.tld)) { + tld = options.tld; + } + if (!this._isUndefined(options.dsn)) { + this.dsn = options.dsn; + } + if (!this._isUndefined(options.hosts)) { + hosts = options.hosts; + } + if (!this._isUndefined(options.dsnHost)) { + this.dsnHost = options.dsnHost; + } + if (!this._isUndefined(options.requestTimeoutInMs)) { + this.requestTimeoutInMs = +options.requestTimeoutInMs; + } + if (!this._isUndefined(options.jsonp)) { + this.jsonp = options.jsonp; + } + } + // If hosts is undefined, initialize it with applicationID + if (this._isUndefined(hosts)) { + hosts = [ + this.applicationID + '-1.algolia.' + tld, + this.applicationID + '-2.algolia.' + tld, + this.applicationID + '-3.algolia.' + tld + ]; + } + // detect is we use http or https + this.host_protocol = 'http://'; + if (this._isUndefined(method) || method === null) { + this.host_protocol = ('https:' == document.location.protocol ? 'https' : 'http') + '://'; + } else if (method === 'https' || method === 'HTTPS') { + this.host_protocol = 'https://'; + } + // Add hosts in random order + for (var i = 0; i < hosts.length; ++i) { + if (Math.random() > 0.5) { + this.hosts.reverse(); + } + this.hosts.push(this.host_protocol + hosts[i]); + } + if (Math.random() > 0.5) { + this.hosts.reverse(); + } + // then add Distributed Search Network host if there is one + if (this.dsn || this.dsnHost != null) { + if (this.dsnHost) { + this.hosts.unshift(this.host_protocol + this.dsnHost); + } else { + this.hosts.unshift(this.host_protocol + this.applicationID + '-dsn.algolia.' + tld); + } + } +}; + +function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) { + + function _getHitExplanationForOneAttr_recurse(obj, foundWords) { + var res = []; + if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) { + var match = false; + for (var j = 0; j < obj.matchedWords.length; ++j) { + var word = obj.matchedWords[j]; + if (!(word in foundWords)) { + foundWords[word] = 1; + match = true; + } + } + if (match) { + res.push(obj.value); + } + } else if (Object.prototype.toString.call(obj) === '[object Array]') { + for (var i = 0; i < obj.length; ++i) { + var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords); + res = res.concat(array); + } + } else if (typeof obj === 'object') { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)){ + res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords)); + } + } + } + return res; + } + + function _getHitExplanationForOneAttr(hit, foundWords, attr) { + var base = hit._highlightResult || hit; + if (attr.indexOf('.') === -1) { + if (attr in base) { + return _getHitExplanationForOneAttr_recurse(base[attr], foundWords); + } + return []; + } + var array = attr.split('.'); + var obj = base; + for (var i = 0; i < array.length; ++i) { + if (Object.prototype.toString.call(obj) === '[object Array]') { + var res = []; + for (var j = 0; j < obj.length; ++j) { + res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.'))); + } + return res; + } + if (array[i] in obj) { + obj = obj[array[i]]; + } else { + return []; + } + } + return _getHitExplanationForOneAttr_recurse(obj, foundWords); + } + + var res = {}; + var foundWords = {}; + var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute); + res.title = (title.length > 0) ? title[0] : ''; + res.subtitles = []; + + if (typeof otherAttributes !== 'undefined') { + for (var i = 0; i < otherAttributes.length; ++i) { + var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]); + for (var j = 0; j < attr.length; ++j) { + res.subtitles.push({ attr: otherAttributes[i], value: attr[j] }); + } + } + } + return res; +} + + +AlgoliaSearch.prototype = { + /* + * Delete an index + * + * @param indexName the name of index to delete + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + deleteIndex: function(indexName, callback) { + this._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexName), + callback: callback }); + }, + /** + * Move an existing index. + * @param srcIndexName the name of index to copy. + * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + moveIndex: function(srcIndexName, dstIndexName, callback) { + var postObj = {operation: 'move', destination: dstIndexName}; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', + body: postObj, + callback: callback }); + + }, + /** + * Copy an existing index. + * @param srcIndexName the name of index to copy. + * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + copyIndex: function(srcIndexName, dstIndexName, callback) { + var postObj = {operation: 'copy', destination: dstIndexName}; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', + body: postObj, + callback: callback }); + }, + /** + * Return last log entries. + * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry). + * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + getLogs: function(callback, offset, length) { + if (this._isUndefined(offset)) { + offset = 0; + } + if (this._isUndefined(length)) { + length = 10; + } + + this._jsonRequest({ method: 'GET', + url: '/1/logs?offset=' + offset + '&length=' + length, + callback: callback }); + }, + /* + * List all existing indexes (paginated) + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with index list or error description if success is false. + * @param page The page to retrieve, starting at 0. + */ + listIndexes: function(callback, page) { + var params = page ? '?page=' + page : ''; + this._jsonRequest({ method: 'GET', + url: '/1/indexes' + params, + callback: callback }); + }, + + /* + * Get the index object initialized + * + * @param indexName the name of index + * @param callback the result callback with one argument (the Index instance) + */ + initIndex: function(indexName) { + return new this.Index(this, indexName); + }, + /* + * List all existing user keys with their associated ACLs + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + listUserKeys: function(callback) { + this._jsonRequest({ method: 'GET', + url: '/1/keys', + callback: callback }); + }, + /* + * Get ACL of a user key + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + getUserKeyACL: function(key, callback) { + this._jsonRequest({ method: 'GET', + url: '/1/keys/' + key, + callback: callback }); + }, + /* + * Delete an existing user key + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + deleteUserKey: function(key, callback) { + this._jsonRequest({ method: 'DELETE', + url: '/1/keys/' + key, + callback: callback }); + }, + /* + * Add an existing user key + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKey: function(acls, callback) { + var aclsObject = {}; + aclsObject.acl = acls; + this._jsonRequest({ method: 'POST', + url: '/1/keys', + body: aclsObject, + callback: callback }); + }, + /* + * Add an existing user key + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) + * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. + * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + aclsObject.validity = validity; + aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; + aclsObject.maxHitsPerQuery = maxHitsPerQuery; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + indexObj.indexName + '/keys', + body: aclsObject, + callback: callback }); + }, + + /** + * Set the extra security tagFilters header + * @param {string|array} tags The list of tags defining the current security filters + */ + setSecurityTags: function(tags) { + if (Object.prototype.toString.call(tags) === '[object Array]') { + var strTags = []; + for (var i = 0; i < tags.length; ++i) { + if (Object.prototype.toString.call(tags[i]) === '[object Array]') { + var oredTags = []; + for (var j = 0; j < tags[i].length; ++j) { + oredTags.push(tags[i][j]); + } + strTags.push('(' + oredTags.join(',') + ')'); + } else { + strTags.push(tags[i]); + } + } + tags = strTags.join(','); + } + this.tagFilters = tags; + }, + + /** + * Set the extra user token header + * @param {string} userToken The token identifying a uniq user (used to apply rate limits) + */ + setUserToken: function(userToken) { + this.userToken = userToken; + }, + + /* + * Initialize a new batch of search queries + */ + startQueriesBatch: function() { + this.batch = []; + }, + /* + * Add a search query in the batch + * + * @param query the full text query + * @param args (optional) if set, contains an object with query parameters: + * - attributes: an array of object attribute names to retrieve + * (if not set all attributes are retrieve) + * - attributesToHighlight: an array of object attribute names to highlight + * (if not set indexed attributes are highlighted) + * - minWordSizefor1Typo: the minimum number of characters to accept one typo. + * Defaults to 3. + * - minWordSizefor2Typos: the minimum number of characters to accept two typos. + * Defaults to 7. + * - getRankingInfo: if set, the result hits will contain ranking information in + * _rankingInfo attribute + * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0. + * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10. + */ + addQueryInBatch: function(indexName, query, args) { + var params = 'query=' + encodeURIComponent(query); + if (!this._isUndefined(args) && args !== null) { + params = this._getSearchParams(args, params); + } + this.batch.push({ indexName: indexName, params: params }); + }, + /* + * Clear all queries in cache + */ + clearCache: function() { + this.cache = {}; + }, + /* + * Launch the batch of queries using XMLHttpRequest. + * (Optimized for browser using a POST query to minimize number of OPTIONS queries) + * + * @param callback the function that will receive results + * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime. + */ + sendQueriesBatch: function(callback, delay) { + var as = this; + var params = {requests: []}; + for (var i = 0; i < as.batch.length; ++i) { + params.requests.push(as.batch[i]); + } + window.clearTimeout(as.onDelayTrigger); + if (!this._isUndefined(delay) && delay !== null && delay > 0) { + var onDelayTrigger = window.setTimeout( function() { + as._sendQueriesBatch(params, callback); + }, delay); + as.onDelayTrigger = onDelayTrigger; + } else { + this._sendQueriesBatch(params, callback); + } + }, + + /** + * Set the number of milliseconds a request can take before automatically being terminated. + * + * @param {Number} milliseconds + */ + setRequestTimeout: function(milliseconds) + { + if (milliseconds) { + this.requestTimeoutInMs = parseInt(milliseconds, 10); + } + }, + + /* + * Index class constructor. + * You should not use this method directly but use initIndex() function + */ + Index: function(algoliasearch, indexName) { + this.indexName = indexName; + this.as = algoliasearch; + this.typeAheadArgs = null; + this.typeAheadValueOption = null; + }, + /** + * Add an extra field to the HTTP request + * + * @param key the header field name + * @param value the header field value + */ + setExtraHeader: function(key, value) { + this.extraHeaders.push({ key: key, value: value}); + }, + + _sendQueriesBatch: function(params, callback) { + + if (this.jsonp === null) { + var self = this; + this._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/*/queries', + body: params, + callback: function(success, content) { + if (!success) { + // retry first with JSONP + self.jsonp = true; + self._sendQueriesBatch(params, callback); + } else { + self.jsonp = false; + callback && callback(success, content); + } + } + }); + } else if (this.jsonp) { + var jsonpParams = ''; + for (var i = 0; i < params.requests.length; ++i) { + var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params; + jsonpParams += i + '=' + encodeURIComponent(q) + '&'; + } + var pObj = {params: jsonpParams}; + this._jsonRequest({ cache: this.cache, + method: 'GET', + url: '/1/indexes/*', + body: pObj, + callback: callback }); + } else { + this._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/*/queries', + body: params, + callback: callback}); + } + }, + /* + * Wrapper that try all hosts to maximize the quality of service + */ + _jsonRequest: function(opts) { + var self = this; + var callback = opts.callback; + var cache = null; + var cacheID = opts.url; + if (!this._isUndefined(opts.body)) { + cacheID = opts.url + '_body_' + JSON.stringify(opts.body); + } + if (!this._isUndefined(opts.cache)) { + cache = opts.cache; + if (!this._isUndefined(cache[cacheID])) { + if (!this._isUndefined(callback)) { + setTimeout(function () { callback(true, cache[cacheID]); }, 1); + } + return; + } + } + + opts.successiveRetryCount = 0; + var impl = function() { + if (opts.successiveRetryCount >= self.hosts.length) { + if (!self._isUndefined(callback)) { + opts.successiveRetryCount = 0; + callback(false, { message: 'Cannot connect the Algolia\'s Search API. Please send an email to support@algolia.com to report the issue.' }); + } + return; + } + opts.callback = function(retry, success, res, body) { + if (!success && !self._isUndefined(body)) { + window.console && console.log('Error: ' + body.message); + } + if (success && !self._isUndefined(opts.cache)) { + cache[cacheID] = body; + } + if (!success && retry) { + self.currentHostIndex = ++self.currentHostIndex % self.hosts.length; + opts.successiveRetryCount += 1; + impl(); + } else { + opts.successiveRetryCount = 0; + if (!self._isUndefined(callback)) { + callback(success, body); + } + } + }; + opts.hostname = self.hosts[self.currentHostIndex]; + self._jsonRequestByHost(opts); + }; + impl(); + }, + + _jsonRequestByHost: function(opts) { + var self = this; + var url = opts.hostname + opts.url; + + if (this.jsonp) { + this._makeJsonpRequestByHost(url, opts); + } else { + this._makeXmlHttpRequestByHost(url, opts); + } + }, + + /** + * Make a JSONP request + * + * @param url request url (includes endpoint and path) + * @param opts all request options + */ + _makeJsonpRequestByHost: function(url, opts) { + if (opts.method !== 'GET') { + opts.callback(true, false, null, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' }); + return; + } + + this.jsonpCounter = this.jsonpCounter || 0; + this.jsonpCounter += 1; + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + var cb = 'algoliaJSONP_' + this.jsonpCounter; + var done = false; + var ontimeout = null; + + window[cb] = function(data) { + opts.callback(false, true, null, data); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + }; + + script.type = 'text/javascript'; + script.src = url + '?callback=' + cb + '&X-Algolia-Application-Id=' + this.applicationID + '&X-Algolia-API-Key=' + this.apiKey; + + if (this.tagFilters) { + script.src += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); + } + + if (this.userToken) { + script.src += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); + } + for (var i = 0; i < this.extraHeaders.length; ++i) { + script.src += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; + } + + + if (opts.body && opts.body.params) { + script.src += '&' + opts.body.params; + } + + ontimeout = setTimeout(function() { + script.onload = script.onreadystatechange = script.onerror = null; + window[cb] = function(data) { + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + }; + + opts.callback(true, false, null, { 'message': 'Timeout - Failed to load JSONP script.' }); + head.removeChild(script); + + clearTimeout(ontimeout); + ontimeout = null; + + }, this.requestTimeoutInMs); + + script.onload = script.onreadystatechange = function() { + clearTimeout(ontimeout); + ontimeout = null; + + if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) { + done = true; + + if (typeof window[cb + '_loaded'] === 'undefined') { + opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + } else { + try { delete window[cb + '_loaded']; } catch (e) { window[cb + '_loaded'] = undefined; } + } + script.onload = script.onreadystatechange = null; // Handle memory leak in IE + head.removeChild(script); + } + }; + + script.onerror = function() { + clearTimeout(ontimeout); + ontimeout = null; + + opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); + head.removeChild(script); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + }; + + head.appendChild(script); + }, + + /** + * Make a XmlHttpRequest + * + * @param url request url (includes endpoint and path) + * @param opts all request opts + */ + _makeXmlHttpRequestByHost: function(url, opts) { + var self = this; + var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : {}; + var body = null; + var ontimeout = null; + + if (!this._isUndefined(opts.body)) { + body = JSON.stringify(opts.body); + } + + url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey; + url += '&X-Algolia-Application-Id=' + this.applicationID; + if (this.userToken) { + url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); + } + if (this.tagFilters) { + url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); + } + for (var i = 0; i < this.extraHeaders.length; ++i) { + url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; + } + if ('withCredentials' in xmlHttp) { + xmlHttp.open(opts.method, url, true); + xmlHttp.timeout = this.requestTimeoutInMs * (opts.successiveRetryCount + 1); + if (body !== null) { + /* This content type is specified to follow CORS 'simple header' directive */ + xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + } + } else if (typeof XDomainRequest !== 'undefined') { + // Handle IE8/IE9 + // XDomainRequest only exists in IE, and is IE's way of making CORS requests. + xmlHttp = new XDomainRequest(); + xmlHttp.open(opts.method, url); + } else { + // very old browser, not supported + opts.callback(false, false, null, { 'message': 'CORS not supported' }); + return; + } + + ontimeout = setTimeout(function() { + xmlHttp.abort(); + // Prevent Internet Explorer 9, JScript Error c00c023f + if (xmlHttp.aborted === true) { + stopLoadAnimation(); + return; + } + opts.callback(true, false, null, { 'message': 'Timeout - Could not connect to endpoint ' + url } ); + + clearTimeout(ontimeout); + ontimeout = null; + + }, this.requestTimeoutInMs * (opts.successiveRetryCount + 1)); + + xmlHttp.onload = function(event) { + clearTimeout(ontimeout); + ontimeout = null; + + if (!self._isUndefined(event) && event.target !== null) { + var retry = (event.target.status === 0 || event.target.status === 503); + var success = false; + var response = null; + + if (typeof XDomainRequest !== 'undefined') { + // Handle CORS requests IE8/IE9 + response = event.target.responseText; + success = (response && response.length > 0); + } + else { + response = event.target.response; + success = (event.target.status === 200 || event.target.status === 201); + } + + opts.callback(retry, success, event.target, response ? JSON.parse(response) : null); + } else { + opts.callback(false, true, event, JSON.parse(xmlHttp.responseText)); + } + }; + xmlHttp.ontimeout = function(event) { // stop the network call but rely on ontimeout to call opt.callback + }; + xmlHttp.onerror = function(event) { + clearTimeout(ontimeout); + ontimeout = null; + opts.callback(true, false, null, { 'message': 'Could not connect to host', 'error': event } ); + }; + + xmlHttp.send(body); + }, + + /* + * Transform search param object in query string + */ + _getSearchParams: function(args, params) { + if (this._isUndefined(args) || args === null) { + return params; + } + for (var key in args) { + if (key !== null && args.hasOwnProperty(key)) { + params += (params.length === 0) ? '?' : '&'; + params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]); + } + } + return params; + }, + _isUndefined: function(obj) { + return obj === void 0; + }, + + /// internal attributes + applicationID: null, + apiKey: null, + tagFilters: null, + userToken: null, + hosts: [], + cache: {}, + extraHeaders: [] +}; + +/* + * Contains all the functions related to one index + * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object + */ +AlgoliaSearch.prototype.Index.prototype = { + /* + * Clear all queries in cache + */ + clearCache: function() { + this.cache = {}; + }, + /* + * Add an object in this index + * + * @param content contains the javascript object to add inside the index + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + * @param objectID (optional) an objectID you want to attribute to this object + * (if the attribute already exist the old object will be overwrite) + */ + addObject: function(content, callback, objectID) { + var indexObj = this; + if (this.as._isUndefined(objectID)) { + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName), + body: content, + callback: callback }); + } else { + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), + body: content, + callback: callback }); + } + + }, + /* + * Add several objects + * + * @param objects contains an array of objects to add + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + addObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'addObject', + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Get an object from this index + * + * @param objectID the unique identifier of the object to retrieve + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the object to retrieve or the error message if a failure occured + * @param attributes (optional) if set, contains the array of attribute names to retrieve + */ + getObject: function(objectID, callback, attributes) { + var indexObj = this; + var params = ''; + if (!this.as._isUndefined(attributes)) { + params = '?attributes='; + for (var i = 0; i < attributes.length; ++i) { + if (i !== 0) { + params += ','; + } + params += attributes[i]; + } + } + if (this.as.jsonp === null) { + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params, + callback: callback }); + } else { + var pObj = {params: params}; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), + callback: callback, + body: pObj}); + } + }, + + /* + * Update partially an object (only update attributes passed in argument) + * + * @param partialObject contains the javascript attributes to override, the + * object must contains an objectID attribute + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + */ + partialUpdateObject: function(partialObject, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial', + body: partialObject, + callback: callback }); + }, + /* + * Partially Override the content of several objects + * + * @param objects contains an array of objects to update (each object must contains a objectID attribute) + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + partialUpdateObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'partialUpdateObject', + objectID: objects[i].objectID, + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Override the content of object + * + * @param object contains the javascript object to save, the object must contains an objectID attribute + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + saveObject: function(object, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID), + body: object, + callback: callback }); + }, + /* + * Override the content of several objects + * + * @param objects contains an array of objects to update (each object must contains a objectID attribute) + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + saveObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'updateObject', + objectID: objects[i].objectID, + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Delete an object from the index + * + * @param objectID the unique identifier of object to delete + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + */ + deleteObject: function(objectID, callback) { + if (objectID === null || objectID.length === 0) { + callback(false, { message: 'empty objectID'}); + return; + } + var indexObj = this; + this.as._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), + callback: callback }); + }, + /* + * Search inside the index using XMLHttpRequest request (Using a POST query to + * minimize number of OPTIONS queries: Cross-Origin Resource Sharing). + * + * @param query the full text query + * @param callback the result callback with two arguments: + * success: boolean set to true if the request was successfull. If false, the content contains the error. + * content: the server answer that contains the list of results. + * @param args (optional) if set, contains an object with query parameters: + * - page: (integer) Pagination parameter used to select the page to retrieve. + * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 + * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20. + * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size). + * Attributes are separated with a comma (for example "name,address"). + * You can also use a string array encoding (for example ["name","address"]). + * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index. + * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query. + * Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]). + * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted. + * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted. + * A matchLevel is returned for each highlighted attribute and can contain: + * - full: if all the query terms were found in the attribute, + * - partial: if only some of the query terms were found, + * - none: if none of the query terms were found. + * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`). + * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10). + * You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed. + * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3. + * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7. + * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute. + * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma). + * For example aroundLatLng=47.316669,5.016670). + * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision + * (for example if you set aroundPrecision=100, two objects that are distant of less than 100m will be considered as identical for "geo" ranking parameter). + * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) + * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng). + * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201). + * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) + * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma. + * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`. + * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000. + * You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]). + * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas. + * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3). + * You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3). + * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}). + * - facetFilters: filter the query by a list of facets. + * Facets are separated by commas and each facet is encoded as `attributeName:value`. + * For example: `facetFilters=category:Book,author:John%20Doe`. + * You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`). + * - facets: List of object attributes that you want to use for faceting. + * Attributes are separated with a comma (for example `"category,author"` ). + * You can also use a JSON string array encoding (for example ["category","author"]). + * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter. + * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**. + * - queryType: select how the query words are interpreted, it can be one of the following value: + * - prefixAll: all query words are interpreted as prefixes, + * - prefixLast: only the last word is interpreted as a prefix (default behavior), + * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. + * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query. + * The list of words is comma separated. + * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set. + * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter, + * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results. + * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best + * one is kept and others are removed. + * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime. + */ + search: function(query, callback, args, delay) { + var indexObj = this; + var params = 'query=' + encodeURIComponent(query); + if (!this.as._isUndefined(args) && args !== null) { + params = this.as._getSearchParams(args, params); + } + window.clearTimeout(indexObj.onDelayTrigger); + if (!this.as._isUndefined(delay) && delay !== null && delay > 0) { + var onDelayTrigger = window.setTimeout( function() { + indexObj._search(params, callback); + }, delay); + indexObj.onDelayTrigger = onDelayTrigger; + } else { + this._search(params, callback); + } + }, + + /* + * Browse all index content + * + * @param page Pagination parameter used to select the page to retrieve. + * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 + * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000. + */ + browse: function(page, callback, hitsPerPage) { + var indexObj = this; + var params = '?page=' + page; + if (!this.as._isUndefined(hitsPerPage)) { + params += '&hitsPerPage=' + hitsPerPage; + } + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params, + callback: callback }); + }, + + /* + * Get a Typeahead.js adapter + * @param searchParams contains an object with query parameters (see search for details) + */ + ttAdapter: function(params) { + var self = this; + return function(query, cb) { + self.search(query, function(success, content) { + if (success) { + cb(content.hits); + } + }, params); + }; + }, + + /* + * Wait the publication of a task on the server. + * All server task are asynchronous and you can check with this method that the task is published. + * + * @param taskID the id of the task returned by server + * @param callback the result callback with with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains the list of results + */ + waitTask: function(taskID, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID, + callback: function(success, body) { + if (success) { + if (body.status === 'published') { + callback(true, body); + } else { + setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100); + } + } else { + callback(false, body); + } + }}); + }, + + /* + * This function deletes the index content. Settings and index specific API keys are kept untouched. + * + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the settings object or the error message if a failure occured + */ + clearIndex: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear', + callback: callback }); + }, + /* + * Get settings of this index + * + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the settings object or the error message if a failure occured + */ + getSettings: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + callback: callback }); + }, + + /* + * Set settings for this index + * + * @param settigns the settings object that can contains : + * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3). + * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7). + * - hitsPerPage: (integer) the number of hits per page (default = 10). + * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects. + * If set to null, all attributes are retrieved. + * - attributesToHighlight: (array of strings) default list of attributes to highlight. + * If set to null, all indexed attributes are highlighted. + * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords). + * By default no snippet is computed. If set to null, no snippet is computed. + * - attributesToIndex: (array of strings) the list of fields you want to index. + * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results. + * This parameter has two important uses: + * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to + * retrieve it but you don't want to search in the base64 string. + * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of + * the list will be considered more important than matches in attributes further down the list. + * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable + * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"]. + * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting. + * All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting. + * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled + * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results. + * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best one is kept and others are removed. + * - ranking: (array of strings) controls the way results are sorted. + * We have six available criteria: + * - typo: sort according to number of typos, + * - geo: sort according to decreassing distance when performing a geo-location based search, + * - proximity: sort according to the proximity of query words in hits, + * - attribute: sort according to the order of attributes defined by attributesToIndex, + * - exact: + * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others. + * For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV + * show starting by the v letter before it. + * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix). + * - custom: sort according to a user defined formula set in **customRanking** attribute. + * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"] + * - customRanking: (array of strings) lets you specify part of the ranking. + * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator. + * For example `"customRanking" => ["desc(population)", "asc(name)"]` + * - queryType: Select how the query words are interpreted, it can be one of the following value: + * - prefixAll: all query words are interpreted as prefixes, + * - prefixLast: only the last word is interpreted as a prefix (default behavior), + * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. + * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to ""). + * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to ""). + * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query. + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer or the error message if a failure occured + */ + setSettings: function(settings, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + body: settings, + callback: callback }); + }, + /* + * List all existing user keys associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + listUserKeys: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + callback: callback }); + }, + /* + * Get ACL of a user key associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + getUserKeyACL: function(key, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + callback: callback }); + }, + /* + * Delete an existing user key associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + deleteUserKey: function(key, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + callback: callback }); + }, + /* + * Add an existing user key associated to this index + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKey: function(acls, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + body: aclsObject, + callback: callback }); + }, + /* + * Add an existing user key associated to this index + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) + * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. + * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + aclsObject.validity = validity; + aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; + aclsObject.maxHitsPerQuery = maxHitsPerQuery; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + body: aclsObject, + callback: callback }); + }, + /// + /// Internal methods only after this line + /// + _search: function(params, callback) { + var pObj = {params: params}; + if (this.as.jsonp === null) { + var self = this; + this.as._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', + body: pObj, + callback: function(success, content) { + if (!success) { + // retry first with JSONP + self.as.jsonp = true; + self._search(params, callback); + } else { + self.as.jsonp = false; + callback && callback(success, content); + } + } + }); + } else if (this.as.jsonp) { + this.as._jsonRequest({ cache: this.cache, + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(this.indexName), + body: pObj, + callback: callback }); + } else { + this.as._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', + body: pObj, + callback: callback}); + } + }, + + // internal attributes + as: null, + indexName: null, + cache: {}, + typeAheadArgs: null, + typeAheadValueOption: null, + emptyConstructor: function() {} +}; + +/* + * Copyright (c) 2014 Algolia + * http://www.algolia.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +(function($) { + var extend = function(out) { + out = out || {}; + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) { + continue; + } + for (var key in arguments[i]) { + if (arguments[i].hasOwnProperty(key)) { + out[key] = arguments[i][key]; + } + } + } + return out; + }; + + /** + * Algolia Search Helper providing faceting and disjunctive faceting + * @param {AlgoliaSearch} client an AlgoliaSearch client + * @param {string} index the index name to query + * @param {hash} options an associative array defining the hitsPerPage, list of facets, the list of disjunctive facets and the default facet filters + */ + window.AlgoliaSearchHelper = function(client, index, options) { + /// Default options + var defaults = { + facets: [], // list of facets to compute + disjunctiveFacets: [], // list of disjunctive facets to compute + hitsPerPage: 20, // number of hits per page + defaultFacetFilters: [] // the default list of facetFilters + }; + + this.init(client, index, extend({}, defaults, options)); + }; + + AlgoliaSearchHelper.prototype = { + /** + * Initialize a new AlgoliaSearchHelper + * @param {AlgoliaSearch} client an AlgoliaSearch client + * @param {string} index the index name to query + * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets + * @return {AlgoliaSearchHelper} + */ + init: function(client, index, options) { + this.client = client; + this.index = index; + this.options = options; + this.page = 0; + this.refinements = {}; + this.disjunctiveRefinements = {}; + this.extraQueries = []; + }, + + /** + * Perform a query + * @param {string} q the user query + * @param {function} searchCallback the result callback called with two arguments: + * success: boolean set to true if the request was successfull + * content: the query answer with an extra 'disjunctiveFacets' attribute + */ + search: function(q, searchCallback, searchParams) { + this.q = q; + this.searchCallback = searchCallback; + this.searchParams = searchParams || {}; + this.page = this.page || 0; + this.refinements = this.refinements || {}; + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this._search(); + }, + + /** + * Remove all refinements (disjunctive + conjunctive) + */ + clearRefinements: function() { + this.disjunctiveRefinements = {}; + this.refinements = {}; + }, + + /** + * Ensure a facet refinement exists + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + addDisjunctiveRefine: function(facet, value) { + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + this.disjunctiveRefinements[facet][value] = true; + }, + + /** + * Ensure a facet refinement does not exist + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + removeDisjunctiveRefine: function(facet, value) { + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + try { + delete this.disjunctiveRefinements[facet][value]; + } catch (e) { + this.disjunctiveRefinements[facet][value] = undefined; // IE compat + } + }, + + /** + * Ensure a facet refinement exists + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + addRefine: function(facet, value) { + var refinement = facet + ':' + value; + this.refinements = this.refinements || {}; + this.refinements[refinement] = true; + }, + + /** + * Ensure a facet refinement does not exist + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + removeRefine: function(facet, value) { + var refinement = facet + ':' + value; + this.refinements = this.refinements || {}; + this.refinements[refinement] = false; + }, + + /** + * Toggle refinement state of a facet + * @param {string} facet the facet to refine + * @param {string} value the associated value + * @return {boolean} true if the facet has been found + */ + toggleRefine: function(facet, value) { + for (var i = 0; i < this.options.facets.length; ++i) { + if (this.options.facets[i] == facet) { + var refinement = facet + ':' + value; + this.refinements[refinement] = !this.refinements[refinement]; + this.page = 0; + this._search(); + return true; + } + } + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + for (var j = 0; j < this.options.disjunctiveFacets.length; ++j) { + if (this.options.disjunctiveFacets[j] == facet) { + this.disjunctiveRefinements[facet][value] = !this.disjunctiveRefinements[facet][value]; + this.page = 0; + this._search(); + return true; + } + } + return false; + }, + + /** + * Check the refinement state of a facet + * @param {string} facet the facet + * @param {string} value the associated value + * @return {boolean} true if refined + */ + isRefined: function(facet, value) { + var refinement = facet + ':' + value; + if (this.refinements[refinement]) { + return true; + } + if (this.disjunctiveRefinements[facet] && this.disjunctiveRefinements[facet][value]) { + return true; + } + return false; + }, + + /** + * Go to next page + */ + nextPage: function() { + this._gotoPage(this.page + 1); + }, + + /** + * Go to previous page + */ + previousPage: function() { + if (this.page > 0) { + this._gotoPage(this.page - 1); + } + }, + + /** + * Goto a page + * @param {integer} page The page number + */ + gotoPage: function(page) { + this._gotoPage(page); + }, + + /** + * Configure the page but do not trigger a reload + * @param {integer} page The page number + */ + setPage: function(page) { + this.page = page; + }, + + /** + * Configure the underlying index name + * @param {string} name the index name + */ + setIndex: function(name) { + this.index = name; + }, + + /** + * Get the underlying configured index name + */ + getIndex: function() { + return this.index; + }, + + /** + * Clear the extra queries added to the underlying batch of queries + */ + clearExtraQueries: function() { + this.extraQueries = []; + }, + + /** + * Add an extra query to the underlying batch of queries. Once you add queries + * to the batch, the 2nd parameter of the searchCallback will be an object with a `results` + * attribute listing all search results. + */ + addExtraQuery: function(index, query, params) { + this.extraQueries.push({ index: index, query: query, params: (params || {}) }); + }, + + ///////////// PRIVATE + + /** + * Goto a page + * @param {integer} page The page number + */ + _gotoPage: function(page) { + this.page = page; + this._search(); + }, + + /** + * Perform the underlying queries + */ + _search: function() { + this.client.startQueriesBatch(); + this.client.addQueryInBatch(this.index, this.q, this._getHitsSearchParams()); + var disjunctiveFacets = []; + var unusedDisjunctiveFacets = {}; + for (var i = 0; i < this.options.disjunctiveFacets.length; ++i) { + var facet = this.options.disjunctiveFacets[i]; + if (this._hasDisjunctiveRefinements(facet)) { + disjunctiveFacets.push(facet); + } else { + unusedDisjunctiveFacets[facet] = true; + } + } + for (var i = 0; i < disjunctiveFacets.length; ++i) { + this.client.addQueryInBatch(this.index, this.q, this._getDisjunctiveFacetSearchParams(disjunctiveFacets[i])); + } + for (var i = 0; i < this.extraQueries.length; ++i) { + this.client.addQueryInBatch(this.extraQueries[i].index, this.extraQueries[i].query, this.extraQueries[i].params); + } + var self = this; + this.client.sendQueriesBatch(function(success, content) { + if (!success) { + self.searchCallback(false, content); + return; + } + var aggregatedAnswer = content.results[0]; + aggregatedAnswer.disjunctiveFacets = aggregatedAnswer.disjunctiveFacets || {}; + aggregatedAnswer.facetStats = aggregatedAnswer.facetStats || {}; + for (var facet in unusedDisjunctiveFacets) { + if (aggregatedAnswer.facets[facet] && !aggregatedAnswer.disjunctiveFacets[facet]) { + aggregatedAnswer.disjunctiveFacets[facet] = aggregatedAnswer.facets[facet]; + try { + delete aggregatedAnswer.facets[facet]; + } catch (e) { + aggregatedAnswer.facets[facet] = undefined; // IE compat + } + } + } + for (var i = 0; i < disjunctiveFacets.length; ++i) { + for (var facet in content.results[i + 1].facets) { + aggregatedAnswer.disjunctiveFacets[facet] = content.results[i + 1].facets[facet]; + if (self.disjunctiveRefinements[facet]) { + for (var value in self.disjunctiveRefinements[facet]) { + if (!aggregatedAnswer.disjunctiveFacets[facet][value] && self.disjunctiveRefinements[facet][value]) { + aggregatedAnswer.disjunctiveFacets[facet][value] = 0; + } + } + } + } + for (var stats in content.results[i + 1].facets_stats) { + aggregatedAnswer.facetStats[stats] = content.results[i + 1].facets_stats[stats]; + } + } + if (self.extraQueries.length === 0) { + self.searchCallback(true, aggregatedAnswer); + } else { + var c = { results: [ aggregatedAnswer ] }; + for (var i = 0; i < self.extraQueries.length; ++i) { + c.results.push(content.results[1 + disjunctiveFacets.length + i]); + } + self.searchCallback(true, c); + } + }); + }, + + /** + * Build search parameters used to fetch hits + * @return {hash} + */ + _getHitsSearchParams: function() { + var facets = []; + for (var i = 0; i < this.options.facets.length; ++i) { + facets.push(this.options.facets[i]); + } + for (var i = 0; i < this.options.disjunctiveFacets.length; ++i) { + var facet = this.options.disjunctiveFacets[i]; + if (!this._hasDisjunctiveRefinements(facet)) { + facets.push(facet); + } + } + return extend({}, { + hitsPerPage: this.options.hitsPerPage, + page: this.page, + facets: facets, + facetFilters: this._getFacetFilters() + }, this.searchParams); + }, + + /** + * Build search parameters used to fetch a disjunctive facet + * @param {string} facet the associated facet name + * @return {hash} + */ + _getDisjunctiveFacetSearchParams: function(facet) { + return extend({}, this.searchParams, { + hitsPerPage: 1, + page: 0, + attributesToRetrieve: [], + attributesToHighlight: [], + attributesToSnippet: [], + facets: facet, + facetFilters: this._getFacetFilters(facet) + }); + }, + + /** + * Test if there are some disjunctive refinements on the facet + */ + _hasDisjunctiveRefinements: function(facet) { + for (var value in this.disjunctiveRefinements[facet]) { + if (this.disjunctiveRefinements[facet][value]) { + return true; + } + } + return false; + }, + + /** + * Build facetFilters parameter based on current refinements + * @param {string} facet if set, the current disjunctive facet + * @return {hash} + */ + _getFacetFilters: function(facet) { + var facetFilters = []; + if (this.options.defaultFacetFilters) { + for (var i = 0; i < this.options.defaultFacetFilters.length; ++i) { + facetFilters.push(this.options.defaultFacetFilters[i]); + } + } + for (var refinement in this.refinements) { + if (this.refinements[refinement]) { + facetFilters.push(refinement); + } + } + for (var disjunctiveRefinement in this.disjunctiveRefinements) { + if (disjunctiveRefinement != facet) { + var refinements = []; + for (var value in this.disjunctiveRefinements[disjunctiveRefinement]) { + if (this.disjunctiveRefinements[disjunctiveRefinement][value]) { + refinements.push(disjunctiveRefinement + ':' + value); + } + } + if (refinements.length > 0) { + facetFilters.push(refinements); + } + } + } + return facetFilters; + } + }; +})(); + +/* + * Copyright (c) 2014 Algolia + * http://www.algolia.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +(function($) { + + /** + * Algolia Places API + * @param {string} Your application ID + * @param {string} Your API Key + */ + window.AlgoliaPlaces = function(applicationID, apiKey) { + this.init(applicationID, apiKey); + }; + + AlgoliaPlaces.prototype = { + /** + * @param {string} Your application ID + * @param {string} Your API Key + */ + init: function(applicationID, apiKey) { + this.client = new AlgoliaSearch(applicationID, apiKey, 'http', true, ['places-1.algolia.io', 'places-2.algolia.io', 'places-3.algolia.io']); + this.cache = {}; + }, + + /** + * Perform a query + * @param {string} q the user query + * @param {function} searchCallback the result callback called with two arguments: + * success: boolean set to true if the request was successfull + * content: the query answer with an extra 'disjunctiveFacets' attribute + * @param {hash} the list of search parameters + */ + search: function(q, searchCallback, searchParams) { + var indexObj = this; + var params = 'query=' + encodeURIComponent(q); + if (!this.client._isUndefined(searchParams) && searchParams != null) { + params = this.client._getSearchParams(searchParams, params); + } + var pObj = {params: params, apiKey: this.client.apiKey, appID: this.client.applicationID}; + this.client._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/places/query', + body: pObj, + callback: searchCallback, + removeCustomHTTPHeaders: true }); + } + }; +})(); + +/* + json2.js + 2014-02-04 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function () { + return this.valueOf(); + }; + } + + var cx, + escapable, + gap, + indent, + meta, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/third_party/bourbon/LICENSE b/third_party/bourbon/LICENSE new file mode 100644 index 00000000..a5672409 --- /dev/null +++ b/third_party/bourbon/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2011 [thoughtbot, inc.](http://thoughtbot.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/bourbon/_bourbon-deprecated-upcoming.scss b/third_party/bourbon/_bourbon-deprecated-upcoming.scss new file mode 100644 index 00000000..f946b3b4 --- /dev/null +++ b/third_party/bourbon/_bourbon-deprecated-upcoming.scss @@ -0,0 +1,8 @@ +//************************************************************************// +// These mixins/functions are deprecated +// They will be removed in the next MAJOR version release +//************************************************************************// +@mixin inline-block { + display: inline-block; + @warn "inline-block mixin is deprecated and will be removed in the next major version release"; +} diff --git a/third_party/bourbon/_bourbon.scss b/third_party/bourbon/_bourbon.scss new file mode 100644 index 00000000..eea6e21e --- /dev/null +++ b/third_party/bourbon/_bourbon.scss @@ -0,0 +1,79 @@ +// Settings +@import "settings/prefixer"; +@import "settings/px-to-em"; +@import "settings/asset-pipeline"; + +// Custom Helpers +@import "helpers/convert-units"; +@import "helpers/gradient-positions-parser"; +@import "helpers/is-num"; +@import "helpers/linear-angle-parser"; +@import "helpers/linear-gradient-parser"; +@import "helpers/linear-positions-parser"; +@import "helpers/linear-side-corner-parser"; +@import "helpers/radial-arg-parser"; +@import "helpers/radial-positions-parser"; +@import "helpers/radial-gradient-parser"; +@import "helpers/render-gradients"; +@import "helpers/shape-size-stripper"; +@import "helpers/str-to-num"; + +// Custom Functions +@import "functions/assign"; +@import "functions/color-lightness"; +@import "functions/flex-grid"; +@import "functions/golden-ratio"; +@import "functions/grid-width"; +@import "functions/modular-scale"; +@import "functions/px-to-em"; +@import "functions/px-to-rem"; +@import "functions/strip-units"; +@import "functions/tint-shade"; +@import "functions/transition-property-name"; +@import "functions/unpack"; + +// CSS3 Mixins +@import "css3/animation"; +@import "css3/appearance"; +@import "css3/backface-visibility"; +@import "css3/background"; +@import "css3/background-image"; +@import "css3/border-image"; +@import "css3/border-radius"; +@import "css3/box-sizing"; +@import "css3/calc"; +@import "css3/columns"; +@import "css3/filter"; +@import "css3/flex-box"; +@import "css3/font-face"; +@import "css3/font-feature-settings"; +@import "css3/hyphens"; +@import "css3/hidpi-media-query"; +@import "css3/image-rendering"; +@import "css3/keyframes"; +@import "css3/linear-gradient"; +@import "css3/perspective"; +@import "css3/radial-gradient"; +@import "css3/transform"; +@import "css3/transition"; +@import "css3/user-select"; +@import "css3/placeholder"; + +// Addons & other mixins +@import "addons/button"; +@import "addons/clearfix"; +@import "addons/directional-values"; +@import "addons/ellipsis"; +@import "addons/font-family"; +@import "addons/hide-text"; +@import "addons/html5-input-types"; +@import "addons/position"; +@import "addons/prefixer"; +@import "addons/retina-image"; +@import "addons/size"; +@import "addons/timing-functions"; +@import "addons/triangle"; +@import "addons/word-wrap"; + +// Soon to be deprecated Mixins +@import "bourbon-deprecated-upcoming"; diff --git a/third_party/bourbon/addons/_button.scss b/third_party/bourbon/addons/_button.scss new file mode 100644 index 00000000..14a89e48 --- /dev/null +++ b/third_party/bourbon/addons/_button.scss @@ -0,0 +1,374 @@ +@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { + + @if type-of($style) == string and type-of($base-color) == color { + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == string and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: #4294f0; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == color { + $base-color: $style; + $style: simple; + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: $style; + $style: simple; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == number { + $padding: $base-color; + $text-size: $style; + $base-color: #4294f0; + $style: simple; + + @if $padding == #4294f0 { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + + +// Selector Style Button +//************************************************************************// +@mixin buttonstyle($type, $b-color, $t-size, $pad) { + // Grayscale button + @if $type == simple and $b-color == grayscale($b-color) { + @include simple($b-color, true, $t-size, $pad); + } + + @if $type == shiny and $b-color == grayscale($b-color) { + @include shiny($b-color, true, $t-size, $pad); + } + + @if $type == pill and $b-color == grayscale($b-color) { + @include pill($b-color, true, $t-size, $pad); + } + + @if $type == flat and $b-color == grayscale($b-color) { + @include flat($b-color, true, $t-size, $pad); + } + + // Colored button + @if $type == simple { + @include simple($b-color, false, $t-size, $pad); + } + + @else if $type == shiny { + @include shiny($b-color, false, $t-size, $pad); + } + + @else if $type == pill { + @include pill($b-color, false, $t-size, $pad); + } + + @else if $type == flat { + @include flat($b-color, false, $t-size, $pad); + } +} + + +// Simple Button +//************************************************************************// +@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); + $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); + $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border; + border-radius: 3px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: bold; + @include linear-gradient ($base-color, $stop-gradient); + padding: $padding; + text-decoration: none; + text-shadow: 0 1px 0 $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); + $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + } + + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); + + @if $grayscale == true { + $border-active: grayscale($border-active); + $inset-shadow-active: grayscale($inset-shadow-active); + } + + border: 1px solid $border-active; + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; + } +} + + +// Shiny Button +//************************************************************************// +@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); + $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); + $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); + $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); + $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); + $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); + $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $border-bottom: grayscale($border-bottom); + $fourth-stop: grayscale($fourth-stop); + $inset-shadow: grayscale($inset-shadow); + $second-stop: grayscale($second-stop); + $text-shadow: grayscale($text-shadow); + $third-stop: grayscale($third-stop); + } + + border: 1px solid $border; + border-bottom: 1px solid $border-bottom; + border-radius: 5px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: bold; + @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); + padding: $padding; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + + &:hover:not(:disabled) { + $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); + $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); + $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); + $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); + + @if $grayscale == true { + $first-stop-hover: grayscale($first-stop-hover); + $second-stop-hover: grayscale($second-stop-hover); + $third-stop-hover: grayscale($third-stop-hover); + $fourth-stop-hover: grayscale($fourth-stop-hover); + } + + cursor: pointer; + @include linear-gradient(top, $first-stop-hover 0%, + $second-stop-hover 50%, + $third-stop-hover 50%, + $fourth-stop-hover 100%); + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); + + @if $grayscale == true { + $inset-shadow-active: grayscale($inset-shadow-active); + } + + box-shadow: inset 0 0 20px 0 $inset-shadow-active; + } +} + + +// Pill Button +//************************************************************************// +@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); + $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); + $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); + $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + border-radius: 16px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: normal; + line-height: 1; + @include linear-gradient ($base-color, $stop-gradient); + padding: $padding; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $lightness: -4.5%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); + $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); + $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + $text-shadow-hover: grayscale($text-shadow-hover); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + text-shadow: 0 -1px 1px $text-shadow-hover; + background-clip: padding-box; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); + $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); + $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); + $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); + $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); + + @if $grayscale == true { + $active-color: grayscale($active-color); + $border-active: grayscale($border-active); + $border-bottom-active: grayscale($border-bottom-active); + $inset-shadow-active: grayscale($inset-shadow-active); + $text-shadow-active: grayscale($text-shadow-active); + } + + background: $active-color; + border: 1px solid $border-active; + border-bottom: 1px solid $border-bottom-active; + box-shadow: inset 0 0 6px 3px $inset-shadow-active; + text-shadow: 0 -1px 1px $text-shadow-active; + } +} + + + +// Flat Button +//************************************************************************// +@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + } + + background-color: $base-color; + border-radius: 3px; + border: none; + color: $color; + display: inline-block; + font-size: inherit; + font-weight: bold; + padding: 7px 18px; + text-decoration: none; + background-clip: padding-box; + + &:hover:not(:disabled){ + $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + } + + background-color: $base-color-hover; + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + + @if $grayscale == true { + $base-color-active: grayscale($base-color-active); + } + + background-color: $base-color-active; + cursor: pointer; + } +} diff --git a/third_party/bourbon/addons/_clearfix.scss b/third_party/bourbon/addons/_clearfix.scss new file mode 100644 index 00000000..783cfbc7 --- /dev/null +++ b/third_party/bourbon/addons/_clearfix.scss @@ -0,0 +1,23 @@ +// Modern micro clearfix provides an easy way to contain floats without adding additional markup. +// +// Example usage: +// +// // Contain all floats within .wrapper +// .wrapper { +// @include clearfix; +// .content, +// .sidebar { +// float : left; +// } +// } + +@mixin clearfix { + &:after { + content:""; + display:table; + clear:both; + } +} + +// Acknowledgements +// Beat *that* clearfix: [Thierry Koblentz](http://www.css-101.org/articles/clearfix/latest-new-clearfix-so-far.php) diff --git a/third_party/bourbon/addons/_directional-values.scss b/third_party/bourbon/addons/_directional-values.scss new file mode 100644 index 00000000..742f1031 --- /dev/null +++ b/third_party/bourbon/addons/_directional-values.scss @@ -0,0 +1,111 @@ +// directional-property mixins are shorthands +// for writing properties like the following +// +// @include margin(null 0 10px); +// ------ +// margin-right: 0; +// margin-bottom: 10px; +// margin-left: 0; +// +// - or - +// +// @include border-style(dotted null); +// ------ +// border-top-style: dotted; +// border-bottom-style: dotted; +// +// ------ +// +// Note: You can also use false instead of null + +@function collapse-directionals($vals) { + $output: null; + + $A: nth( $vals, 1 ); + $B: if( length($vals) < 2, $A, nth($vals, 2)); + $C: if( length($vals) < 3, $A, nth($vals, 3)); + $D: if( length($vals) < 2, $A, nth($vals, if( length($vals) < 4, 2, 4) )); + + @if $A == 0 { $A: 0 } + @if $B == 0 { $B: 0 } + @if $C == 0 { $C: 0 } + @if $D == 0 { $D: 0 } + + @if $A == $B and $A == $C and $A == $D { $output: $A } + @else if $A == $C and $B == $D { $output: $A $B } + @else if $B == $D { $output: $A $B $C } + @else { $output: $A $B $C $D } + + @return $output; +} + +@function contains-falsy($list) { + @each $item in $list { + @if not $item { + @return true; + } + } + + @return false; +} + +@mixin directional-property($pre, $suf, $vals) { + // Property Names + $top: $pre + "-top" + if($suf, "-#{$suf}", ""); + $bottom: $pre + "-bottom" + if($suf, "-#{$suf}", ""); + $left: $pre + "-left" + if($suf, "-#{$suf}", ""); + $right: $pre + "-right" + if($suf, "-#{$suf}", ""); + $all: $pre + if($suf, "-#{$suf}", ""); + + $vals: collapse-directionals($vals); + + @if contains-falsy($vals) { + @if nth($vals, 1) { #{$top}: nth($vals, 1); } + + @if length($vals) == 1 { + @if nth($vals, 1) { #{$right}: nth($vals, 1); } + } @else { + @if nth($vals, 2) { #{$right}: nth($vals, 2); } + } + + // prop: top/bottom right/left + @if length($vals) == 2 { + @if nth($vals, 1) { #{$bottom}: nth($vals, 1); } + @if nth($vals, 2) { #{$left}: nth($vals, 2); } + + // prop: top right/left bottom + } @else if length($vals) == 3 { + @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } + @if nth($vals, 2) { #{$left}: nth($vals, 2); } + + // prop: top right bottom left + } @else if length($vals) == 4 { + @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } + @if nth($vals, 4) { #{$left}: nth($vals, 4); } + } + + // prop: top/right/bottom/left + } @else { + #{$all}: $vals; + } +} + +@mixin margin($vals...) { + @include directional-property(margin, false, $vals...); +} + +@mixin padding($vals...) { + @include directional-property(padding, false, $vals...); +} + +@mixin border-style($vals...) { + @include directional-property(border, style, $vals...); +} + +@mixin border-color($vals...) { + @include directional-property(border, color, $vals...); +} + +@mixin border-width($vals...) { + @include directional-property(border, width, $vals...); +} diff --git a/third_party/bourbon/addons/_ellipsis.scss b/third_party/bourbon/addons/_ellipsis.scss new file mode 100644 index 00000000..a8ea2a4a --- /dev/null +++ b/third_party/bourbon/addons/_ellipsis.scss @@ -0,0 +1,7 @@ +@mixin ellipsis($width: 100%) { + display: inline-block; + max-width: $width; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/third_party/bourbon/addons/_font-family.scss b/third_party/bourbon/addons/_font-family.scss new file mode 100644 index 00000000..8073dc8d --- /dev/null +++ b/third_party/bourbon/addons/_font-family.scss @@ -0,0 +1,5 @@ +$georgia: Georgia, Cambria, "Times New Roman", Times, serif; +$helvetica: /* "Helvetica Neue", */Helvetica, Roboto, Arial, sans-serif; +$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; +$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; +$verdana: Verdana, Geneva, sans-serif; diff --git a/third_party/bourbon/addons/_hide-text.scss b/third_party/bourbon/addons/_hide-text.scss new file mode 100644 index 00000000..fc794381 --- /dev/null +++ b/third_party/bourbon/addons/_hide-text.scss @@ -0,0 +1,10 @@ +@mixin hide-text { + overflow: hidden; + + &:before { + content: ""; + display: block; + width: 0; + height: 100%; + } +} diff --git a/third_party/bourbon/addons/_html5-input-types.scss b/third_party/bourbon/addons/_html5-input-types.scss new file mode 100644 index 00000000..9e9324ae --- /dev/null +++ b/third_party/bourbon/addons/_html5-input-types.scss @@ -0,0 +1,86 @@ +//************************************************************************// +// Generate a variable ($all-text-inputs) with a list of all html5 +// input types that have a text-based input, excluding textarea. +// http://diveintohtml5.org/forms.html +//************************************************************************// +$inputs-list: 'input[type="email"]', + 'input[type="number"]', + 'input[type="password"]', + 'input[type="search"]', + 'input[type="tel"]', + 'input[type="text"]', + 'input[type="url"]', + + // Webkit & Gecko may change the display of these in the future + 'input[type="color"]', + 'input[type="date"]', + 'input[type="datetime"]', + 'input[type="datetime-local"]', + 'input[type="month"]', + 'input[type="time"]', + 'input[type="week"]'; + +// Bare inputs +//************************************************************************// +$all-text-inputs: assign-inputs($inputs-list); + +// Hover Pseudo-class +//************************************************************************// +$all-text-inputs-hover: assign-inputs($inputs-list, hover); + +// Focus Pseudo-class +//************************************************************************// +$all-text-inputs-focus: assign-inputs($inputs-list, focus); + + + +// You must use interpolation on the variable: +// #{$all-text-inputs} +// #{$all-text-inputs-hover} +// #{$all-text-inputs-focus} + +// Example +//************************************************************************// +// #{$all-text-inputs}, textarea { +// border: 1px solid red; +// } + + + +//************************************************************************// +// Generate a variable ($all-button-inputs) with a list of all html5 +// input types that have a button-based input, excluding button. +//************************************************************************// +$inputs-button-list: 'input[type="button"]', + 'input[type="reset"]', + 'input[type="submit"]'; + +// Bare inputs +//************************************************************************// +$all-button-inputs: assign-inputs($inputs-button-list); + +// Hover Pseudo-class +//************************************************************************// +$all-button-inputs-hover: assign-inputs($inputs-button-list, hover); + +// Focus Pseudo-class +//************************************************************************// +$all-button-inputs-focus: assign-inputs($inputs-button-list, focus); + +// Active Pseudo-class +//************************************************************************// +$all-button-inputs-active: assign-inputs($inputs-button-list, active); + + + +// You must use interpolation on the variable: +// #{$all-button-inputs} +// #{$all-button-inputs-hover} +// #{$all-button-inputs-focus} +// #{$all-button-inputs-active} + +// Example +//************************************************************************// +// #{$all-button-inputs}, button { +// border: 1px solid red; +// } diff --git a/third_party/bourbon/addons/_position.scss b/third_party/bourbon/addons/_position.scss new file mode 100644 index 00000000..7de75182 --- /dev/null +++ b/third_party/bourbon/addons/_position.scss @@ -0,0 +1,32 @@ +@mixin position ($position: relative, $coordinates: null null null null) { + + @if type-of($position) == list { + $coordinates: $position; + $position: relative; + } + + $coordinates: unpack($coordinates); + + $top: nth($coordinates, 1); + $right: nth($coordinates, 2); + $bottom: nth($coordinates, 3); + $left: nth($coordinates, 4); + + position: $position; + + @if ($top and $top == auto) or (type-of($top) == number) { + top: $top; + } + + @if ($right and $right == auto) or (type-of($right) == number) { + right: $right; + } + + @if ($bottom and $bottom == auto) or (type-of($bottom) == number) { + bottom: $bottom; + } + + @if ($left and $left == auto) or (type-of($left) == number) { + left: $left; + } +} diff --git a/third_party/bourbon/addons/_prefixer.scss b/third_party/bourbon/addons/_prefixer.scss new file mode 100644 index 00000000..c32f5027 --- /dev/null +++ b/third_party/bourbon/addons/_prefixer.scss @@ -0,0 +1,45 @@ +//************************************************************************// +// Example: @include prefixer(border-radius, $radii, webkit ms spec); +//************************************************************************// +// Variables located in /settings/_prefixer.scss + +@mixin prefixer ($property, $value, $prefixes) { + @each $prefix in $prefixes { + @if $prefix == webkit { + @if $prefix-for-webkit { + -webkit-#{$property}: $value; + } + } + @else if $prefix == moz { + @if $prefix-for-mozilla { + -moz-#{$property}: $value; + } + } + @else if $prefix == ms { + @if $prefix-for-microsoft { + -ms-#{$property}: $value; + } + } + @else if $prefix == o { + @if $prefix-for-opera { + -o-#{$property}: $value; + } + } + @else if $prefix == spec { + @if $prefix-for-spec { + #{$property}: $value; + } + } + @else { + @warn "Unrecognized prefix: #{$prefix}"; + } + } +} + +@mixin disable-prefix-for-all() { + $prefix-for-webkit: false !global; + $prefix-for-mozilla: false !global; + $prefix-for-microsoft: false !global; + $prefix-for-opera: false !global; + $prefix-for-spec: false !global; +} diff --git a/third_party/bourbon/addons/_retina-image.scss b/third_party/bourbon/addons/_retina-image.scss new file mode 100644 index 00000000..3995c197 --- /dev/null +++ b/third_party/bourbon/addons/_retina-image.scss @@ -0,0 +1,31 @@ +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { + @if $asset-pipeline { + background-image: image-url("#{$filename}.#{$extension}"); + } + @else { + background-image: url("#{$filename}.#{$extension}"); + } + + @include hidpi { + @if $asset-pipeline { + @if $retina-filename { + background-image: image-url("#{$retina-filename}.#{$extension}"); + } + @else { + background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}"); + } + } + + @else { + @if $retina-filename { + background-image: url("#{$retina-filename}.#{$extension}"); + } + @else { + background-image: url("#{$filename}#{$retina-suffix}.#{$extension}"); + } + } + + background-size: $background-size; + + } +} diff --git a/third_party/bourbon/addons/_size.scss b/third_party/bourbon/addons/_size.scss new file mode 100644 index 00000000..a8653799 --- /dev/null +++ b/third_party/bourbon/addons/_size.scss @@ -0,0 +1,16 @@ +@mixin size($size) { + $height: nth($size, 1); + $width: $height; + + @if length($size) > 1 { + $height: nth($size, 2); + } + + @if $height == auto or (type-of($height) == number and not unitless($height)) { + height: $height; + } + + @if $width == auto or (type-of($width) == number and not unitless($width)) { + width: $width; + } +} diff --git a/third_party/bourbon/addons/_timing-functions.scss b/third_party/bourbon/addons/_timing-functions.scss new file mode 100644 index 00000000..5ecc6f9d --- /dev/null +++ b/third_party/bourbon/addons/_timing-functions.scss @@ -0,0 +1,32 @@ +// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) +// Timing functions are the same as demo'ed here: http://jqueryui.com/resources/demos/effect/easing.html + +// EASE IN +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); + +// EASE OUT +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); + +// EASE IN OUT +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); diff --git a/third_party/bourbon/addons/_triangle.scss b/third_party/bourbon/addons/_triangle.scss new file mode 100644 index 00000000..573954e4 --- /dev/null +++ b/third_party/bourbon/addons/_triangle.scss @@ -0,0 +1,83 @@ +@mixin triangle ($size, $color, $direction) { + height: 0; + width: 0; + + $width: nth($size, 1); + $height: nth($size, length($size)); + + $foreground-color: nth($color, 1); + $background-color: if(length($color) == 2, nth($color, 2), transparent); + + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { + + $width: $width / 2; + $height: if(length($size) > 1, $height, $height/2); + + @if $direction == up { + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-bottom: $height solid $foreground-color; + + } @else if $direction == right { + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-left: $height solid $foreground-color; + + } @else if $direction == down { + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-top: $height solid $foreground-color; + + } @else if $direction == left { + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-right: $height solid $foreground-color; + } + } + + @else if ($direction == up-right) or ($direction == up-left) { + border-top: $height solid $foreground-color; + + @if $direction == up-right { + border-left: $width solid $background-color; + + } @else if $direction == up-left { + border-right: $width solid $background-color; + } + } + + @else if ($direction == down-right) or ($direction == down-left) { + border-bottom: $height solid $foreground-color; + + @if $direction == down-right { + border-left: $width solid $background-color; + + } @else if $direction == down-left { + border-right: $width solid $background-color; + } + } + + @else if ($direction == inset-up) { + border-width: $height $width; + border-style: solid; + border-color: $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-down) { + border-width: $height $width; + border-style: solid; + border-color: $foreground-color $background-color $background-color; + } + + @else if ($direction == inset-right) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-left) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $foreground-color $background-color $background-color; + } +} diff --git a/third_party/bourbon/addons/_word-wrap.scss b/third_party/bourbon/addons/_word-wrap.scss new file mode 100644 index 00000000..9734a597 --- /dev/null +++ b/third_party/bourbon/addons/_word-wrap.scss @@ -0,0 +1,8 @@ +@mixin word-wrap($wrap: break-word) { + word-wrap: $wrap; + + @if $wrap == break-word { + overflow-wrap: break-word; + word-break: break-all; + } +} diff --git a/third_party/bourbon/css3/_animation.scss b/third_party/bourbon/css3/_animation.scss new file mode 100644 index 00000000..08c3dbf1 --- /dev/null +++ b/third_party/bourbon/css3/_animation.scss @@ -0,0 +1,52 @@ +// http://www.w3.org/TR/css3-animations/#the-animation-name-property- +// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. + +// Official animation shorthand property. +@mixin animation ($animations...) { + @include prefixer(animation, $animations, webkit moz spec); +} + +// Individual Animation Properties +@mixin animation-name ($names...) { + @include prefixer(animation-name, $names, webkit moz spec); +} + + +@mixin animation-duration ($times...) { + @include prefixer(animation-duration, $times, webkit moz spec); +} + + +@mixin animation-timing-function ($motions...) { +// ease | linear | ease-in | ease-out | ease-in-out + @include prefixer(animation-timing-function, $motions, webkit moz spec); +} + + +@mixin animation-iteration-count ($values...) { +// infinite | + @include prefixer(animation-iteration-count, $values, webkit moz spec); +} + + +@mixin animation-direction ($directions...) { +// normal | alternate + @include prefixer(animation-direction, $directions, webkit moz spec); +} + + +@mixin animation-play-state ($states...) { +// running | paused + @include prefixer(animation-play-state, $states, webkit moz spec); +} + + +@mixin animation-delay ($times...) { + @include prefixer(animation-delay, $times, webkit moz spec); +} + + +@mixin animation-fill-mode ($modes...) { +// none | forwards | backwards | both + @include prefixer(animation-fill-mode, $modes, webkit moz spec); +} diff --git a/third_party/bourbon/css3/_appearance.scss b/third_party/bourbon/css3/_appearance.scss new file mode 100644 index 00000000..3eb16e45 --- /dev/null +++ b/third_party/bourbon/css3/_appearance.scss @@ -0,0 +1,3 @@ +@mixin appearance ($value) { + @include prefixer(appearance, $value, webkit moz ms o spec); +} diff --git a/third_party/bourbon/css3/_backface-visibility.scss b/third_party/bourbon/css3/_backface-visibility.scss new file mode 100644 index 00000000..1161fe60 --- /dev/null +++ b/third_party/bourbon/css3/_backface-visibility.scss @@ -0,0 +1,6 @@ +//************************************************************************// +// Backface-visibility mixin +//************************************************************************// +@mixin backface-visibility($visibility) { + @include prefixer(backface-visibility, $visibility, webkit spec); +} diff --git a/third_party/bourbon/css3/_background-image.scss b/third_party/bourbon/css3/_background-image.scss new file mode 100644 index 00000000..6abe88be --- /dev/null +++ b/third_party/bourbon/css3/_background-image.scss @@ -0,0 +1,42 @@ +//************************************************************************// +// Background-image property for adding multiple background images with +// gradients, or for stringing multiple gradients together. +//************************************************************************// + +@mixin background-image($images...) { + $webkit-images: (); + $spec-images: (); + + @each $image in $images { + $webkit-image: (); + $spec-image: (); + + @if (type-of($image) == string) { + $url-str: str-slice($image, 0, 3); + $gradient-type: str-slice($image, 0, 6); + + @if $url-str == "url" { + $webkit-image: $image; + $spec-image: $image; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser($image); + $webkit-image: map-get($gradients, webkit-image); + $spec-image: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser($image); + $webkit-image: map-get($gradients, webkit-image); + $spec-image: map-get($gradients, spec-image); + } + } + + $webkit-images: append($webkit-images, $webkit-image, comma); + $spec-images: append($spec-images, $spec-image, comma); + } + + background-image: $webkit-images; + background-image: $spec-images; +} diff --git a/third_party/bourbon/css3/_background.scss b/third_party/bourbon/css3/_background.scss new file mode 100644 index 00000000..9bce9308 --- /dev/null +++ b/third_party/bourbon/css3/_background.scss @@ -0,0 +1,55 @@ +//************************************************************************// +// Background property for adding multiple backgrounds using shorthand +// notation. +//************************************************************************// + +@mixin background($backgrounds...) { + $webkit-backgrounds: (); + $spec-backgrounds: (); + + @each $background in $backgrounds { + $webkit-background: (); + $spec-background: (); + $background-type: type-of($background); + + @if $background-type == string or list { + $background-str: if($background-type == list, nth($background, 1), $background); + + $url-str: str-slice($background-str, 0, 3); + $gradient-type: str-slice($background-str, 0, 6); + + @if $url-str == "url" { + $webkit-background: $background; + $spec-background: $background; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser("#{$background}"); + $webkit-background: map-get($gradients, webkit-image); + $spec-background: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser("#{$background}"); + $webkit-background: map-get($gradients, webkit-image); + $spec-background: map-get($gradients, spec-image); + } + + @else { + $webkit-background: $background; + $spec-background: $background; + } + } + + @else { + $webkit-background: $background; + $spec-background: $background; + } + + $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma); + $spec-backgrounds: append($spec-backgrounds, $spec-background, comma); + } + + background: $webkit-backgrounds; + background: $spec-backgrounds; +} diff --git a/third_party/bourbon/css3/_border-image.scss b/third_party/bourbon/css3/_border-image.scss new file mode 100644 index 00000000..e338c2dc --- /dev/null +++ b/third_party/bourbon/css3/_border-image.scss @@ -0,0 +1,59 @@ +@mixin border-image($borders...) { + $webkit-borders: (); + $spec-borders: (); + + @each $border in $borders { + $webkit-border: (); + $spec-border: (); + $border-type: type-of($border); + + @if $border-type == string or list { + $border-str: if($border-type == list, nth($border, 1), $border); + + $url-str: str-slice($border-str, 0, 3); + $gradient-type: str-slice($border-str, 0, 6); + + @if $url-str == "url" { + $webkit-border: $border; + $spec-border: $border; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser("#{$border}"); + $webkit-border: map-get($gradients, webkit-image); + $spec-border: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser("#{$border}"); + $webkit-border: map-get($gradients, webkit-image); + $spec-border: map-get($gradients, spec-image); + } + + @else { + $webkit-border: $border; + $spec-border: $border; + } + } + + @else { + $webkit-border: $border; + $spec-border: $border; + } + + $webkit-borders: append($webkit-borders, $webkit-border, comma); + $spec-borders: append($spec-borders, $spec-border, comma); + } + + -webkit-border-image: $webkit-borders; + border-image: $spec-borders; + border-style: solid; +} + +//Examples: +// @include border-image(url("image.png")); +// @include border-image(url("image.png") 20 stretch); +// @include border-image(linear-gradient(45deg, orange, yellow)); +// @include border-image(linear-gradient(45deg, orange, yellow) stretch); +// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); +// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); diff --git a/third_party/bourbon/css3/_border-radius.scss b/third_party/bourbon/css3/_border-radius.scss new file mode 100644 index 00000000..7c171901 --- /dev/null +++ b/third_party/bourbon/css3/_border-radius.scss @@ -0,0 +1,22 @@ +//************************************************************************// +// Shorthand Border-radius mixins +//************************************************************************// +@mixin border-top-radius($radii) { + @include prefixer(border-top-left-radius, $radii, spec); + @include prefixer(border-top-right-radius, $radii, spec); +} + +@mixin border-bottom-radius($radii) { + @include prefixer(border-bottom-left-radius, $radii, spec); + @include prefixer(border-bottom-right-radius, $radii, spec); +} + +@mixin border-left-radius($radii) { + @include prefixer(border-top-left-radius, $radii, spec); + @include prefixer(border-bottom-left-radius, $radii, spec); +} + +@mixin border-right-radius($radii) { + @include prefixer(border-top-right-radius, $radii, spec); + @include prefixer(border-bottom-right-radius, $radii, spec); +} diff --git a/third_party/bourbon/css3/_box-sizing.scss b/third_party/bourbon/css3/_box-sizing.scss new file mode 100644 index 00000000..f07e1d41 --- /dev/null +++ b/third_party/bourbon/css3/_box-sizing.scss @@ -0,0 +1,4 @@ +@mixin box-sizing ($box) { +// content-box | border-box | inherit + @include prefixer(box-sizing, $box, webkit moz spec); +} diff --git a/third_party/bourbon/css3/_calc.scss b/third_party/bourbon/css3/_calc.scss new file mode 100644 index 00000000..94d7e4ce --- /dev/null +++ b/third_party/bourbon/css3/_calc.scss @@ -0,0 +1,4 @@ +@mixin calc($property, $value) { + #{$property}: -webkit-calc(#{$value}); + #{$property}: calc(#{$value}); +} diff --git a/third_party/bourbon/css3/_columns.scss b/third_party/bourbon/css3/_columns.scss new file mode 100644 index 00000000..96f601c1 --- /dev/null +++ b/third_party/bourbon/css3/_columns.scss @@ -0,0 +1,47 @@ +@mixin columns($arg: auto) { +// || + @include prefixer(columns, $arg, webkit moz spec); +} + +@mixin column-count($int: auto) { +// auto || integer + @include prefixer(column-count, $int, webkit moz spec); +} + +@mixin column-gap($length: normal) { +// normal || length + @include prefixer(column-gap, $length, webkit moz spec); +} + +@mixin column-fill($arg: auto) { +// auto || length + @include prefixer(column-fill, $arg, webkit moz spec); +} + +@mixin column-rule($arg) { +// || || + @include prefixer(column-rule, $arg, webkit moz spec); +} + +@mixin column-rule-color($color) { + @include prefixer(column-rule-color, $color, webkit moz spec); +} + +@mixin column-rule-style($style: none) { +// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid + @include prefixer(column-rule-style, $style, webkit moz spec); +} + +@mixin column-rule-width ($width: none) { + @include prefixer(column-rule-width, $width, webkit moz spec); +} + +@mixin column-span($arg: none) { +// none || all + @include prefixer(column-span, $arg, webkit moz spec); +} + +@mixin column-width($length: auto) { +// auto || length + @include prefixer(column-width, $length, webkit moz spec); +} diff --git a/third_party/bourbon/css3/_filter.scss b/third_party/bourbon/css3/_filter.scss new file mode 100644 index 00000000..8560d776 --- /dev/null +++ b/third_party/bourbon/css3/_filter.scss @@ -0,0 +1,5 @@ +@mixin filter($function: none) { + // [ + @include prefixer(perspective, $depth, webkit moz spec); +} + +@mixin perspective-origin($value: 50% 50%) { + @include prefixer(perspective-origin, $value, webkit moz spec); +} diff --git a/third_party/bourbon/css3/_placeholder.scss b/third_party/bourbon/css3/_placeholder.scss new file mode 100644 index 00000000..5682fd09 --- /dev/null +++ b/third_party/bourbon/css3/_placeholder.scss @@ -0,0 +1,8 @@ +@mixin placeholder { + $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input"; + @each $placeholder in $placeholders { + &:#{$placeholder}-placeholder { + @content; + } + } +} diff --git a/third_party/bourbon/css3/_radial-gradient.scss b/third_party/bourbon/css3/_radial-gradient.scss new file mode 100644 index 00000000..7a8c3765 --- /dev/null +++ b/third_party/bourbon/css3/_radial-gradient.scss @@ -0,0 +1,39 @@ +// Requires Sass 3.1+ +@mixin radial-gradient($G1, $G2, + $G3: null, $G4: null, + $G5: null, $G6: null, + $G7: null, $G8: null, + $G9: null, $G10: null, + $pos: null, + $shape-size: null, + $fallback: null) { + + $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); + $G1: nth($data, 1); + $G2: nth($data, 2); + $pos: nth($data, 3); + $shape-size: nth($data, 4); + + $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10; + + // Strip deprecated cover/contain for spec + $shape-size-spec: _shape-size-stripper($shape-size); + + // Set $G1 as the default fallback color + $first-color: nth($full, 1); + $fallback-color: nth($first-color, 1); + + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + // Add Commas and spaces + $shape-size: if($shape-size, '#{$shape-size}, ', null); + $pos: if($pos, '#{$pos}, ', null); + $pos-spec: if($pos, 'at #{$pos}', null); + $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} '); + + background-color: $fallback-color; + background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full})); + background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})"); +} diff --git a/third_party/bourbon/css3/_transform.scss b/third_party/bourbon/css3/_transform.scss new file mode 100644 index 00000000..8cc35963 --- /dev/null +++ b/third_party/bourbon/css3/_transform.scss @@ -0,0 +1,15 @@ +@mixin transform($property: none) { +// none | + @include prefixer(transform, $property, webkit moz ms o spec); +} + +@mixin transform-origin($axes: 50%) { +// x-axis - left | center | right | length | % +// y-axis - top | center | bottom | length | % +// z-axis - length + @include prefixer(transform-origin, $axes, webkit moz ms o spec); +} + +@mixin transform-style ($style: flat) { + @include prefixer(transform-style, $style, webkit moz ms o spec); +} diff --git a/third_party/bourbon/css3/_transition.scss b/third_party/bourbon/css3/_transition.scss new file mode 100644 index 00000000..5ad4c0ae --- /dev/null +++ b/third_party/bourbon/css3/_transition.scss @@ -0,0 +1,77 @@ +// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. +// Example: @include transition (all 2s ease-in-out); +// @include transition (opacity 1s ease-in 2s, width 2s ease-out); +// @include transition-property (transform, opacity); + +@mixin transition ($properties...) { + // Fix for vendor-prefix transform property + $needs-prefixes: false; + $webkit: (); + $moz: (); + $spec: (); + + // Create lists for vendor-prefixed transform + @each $list in $properties { + @if nth($list, 1) == "transform" { + $needs-prefixes: true; + $list1: -webkit-transform; + $list2: -moz-transform; + $list3: (); + + @each $var in $list { + $list3: join($list3, $var); + + @if $var != "transform" { + $list1: join($list1, $var); + $list2: join($list2, $var); + } + } + + $webkit: append($webkit, $list1); + $moz: append($moz, $list2); + $spec: append($spec, $list3); + } + + // Create lists for non-prefixed transition properties + @else { + $webkit: append($webkit, $list, comma); + $moz: append($moz, $list, comma); + $spec: append($spec, $list, comma); + } + } + + @if $needs-prefixes { + -webkit-transition: $webkit; + -moz-transition: $moz; + transition: $spec; + } + @else { + @if length($properties) >= 1 { + @include prefixer(transition, $properties, webkit moz spec); + } + + @else { + $properties: all 0.15s ease-out 0s; + @include prefixer(transition, $properties, webkit moz spec); + } + } +} + +@mixin transition-property ($properties...) { + -webkit-transition-property: transition-property-names($properties, 'webkit'); + -moz-transition-property: transition-property-names($properties, 'moz'); + transition-property: transition-property-names($properties, false); +} + +@mixin transition-duration ($times...) { + @include prefixer(transition-duration, $times, webkit moz spec); +} + +@mixin transition-timing-function ($motions...) { +// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() + @include prefixer(transition-timing-function, $motions, webkit moz spec); +} + +@mixin transition-delay ($times...) { + @include prefixer(transition-delay, $times, webkit moz spec); +} diff --git a/third_party/bourbon/css3/_user-select.scss b/third_party/bourbon/css3/_user-select.scss new file mode 100644 index 00000000..1380aa8b --- /dev/null +++ b/third_party/bourbon/css3/_user-select.scss @@ -0,0 +1,3 @@ +@mixin user-select($arg: none) { + @include prefixer(user-select, $arg, webkit moz ms spec); +} diff --git a/third_party/bourbon/functions/_assign.scss b/third_party/bourbon/functions/_assign.scss new file mode 100644 index 00000000..9a1db93e --- /dev/null +++ b/third_party/bourbon/functions/_assign.scss @@ -0,0 +1,11 @@ +@function assign-inputs($inputs, $pseudo: null) { + $list : (); + + @each $input in $inputs { + $input: unquote($input); + $input: if($pseudo, $input + ":" + $pseudo, $input); + $list: append($list, $input, comma); + } + + @return $list; +} \ No newline at end of file diff --git a/third_party/bourbon/functions/_color-lightness.scss b/third_party/bourbon/functions/_color-lightness.scss new file mode 100644 index 00000000..8c6df4e2 --- /dev/null +++ b/third_party/bourbon/functions/_color-lightness.scss @@ -0,0 +1,13 @@ +// Programatically determines whether a color is light or dark +// Returns a boolean +// More details here http://robots.thoughtbot.com/closer-look-color-lightness + +@function is-light($hex-color) { + $-local-red: red(rgba($hex-color, 1.0)); + $-local-green: green(rgba($hex-color, 1.0)); + $-local-blue: blue(rgba($hex-color, 1.0)); + + $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; + + @return $-local-lightness > .6; +} diff --git a/third_party/bourbon/functions/_flex-grid.scss b/third_party/bourbon/functions/_flex-grid.scss new file mode 100644 index 00000000..3bbd8665 --- /dev/null +++ b/third_party/bourbon/functions/_flex-grid.scss @@ -0,0 +1,39 @@ +// Flexible grid +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); +} + +// Flexible gutter +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); +} + +// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function. +// This function takes the fluid grid equation (target / context = result) and uses columns to help define each. +// +// The calculation presumes that your column structure will be missing the last gutter: +// +// -- column -- gutter -- column -- gutter -- column +// +// $fg-column: 60px; // Column Width +// $fg-gutter: 25px; // Gutter Width +// $fg-max-columns: 12; // Total Columns For Main Container +// +// div { +// width: flex-grid(4); // returns (315px / 995px) = 31.65829%; +// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%; +// +// p { +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// float: left; +// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%; +// } +// +// blockquote { +// float: left; +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// } +// } \ No newline at end of file diff --git a/third_party/bourbon/functions/_golden-ratio.scss b/third_party/bourbon/functions/_golden-ratio.scss new file mode 100644 index 00000000..463d14a0 --- /dev/null +++ b/third_party/bourbon/functions/_golden-ratio.scss @@ -0,0 +1,3 @@ +@function golden-ratio($value, $increment) { + @return modular-scale($value, $increment, $golden) +} diff --git a/third_party/bourbon/functions/_grid-width.scss b/third_party/bourbon/functions/_grid-width.scss new file mode 100644 index 00000000..8e63d83d --- /dev/null +++ b/third_party/bourbon/functions/_grid-width.scss @@ -0,0 +1,13 @@ +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; +} + +// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function. +// +// $gw-column: 100px; // Column Width +// $gw-gutter: 40px; // Gutter Width +// +// div { +// width: grid-width(4); // returns 520px; +// margin-left: $gw-gutter; // returns 40px; +// } diff --git a/third_party/bourbon/functions/_modular-scale.scss b/third_party/bourbon/functions/_modular-scale.scss new file mode 100644 index 00000000..afc59eb9 --- /dev/null +++ b/third_party/bourbon/functions/_modular-scale.scss @@ -0,0 +1,66 @@ +// Scaling Variables +$golden: 1.618; +$minor-second: 1.067; +$major-second: 1.125; +$minor-third: 1.2; +$major-third: 1.25; +$perfect-fourth: 1.333; +$augmented-fourth: 1.414; +$perfect-fifth: 1.5; +$minor-sixth: 1.6; +$major-sixth: 1.667; +$minor-seventh: 1.778; +$major-seventh: 1.875; +$octave: 2; +$major-tenth: 2.5; +$major-eleventh: 2.667; +$major-twelfth: 3; +$double-octave: 4; + +@function modular-scale($value, $increment, $ratio) { + $v1: nth($value, 1); + $v2: nth($value, length($value)); + $value: $v1; + + // scale $v2 to just above $v1 + @while $v2 > $v1 { + $v2: ($v2 / $ratio); // will be off-by-1 + } + @while $v2 < $v1 { + $v2: ($v2 * $ratio); // will fix off-by-1 + } + + // check AFTER scaling $v2 to prevent double-counting corner-case + $double-stranded: $v2 > $v1; + + @if $increment > 0 { + @for $i from 1 through $increment { + @if $double-stranded and ($v1 * $ratio) > $v2 { + $value: $v2; + $v2: ($v2 * $ratio); + } @else { + $v1: ($v1 * $ratio); + $value: $v1; + } + } + } + + @if $increment < 0 { + // adjust $v2 to just below $v1 + @if $double-stranded { + $v2: ($v2 / $ratio); + } + + @for $i from $increment through -1 { + @if $double-stranded and ($v1 / $ratio) < $v2 { + $value: $v2; + $v2: ($v2 / $ratio); + } @else { + $v1: ($v1 / $ratio); + $value: $v1; + } + } + } + + @return $value; +} diff --git a/third_party/bourbon/functions/_px-to-em.scss b/third_party/bourbon/functions/_px-to-em.scss new file mode 100644 index 00000000..4832245e --- /dev/null +++ b/third_party/bourbon/functions/_px-to-em.scss @@ -0,0 +1,13 @@ +// Convert pixels to ems +// eg. for a relational value of 12px write em(12) when the parent is 16px +// if the parent is another value say 24px write em(12, 24) + +@function em($pxval, $base: $em-base) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1em; +} diff --git a/third_party/bourbon/functions/_px-to-rem.scss b/third_party/bourbon/functions/_px-to-rem.scss new file mode 100644 index 00000000..96b244e4 --- /dev/null +++ b/third_party/bourbon/functions/_px-to-rem.scss @@ -0,0 +1,15 @@ +// Convert pixels to rems +// eg. for a relational value of 12px write rem(12) +// Assumes $em-base is the font-size of + +@function rem($pxval) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + + $base: $em-base; + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1rem; +} diff --git a/third_party/bourbon/functions/_strip-units.scss b/third_party/bourbon/functions/_strip-units.scss new file mode 100644 index 00000000..6afc6e60 --- /dev/null +++ b/third_party/bourbon/functions/_strip-units.scss @@ -0,0 +1,5 @@ +// Srtips the units from a value. e.g. 12px -> 12 + +@function strip-units($val) { + @return ($val / ($val * 0 + 1)); +} diff --git a/third_party/bourbon/functions/_tint-shade.scss b/third_party/bourbon/functions/_tint-shade.scss new file mode 100644 index 00000000..f7172004 --- /dev/null +++ b/third_party/bourbon/functions/_tint-shade.scss @@ -0,0 +1,9 @@ +// Add percentage of white to a color +@function tint($color, $percent){ + @return mix(white, $color, $percent); +} + +// Add percentage of black to a color +@function shade($color, $percent){ + @return mix(black, $color, $percent); +} diff --git a/third_party/bourbon/functions/_transition-property-name.scss b/third_party/bourbon/functions/_transition-property-name.scss new file mode 100644 index 00000000..54cd4228 --- /dev/null +++ b/third_party/bourbon/functions/_transition-property-name.scss @@ -0,0 +1,22 @@ +// Return vendor-prefixed property names if appropriate +// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background +//************************************************************************// +@function transition-property-names($props, $vendor: false) { + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } + + @return $new-props; +} + +@function transition-property-name($prop, $vendor: false) { + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote('-'+$vendor+'-'+$prop); + } + @else { + @return $prop; + } +} \ No newline at end of file diff --git a/third_party/bourbon/functions/_unpack.scss b/third_party/bourbon/functions/_unpack.scss new file mode 100644 index 00000000..37759636 --- /dev/null +++ b/third_party/bourbon/functions/_unpack.scss @@ -0,0 +1,17 @@ +// Convert shorthand to the 4-value syntax + +@function unpack($shorthand) { + @if length($shorthand) == 1 { + @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1); + } + @else if length($shorthand) == 2 { + @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2); + } + @else if length($shorthand) == 3 { + @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2); + } + @else { + @return $shorthand; + } +} + diff --git a/third_party/bourbon/helpers/_convert-units.scss b/third_party/bourbon/helpers/_convert-units.scss new file mode 100644 index 00000000..3443db39 --- /dev/null +++ b/third_party/bourbon/helpers/_convert-units.scss @@ -0,0 +1,15 @@ +//************************************************************************// +// Helper function for str-to-num fn. +// Source: http://sassmeister.com/gist/9647408 +//************************************************************************// +@function _convert-units($number, $unit) { + $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax', 'deg', 'rad', 'grad', 'turn'; + $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax, 1deg, 1rad, 1grad, 1turn; + $index: index($strings, $unit); + + @if not $index { + @warn "Unknown unit `#{$unit}`."; + @return false; + } + @return $number * nth($units, $index); +} diff --git a/third_party/bourbon/helpers/_gradient-positions-parser.scss b/third_party/bourbon/helpers/_gradient-positions-parser.scss new file mode 100644 index 00000000..07d30b6c --- /dev/null +++ b/third_party/bourbon/helpers/_gradient-positions-parser.scss @@ -0,0 +1,13 @@ +@function _gradient-positions-parser($gradient-type, $gradient-positions) { + @if $gradient-positions + and ($gradient-type == linear) + and (type-of($gradient-positions) != color) { + $gradient-positions: _linear-positions-parser($gradient-positions); + } + @else if $gradient-positions + and ($gradient-type == radial) + and (type-of($gradient-positions) != color) { + $gradient-positions: _radial-positions-parser($gradient-positions); + } + @return $gradient-positions; +} diff --git a/third_party/bourbon/helpers/_is-num.scss b/third_party/bourbon/helpers/_is-num.scss new file mode 100644 index 00000000..71459e14 --- /dev/null +++ b/third_party/bourbon/helpers/_is-num.scss @@ -0,0 +1,8 @@ +//************************************************************************// +// Helper for linear-gradient-parser +//************************************************************************// +@function _is-num($char) { + $values: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 0 1 2 3 4 5 6 7 8 9; + $index: index($values, $char); + @return if($index, true, false); +} diff --git a/third_party/bourbon/helpers/_linear-angle-parser.scss b/third_party/bourbon/helpers/_linear-angle-parser.scss new file mode 100644 index 00000000..e0401ed8 --- /dev/null +++ b/third_party/bourbon/helpers/_linear-angle-parser.scss @@ -0,0 +1,25 @@ +// Private function for linear-gradient-parser +@function _linear-angle-parser($image, $first-val, $prefix, $suffix) { + $offset: null; + $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val)); + $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val)); + + @if ($unit-long == "grad") or + ($unit-long == "turn") { + $offset: if($unit-long == "grad", -100grad * 3, -0.75turn); + } + + @else if ($unit-short == "deg") or + ($unit-short == "rad") { + $offset: if($unit-short == "deg", -90 * 3, 1.6rad); + } + + @if $offset { + $num: _str-to-num($first-val); + + @return ( + webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix, + spec-image: $image + ); + } +} diff --git a/third_party/bourbon/helpers/_linear-gradient-parser.scss b/third_party/bourbon/helpers/_linear-gradient-parser.scss new file mode 100644 index 00000000..12bcdcda --- /dev/null +++ b/third_party/bourbon/helpers/_linear-gradient-parser.scss @@ -0,0 +1,41 @@ +@function _linear-gradient-parser($image) { + $image: unquote($image); + $gradients: (); + $start: str-index($image, "("); + $end: str-index($image, ","); + $first-val: str-slice($image, $start + 1, $end - 1); + + $prefix: str-slice($image, 0, $start); + $suffix: str-slice($image, $end, str-length($image)); + + $has-multiple-vals: str-index($first-val, " "); + $has-single-position: unquote(_position-flipper($first-val) + ""); + $has-angle: _is-num(str-slice($first-val, 0, 0)); + + @if $has-multiple-vals { + $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals); + } + + @else if $has-single-position != "" { + $pos: unquote($has-single-position + ""); + + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $prefix + "to " + $pos + $suffix + ); + } + + @else if $has-angle { + // Rotate degree for webkit + $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix); + } + + @else { + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $image + ); + } + + @return $gradients; +} diff --git a/third_party/bourbon/helpers/_linear-positions-parser.scss b/third_party/bourbon/helpers/_linear-positions-parser.scss new file mode 100644 index 00000000..d26383ed --- /dev/null +++ b/third_party/bourbon/helpers/_linear-positions-parser.scss @@ -0,0 +1,61 @@ +@function _linear-positions-parser($pos) { + $type: type-of(nth($pos, 1)); + $spec: null; + $degree: null; + $side: null; + $corner: null; + $length: length($pos); + // Parse Side and corner positions + @if ($length > 1) { + @if nth($pos, 1) == "to" { // Newer syntax + $side: nth($pos, 2); + + @if $length == 2 { // eg. to top + // Swap for backwards compatability + $degree: _position-flipper(nth($pos, 2)); + } + @else if $length == 3 { // eg. to top left + $corner: nth($pos, 3); + } + } + @else if $length == 2 { // Older syntax ("top left") + $side: _position-flipper(nth($pos, 1)); + $corner: _position-flipper(nth($pos, 2)); + } + + @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + $spec: to $side $corner; + } + @else if $length == 1 { + // Swap for backwards compatability + @if $type == string { + $degree: $pos; + $spec: to _position-flipper($pos); + } + @else { + $degree: -270 - $pos; //rotate the gradient opposite from spec + $spec: $pos; + } + } + $degree: unquote($degree + ","); + $spec: unquote($spec + ","); + @return $degree $spec; +} + +@function _position-flipper($pos) { + @return if($pos == left, right, null) + if($pos == right, left, null) + if($pos == top, bottom, null) + if($pos == bottom, top, null); +} diff --git a/third_party/bourbon/helpers/_linear-side-corner-parser.scss b/third_party/bourbon/helpers/_linear-side-corner-parser.scss new file mode 100644 index 00000000..86ad88fb --- /dev/null +++ b/third_party/bourbon/helpers/_linear-side-corner-parser.scss @@ -0,0 +1,31 @@ +// Private function for linear-gradient-parser +@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) { + $val-1: str-slice($first-val, 0, $has-multiple-vals - 1 ); + $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val)); + $val-3: null; + $has-val-3: str-index($val-2, " "); + + @if $has-val-3 { + $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2)); + $val-2: str-slice($val-2, 0, $has-val-3 - 1); + } + + $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3); + $pos: unquote($pos + ""); + + // Use old spec for webkit + @if $val-1 == "to" { + @return ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ); + } + + // Bring the code up to spec + @else { + @return ( + webkit-image: -webkit- + $image, + spec-image: $prefix + "to " + $pos + $suffix + ); + } +} diff --git a/third_party/bourbon/helpers/_radial-arg-parser.scss b/third_party/bourbon/helpers/_radial-arg-parser.scss new file mode 100644 index 00000000..a3a3704a --- /dev/null +++ b/third_party/bourbon/helpers/_radial-arg-parser.scss @@ -0,0 +1,69 @@ +@function _radial-arg-parser($G1, $G2, $pos, $shape-size) { + @each $value in $G1, $G2 { + $first-val: nth($value, 1); + $pos-type: type-of($first-val); + $spec-at-index: null; + + // Determine if spec was passed to mixin + @if type-of($value) == list { + $spec-at-index: if(index($value, at), index($value, at), false); + } + @if $spec-at-index { + @if $spec-at-index > 1 { + @for $i from 1 through ($spec-at-index - 1) { + $shape-size: $shape-size nth($value, $i); + } + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + @else if $spec-at-index == 1 { + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + $G1: null; + } + + // If not spec calculate correct values + @else { + @if ($pos-type != color) or ($first-val != "transparent") { + @if ($pos-type == number) + or ($first-val == "center") + or ($first-val == "top") + or ($first-val == "right") + or ($first-val == "bottom") + or ($first-val == "left") { + + $pos: $value; + + @if $pos == $G1 { + $G1: null; + } + } + + @else if + ($first-val == "ellipse") + or ($first-val == "circle") + or ($first-val == "closest-side") + or ($first-val == "closest-corner") + or ($first-val == "farthest-side") + or ($first-val == "farthest-corner") + or ($first-val == "contain") + or ($first-val == "cover") { + + $shape-size: $value; + + @if $value == $G1 { + $G1: null; + } + + @else if $value == $G2 { + $G2: null; + } + } + } + } + } + @return $G1, $G2, $pos, $shape-size; +} diff --git a/third_party/bourbon/helpers/_radial-gradient-parser.scss b/third_party/bourbon/helpers/_radial-gradient-parser.scss new file mode 100644 index 00000000..6dde50f0 --- /dev/null +++ b/third_party/bourbon/helpers/_radial-gradient-parser.scss @@ -0,0 +1,50 @@ +@function _radial-gradient-parser($image) { + $image: unquote($image); + $gradients: (); + $start: str-index($image, "("); + $end: str-index($image, ","); + $first-val: str-slice($image, $start + 1, $end - 1); + + $prefix: str-slice($image, 0, $start); + $suffix: str-slice($image, $end, str-length($image)); + + $is-spec-syntax: str-index($first-val, "at"); + + @if $is-spec-syntax and $is-spec-syntax > 1 { + $keyword: str-slice($first-val, 1, $is-spec-syntax - 2); + $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); + $pos: append($pos, $keyword, comma); + + $gradients: ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ) + } + + @else if $is-spec-syntax == 1 { + $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); + + $gradients: ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ) + } + + @else if str-index($image, "cover") or str-index($image, "contain") { + @warn "Radial-gradient needs to be updated to conform to latest spec."; + + $gradients: ( + webkit-image: null, + spec-image: $image + ) + } + + @else { + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $image + ) + } + + @return $gradients; +} diff --git a/third_party/bourbon/helpers/_radial-positions-parser.scss b/third_party/bourbon/helpers/_radial-positions-parser.scss new file mode 100644 index 00000000..6a5b4777 --- /dev/null +++ b/third_party/bourbon/helpers/_radial-positions-parser.scss @@ -0,0 +1,18 @@ +@function _radial-positions-parser($gradient-pos) { + $shape-size: nth($gradient-pos, 1); + $pos: nth($gradient-pos, 2); + $shape-size-spec: _shape-size-stripper($shape-size); + + $pre-spec: unquote(if($pos, "#{$pos}, ", null)) + unquote(if($shape-size, "#{$shape-size},", null)); + $pos-spec: if($pos, "at #{$pos}", null); + + $spec: "#{$shape-size-spec} #{$pos-spec}"; + + // Add comma + @if ($spec != ' ') { + $spec: "#{$spec}," + } + + @return $pre-spec $spec; +} diff --git a/third_party/bourbon/helpers/_render-gradients.scss b/third_party/bourbon/helpers/_render-gradients.scss new file mode 100644 index 00000000..57656768 --- /dev/null +++ b/third_party/bourbon/helpers/_render-gradients.scss @@ -0,0 +1,26 @@ +// User for linear and radial gradients within background-image or border-image properties + +@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { + $pre-spec: null; + $spec: null; + $vendor-gradients: null; + @if $gradient-type == linear { + @if $gradient-positions { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + } + @else if $gradient-type == radial { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + + @if $vendor { + $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); + } + @else if $vendor == false { + $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; + $vendor-gradients: unquote($vendor-gradients); + } + @return $vendor-gradients; +} diff --git a/third_party/bourbon/helpers/_shape-size-stripper.scss b/third_party/bourbon/helpers/_shape-size-stripper.scss new file mode 100644 index 00000000..ee5eda42 --- /dev/null +++ b/third_party/bourbon/helpers/_shape-size-stripper.scss @@ -0,0 +1,10 @@ +@function _shape-size-stripper($shape-size) { + $shape-size-spec: null; + @each $value in $shape-size { + @if ($value == "cover") or ($value == "contain") { + $value: null; + } + $shape-size-spec: "#{$shape-size-spec} #{$value}"; + } + @return $shape-size-spec; +} diff --git a/third_party/bourbon/helpers/_str-to-num.scss b/third_party/bourbon/helpers/_str-to-num.scss new file mode 100644 index 00000000..b3d61682 --- /dev/null +++ b/third_party/bourbon/helpers/_str-to-num.scss @@ -0,0 +1,50 @@ +//************************************************************************// +// Helper function for linear/radial-gradient-parsers. +// Source: http://sassmeister.com/gist/9647408 +//************************************************************************// +@function _str-to-num($string) { + // Matrices + $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; + $numbers: 0 1 2 3 4 5 6 7 8 9; + + // Result + $result: 0; + $divider: 0; + $minus: false; + + // Looping through all characters + @for $i from 1 through str-length($string) { + $character: str-slice($string, $i, $i); + $index: index($strings, $character); + + @if $character == '-' { + $minus: true; + } + + @else if $character == '.' { + $divider: 1; + } + + @else { + @if not $index { + $result: if($minus, $result * -1, $result); + @return _convert-units($result, str-slice($string, $i)); + } + + $number: nth($numbers, $index); + + @if $divider == 0 { + $result: $result * 10; + } + + @else { + // Move the decimal dot to the left + $divider: $divider * 10; + $number: $number / $divider; + } + + $result: $result + $number; + } + } + @return if($minus, $result * -1, $result); +} diff --git a/third_party/bourbon/settings/_asset-pipeline.scss b/third_party/bourbon/settings/_asset-pipeline.scss new file mode 100644 index 00000000..d481a6af --- /dev/null +++ b/third_party/bourbon/settings/_asset-pipeline.scss @@ -0,0 +1 @@ +$asset-pipeline: false !default; diff --git a/third_party/bourbon/settings/_prefixer.scss b/third_party/bourbon/settings/_prefixer.scss new file mode 100644 index 00000000..ecab49fb --- /dev/null +++ b/third_party/bourbon/settings/_prefixer.scss @@ -0,0 +1,6 @@ +// Variable settings for /addons/prefixer.scss +$prefix-for-webkit: true !default; +$prefix-for-mozilla: true !default; +$prefix-for-microsoft: true !default; +$prefix-for-opera: true !default; +$prefix-for-spec: true !default; // required for keyframe mixin diff --git a/third_party/bourbon/settings/_px-to-em.scss b/third_party/bourbon/settings/_px-to-em.scss new file mode 100644 index 00000000..f2f9a3e8 --- /dev/null +++ b/third_party/bourbon/settings/_px-to-em.scss @@ -0,0 +1 @@ +$em-base: 16px !default; diff --git a/third_party/cookiesjs/LICENSE b/third_party/cookiesjs/LICENSE new file mode 100644 index 00000000..68a49daa --- /dev/null +++ b/third_party/cookiesjs/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/third_party/cookiesjs/cookies.js b/third_party/cookiesjs/cookies.js new file mode 100644 index 00000000..84e2c15f --- /dev/null +++ b/third_party/cookiesjs/cookies.js @@ -0,0 +1,159 @@ +/* + * Cookies.js - 1.2.1 + * https://github.com/ScottHamper/Cookies + */ +(function (global, undefined) { + 'use strict'; + + var factory = function (window) { + if (typeof window.document !== 'object') { + throw new Error('Cookies.js requires a `window` with a `document` object'); + } + + var Cookies = function (key, value, options) { + return arguments.length === 1 ? + Cookies.get(key) : Cookies.set(key, value, options); + }; + + // Allows for setter injection in unit tests + Cookies._document = window.document; + + // Used to ensure cookie keys do not collide with + // built-in `Object` properties + Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :) + + Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC'); + + Cookies.defaults = { + path: '/', + secure: false + }; + + Cookies.get = function (key) { + if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) { + Cookies._renewCache(); + } + + return Cookies._cache[Cookies._cacheKeyPrefix + key]; + }; + + Cookies.set = function (key, value, options) { + options = Cookies._getExtendedOptions(options); + options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires); + + Cookies._document.cookie = Cookies._generateCookieString(key, value, options); + + return Cookies; + }; + + Cookies.expire = function (key, options) { + return Cookies.set(key, undefined, options); + }; + + Cookies._getExtendedOptions = function (options) { + return { + path: options && options.path || Cookies.defaults.path, + domain: options && options.domain || Cookies.defaults.domain, + expires: options && options.expires || Cookies.defaults.expires, + secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure + }; + }; + + Cookies._isValidDate = function (date) { + return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime()); + }; + + Cookies._getExpiresDate = function (expires, now) { + now = now || new Date(); + + if (typeof expires === 'number') { + expires = expires === Infinity ? + Cookies._maxExpireDate : new Date(now.getTime() + expires * 1000); + } else if (typeof expires === 'string') { + expires = new Date(expires); + } + + if (expires && !Cookies._isValidDate(expires)) { + throw new Error('`expires` parameter cannot be converted to a valid Date instance'); + } + + return expires; + }; + + Cookies._generateCookieString = function (key, value, options) { + key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent); + key = key.replace(/\(/g, '%28').replace(/\)/g, '%29'); + value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent); + options = options || {}; + + var cookieString = key + '=' + value; + cookieString += options.path ? ';path=' + options.path : ''; + cookieString += options.domain ? ';domain=' + options.domain : ''; + cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : ''; + cookieString += options.secure ? ';secure' : ''; + + return cookieString; + }; + + Cookies._getCacheFromString = function (documentCookie) { + var cookieCache = {}; + var cookiesArray = documentCookie ? documentCookie.split('; ') : []; + + for (var i = 0; i < cookiesArray.length; i++) { + var cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]); + + if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) { + cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value; + } + } + + return cookieCache; + }; + + Cookies._getKeyValuePairFromCookieString = function (cookieString) { + // "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')` + var separatorIndex = cookieString.indexOf('='); + + // IE omits the "=" when the cookie value is an empty string + separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex; + + return { + key: decodeURIComponent(cookieString.substr(0, separatorIndex)), + value: decodeURIComponent(cookieString.substr(separatorIndex + 1)) + }; + }; + + Cookies._renewCache = function () { + Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie); + Cookies._cachedDocumentCookie = Cookies._document.cookie; + }; + + Cookies._areEnabled = function () { + var testKey = 'cookies.js'; + var areEnabled = Cookies.set(testKey, 1).get(testKey) === '1'; + Cookies.expire(testKey); + return areEnabled; + }; + + Cookies.enabled = Cookies._areEnabled(); + + return Cookies; + }; + + var cookiesExport = typeof global.document === 'object' ? factory(global) : factory; + + // AMD support + if (typeof define === 'function' && define.amd) { + define(function () { return cookiesExport; }); + // CommonJS/Node.js support + } else if (typeof exports === 'object') { + // Support Node.js specific `module.exports` (which can be a function) + if (typeof module === 'object' && typeof module.exports === 'object') { + exports = module.exports = cookiesExport; + } + // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) + exports.Cookies = cookiesExport; + } else { + global.Cookies = cookiesExport; + } +})(typeof window === 'undefined' ? this : window); \ No newline at end of file diff --git a/third_party/directory_locations.json b/third_party/directory_locations.json index ab75f08e..7e356775 100644 --- a/third_party/directory_locations.json +++ b/third_party/directory_locations.json @@ -164,5 +164,141 @@ { "source": "tzdata/timezones_olson.txt", "destination": "resources/normal/base/tzdata/" + }, + { + "source": "cookiesjs/cookies.js", + "destination": "devsite/source/assets/js/libs/cookies.js" + }, + { + "source": "cookiesjs/cookies.js", + "destination": "devsite/source/_js/libs/cookies.min.js" + }, + { + "source": "bourbon/", + "destination": "devsite/source/_sass/" + }, + { + "source": "font-awesome/sass/font-awesome/", + "destination": "devsite/source/_sass/" + }, + { + "source": "font-awesome/fonts/fontawesome-webfont.eot", + "destination": "devsite/source/assets/fonts/fontawesome-webfont.eot" + }, + { + "source": "font-awesome/fonts/fontawesome-webfont.svg", + "destination": "devsite/source/assets/fonts/fontawesome-webfont.svg" + }, + { + "source": "font-awesome/fonts/fontawesome-webfont.ttf", + "destination": "devsite/source/assets/fonts/fontawesome-webfont.ttf" + }, + { + "source": "font-awesome/fonts/fontawesome-webfont.woff", + "destination": "devsite/source/assets/fonts/fontawesome-webfont.woff" + }, + { + "source": "slick/slick.scss", + "destination": "devsite/source/_sass/libs/slick.scss" + }, + { + "source": "slick/slick.eot", + "destination": "devsite/source/assets/fonts/slick.eot" + }, + { + "source": "slick/slick.svg", + "destination": "devsite/source/assets/fonts/slick.svg" + }, + { + "source": "slick/slick.ttf", + "destination": "devsite/source/assets/fonts/slick.ttf" + }, + { + "source": "slick/slick.woff", + "destination": "devsite/source/assets/fonts/slick.woff" + }, + { + "source": "slick/slick.js", + "destination": "devsite/source/assets/js/libs/slick.js" + }, + { + "source": "jquery-mmenu/sass/mmenu", + "destination": "devsite/source/_sass/" + }, + { + "source": "jquery-mmenu/js/jquery.mmenu.footer.js", + "destination": "devsite/source/assets/js/libs/jquery.mmenu.footer.js" + }, + { + "source": "jquery-mmenu/js/jquery.mmenu.offcanvas.js", + "destination": "devsite/source/assets/js/libs/jquery.mmenu.offcanvas.js" + }, + { + "source": "jquery-mmenu/js/jquery.mmenu.oncanvas.js", + "destination": "devsite/source/assets/js/libs/jquery.mmenu.oncanvas.js" + }, + { + "source": "responsive/sass/responsive/", + "destination": "devsite/source/_sass/" + }, + { + "source": "responsive/js/responsive.js", + "destination": "devsite/source/js/libs/responsive.js" + }, + { + "source": "handlebars/handlebars.runtime-v2.0.0.js", + "destination": "devsite/source/js/libs/handlebars.runtime-v2.0.0.js" + }, + { + "source": "algolia/algoliasearch.js", + "destination": "devsite/source/js/libs/algoliasearch.js" + }, + { + "source": "oliver-caldwell/heir.js", + "destination": "devsite/source/js/libs/heir.js" + }, + { + "source": "oliver-caldwell/EventEmitter.js", + "destination": "devsite/source/js/libs/EventEmitter.js" + }, + { + "source": "jquery/jquery-1.11.2.js", + "destination": "devsite/source/js/libs/jquery-1.11.2.js" + }, + { + "source": "jquery-fitvids/jquery.fitvids.js", + "destination": "devsite/source/js/libs/jquery.fitvids.js" + }, + { + "source": "jquery-scrollto/jquery.scrollTo.js", + "destination": "devsite/source/js/libs/jquery.scrollTo.js" + }, + { + "source": "jquery-vide/jquery.vide.js", + "destination": "devsite/source/js/libs/jquery.vide.js" + }, + { + "source": "moment/moment.js", + "destination": "devsite/source/js/libs/moment.js" + }, + { + "source": "query-string/query-string.js", + "destination": "devsite/source/js/libs/query-string.js" + }, + { + "source": "hubspot/select.js", + "destination": "devsite/source/js/libs/select.js" + }, + { + "source": "hubspot/select-theme-default.css", + "destination": "devsite/source/css/libs/select-theme-default.css" + }, + { + "source": "hubspot/tether.js", + "destination": "devsite/source/js/libs/tether.js" + }, + { + "source": "hubspot/utils.js", + "destination": "devsite/source/js/libs/utils.js" } ] diff --git a/third_party/font-awesome/fonts/LICENSE b/third_party/font-awesome/fonts/LICENSE new file mode 100644 index 00000000..451f88fe --- /dev/null +++ b/third_party/font-awesome/fonts/LICENSE @@ -0,0 +1,92 @@ +Copyright (c) Dave Gandy (https://fontawesome.com) +with Reserved Font Name: "Font Awesome". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/third_party/font-awesome/fonts/fontawesome-webfont.eot b/third_party/font-awesome/fonts/fontawesome-webfont.eot new file mode 100755 index 00000000..6cfd5660 Binary files /dev/null and b/third_party/font-awesome/fonts/fontawesome-webfont.eot differ diff --git a/third_party/font-awesome/fonts/fontawesome-webfont.svg b/third_party/font-awesome/fonts/fontawesome-webfont.svg new file mode 100755 index 00000000..a9f84695 --- /dev/null +++ b/third_party/font-awesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/third_party/font-awesome/fonts/fontawesome-webfont.ttf b/third_party/font-awesome/fonts/fontawesome-webfont.ttf new file mode 100755 index 00000000..5cd6cff6 Binary files /dev/null and b/third_party/font-awesome/fonts/fontawesome-webfont.ttf differ diff --git a/third_party/font-awesome/fonts/fontawesome-webfont.woff b/third_party/font-awesome/fonts/fontawesome-webfont.woff new file mode 100755 index 00000000..9eaecb37 Binary files /dev/null and b/third_party/font-awesome/fonts/fontawesome-webfont.woff differ diff --git a/third_party/font-awesome/sass/LICENSE b/third_party/font-awesome/sass/LICENSE new file mode 100644 index 00000000..134731d5 --- /dev/null +++ b/third_party/font-awesome/sass/LICENSE @@ -0,0 +1,18 @@ +Copyright 2014 Dave Gandy. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/font-awesome/sass/font-awesome/_bordered-pulled.scss b/third_party/font-awesome/sass/font-awesome/_bordered-pulled.scss new file mode 100644 index 00000000..9d3fdf3a --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_bordered-pulled.scss @@ -0,0 +1,16 @@ +// Bordered & Pulled +// ------------------------- + +.#{$fa-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em $fa-border-color; + border-radius: .1em; +} + +.pull-right { float: right; } +.pull-left { float: left; } + +.#{$fa-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/third_party/font-awesome/sass/font-awesome/_core.scss b/third_party/font-awesome/sass/font-awesome/_core.scss new file mode 100644 index 00000000..861ccd9d --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_core.scss @@ -0,0 +1,12 @@ +// Base Class Definition +// ------------------------- + +.#{$fa-css-prefix} { + display: inline-block; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/third_party/font-awesome/sass/font-awesome/_fixed-width.scss b/third_party/font-awesome/sass/font-awesome/_fixed-width.scss new file mode 100644 index 00000000..b221c981 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_fixed-width.scss @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.#{$fa-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/third_party/font-awesome/sass/font-awesome/_icons.scss b/third_party/font-awesome/sass/font-awesome/_icons.scss new file mode 100644 index 00000000..efb44359 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_icons.scss @@ -0,0 +1,506 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.#{$fa-css-prefix}-glass:before { content: $fa-var-glass; } +.#{$fa-css-prefix}-music:before { content: $fa-var-music; } +.#{$fa-css-prefix}-search:before { content: $fa-var-search; } +.#{$fa-css-prefix}-envelope-o:before { content: $fa-var-envelope-o; } +.#{$fa-css-prefix}-heart:before { content: $fa-var-heart; } +.#{$fa-css-prefix}-star:before { content: $fa-var-star; } +.#{$fa-css-prefix}-star-o:before { content: $fa-var-star-o; } +.#{$fa-css-prefix}-user:before { content: $fa-var-user; } +.#{$fa-css-prefix}-film:before { content: $fa-var-film; } +.#{$fa-css-prefix}-th-large:before { content: $fa-var-th-large; } +.#{$fa-css-prefix}-th:before { content: $fa-var-th; } +.#{$fa-css-prefix}-th-list:before { content: $fa-var-th-list; } +.#{$fa-css-prefix}-check:before { content: $fa-var-check; } +.#{$fa-css-prefix}-times:before { content: $fa-var-times; } +.#{$fa-css-prefix}-search-plus:before { content: $fa-var-search-plus; } +.#{$fa-css-prefix}-search-minus:before { content: $fa-var-search-minus; } +.#{$fa-css-prefix}-power-off:before { content: $fa-var-power-off; } +.#{$fa-css-prefix}-signal:before { content: $fa-var-signal; } +.#{$fa-css-prefix}-gear:before, +.#{$fa-css-prefix}-cog:before { content: $fa-var-cog; } +.#{$fa-css-prefix}-trash-o:before { content: $fa-var-trash-o; } +.#{$fa-css-prefix}-home:before { content: $fa-var-home; } +.#{$fa-css-prefix}-file-o:before { content: $fa-var-file-o; } +.#{$fa-css-prefix}-clock-o:before { content: $fa-var-clock-o; } +.#{$fa-css-prefix}-road:before { content: $fa-var-road; } +.#{$fa-css-prefix}-download:before { content: $fa-var-download; } +.#{$fa-css-prefix}-arrow-circle-o-down:before { content: $fa-var-arrow-circle-o-down; } +.#{$fa-css-prefix}-arrow-circle-o-up:before { content: $fa-var-arrow-circle-o-up; } +.#{$fa-css-prefix}-inbox:before { content: $fa-var-inbox; } +.#{$fa-css-prefix}-play-circle-o:before { content: $fa-var-play-circle-o; } +.#{$fa-css-prefix}-rotate-right:before, +.#{$fa-css-prefix}-repeat:before { content: $fa-var-repeat; } +.#{$fa-css-prefix}-refresh:before { content: $fa-var-refresh; } +.#{$fa-css-prefix}-list-alt:before { content: $fa-var-list-alt; } +.#{$fa-css-prefix}-lock:before { content: $fa-var-lock; } +.#{$fa-css-prefix}-flag:before { content: $fa-var-flag; } +.#{$fa-css-prefix}-headphones:before { content: $fa-var-headphones; } +.#{$fa-css-prefix}-volume-off:before { content: $fa-var-volume-off; } +.#{$fa-css-prefix}-volume-down:before { content: $fa-var-volume-down; } +.#{$fa-css-prefix}-volume-up:before { content: $fa-var-volume-up; } +.#{$fa-css-prefix}-qrcode:before { content: $fa-var-qrcode; } +.#{$fa-css-prefix}-barcode:before { content: $fa-var-barcode; } +.#{$fa-css-prefix}-tag:before { content: $fa-var-tag; } +.#{$fa-css-prefix}-tags:before { content: $fa-var-tags; } +.#{$fa-css-prefix}-book:before { content: $fa-var-book; } +.#{$fa-css-prefix}-bookmark:before { content: $fa-var-bookmark; } +.#{$fa-css-prefix}-print:before { content: $fa-var-print; } +.#{$fa-css-prefix}-camera:before { content: $fa-var-camera; } +.#{$fa-css-prefix}-font:before { content: $fa-var-font; } +.#{$fa-css-prefix}-bold:before { content: $fa-var-bold; } +.#{$fa-css-prefix}-italic:before { content: $fa-var-italic; } +.#{$fa-css-prefix}-text-height:before { content: $fa-var-text-height; } +.#{$fa-css-prefix}-text-width:before { content: $fa-var-text-width; } +.#{$fa-css-prefix}-align-left:before { content: $fa-var-align-left; } +.#{$fa-css-prefix}-align-center:before { content: $fa-var-align-center; } +.#{$fa-css-prefix}-align-right:before { content: $fa-var-align-right; } +.#{$fa-css-prefix}-align-justify:before { content: $fa-var-align-justify; } +.#{$fa-css-prefix}-list:before { content: $fa-var-list; } +.#{$fa-css-prefix}-dedent:before, +.#{$fa-css-prefix}-outdent:before { content: $fa-var-outdent; } +.#{$fa-css-prefix}-indent:before { content: $fa-var-indent; } +.#{$fa-css-prefix}-video-camera:before { content: $fa-var-video-camera; } +.#{$fa-css-prefix}-photo:before, +.#{$fa-css-prefix}-image:before, +.#{$fa-css-prefix}-picture-o:before { content: $fa-var-picture-o; } +.#{$fa-css-prefix}-pencil:before { content: $fa-var-pencil; } +.#{$fa-css-prefix}-map-marker:before { content: $fa-var-map-marker; } +.#{$fa-css-prefix}-adjust:before { content: $fa-var-adjust; } +.#{$fa-css-prefix}-tint:before { content: $fa-var-tint; } +.#{$fa-css-prefix}-edit:before, +.#{$fa-css-prefix}-pencil-square-o:before { content: $fa-var-pencil-square-o; } +.#{$fa-css-prefix}-share-square-o:before { content: $fa-var-share-square-o; } +.#{$fa-css-prefix}-check-square-o:before { content: $fa-var-check-square-o; } +.#{$fa-css-prefix}-arrows:before { content: $fa-var-arrows; } +.#{$fa-css-prefix}-step-backward:before { content: $fa-var-step-backward; } +.#{$fa-css-prefix}-fast-backward:before { content: $fa-var-fast-backward; } +.#{$fa-css-prefix}-backward:before { content: $fa-var-backward; } +.#{$fa-css-prefix}-play:before { content: $fa-var-play; } +.#{$fa-css-prefix}-pause:before { content: $fa-var-pause; } +.#{$fa-css-prefix}-stop:before { content: $fa-var-stop; } +.#{$fa-css-prefix}-forward:before { content: $fa-var-forward; } +.#{$fa-css-prefix}-fast-forward:before { content: $fa-var-fast-forward; } +.#{$fa-css-prefix}-step-forward:before { content: $fa-var-step-forward; } +.#{$fa-css-prefix}-eject:before { content: $fa-var-eject; } +.#{$fa-css-prefix}-chevron-left:before { content: $fa-var-chevron-left; } +.#{$fa-css-prefix}-chevron-right:before { content: $fa-var-chevron-right; } +.#{$fa-css-prefix}-plus-circle:before { content: $fa-var-plus-circle; } +.#{$fa-css-prefix}-minus-circle:before { content: $fa-var-minus-circle; } +.#{$fa-css-prefix}-times-circle:before { content: $fa-var-times-circle; } +.#{$fa-css-prefix}-check-circle:before { content: $fa-var-check-circle; } +.#{$fa-css-prefix}-question-circle:before { content: $fa-var-question-circle; } +.#{$fa-css-prefix}-info-circle:before { content: $fa-var-info-circle; } +.#{$fa-css-prefix}-crosshairs:before { content: $fa-var-crosshairs; } +.#{$fa-css-prefix}-times-circle-o:before { content: $fa-var-times-circle-o; } +.#{$fa-css-prefix}-check-circle-o:before { content: $fa-var-check-circle-o; } +.#{$fa-css-prefix}-ban:before { content: $fa-var-ban; } +.#{$fa-css-prefix}-arrow-left:before { content: $fa-var-arrow-left; } +.#{$fa-css-prefix}-arrow-right:before { content: $fa-var-arrow-right; } +.#{$fa-css-prefix}-arrow-up:before { content: $fa-var-arrow-up; } +.#{$fa-css-prefix}-arrow-down:before { content: $fa-var-arrow-down; } +.#{$fa-css-prefix}-mail-forward:before, +.#{$fa-css-prefix}-share:before { content: $fa-var-share; } +.#{$fa-css-prefix}-expand:before { content: $fa-var-expand; } +.#{$fa-css-prefix}-compress:before { content: $fa-var-compress; } +.#{$fa-css-prefix}-plus:before { content: $fa-var-plus; } +.#{$fa-css-prefix}-minus:before { content: $fa-var-minus; } +.#{$fa-css-prefix}-asterisk:before { content: $fa-var-asterisk; } +.#{$fa-css-prefix}-exclamation-circle:before { content: $fa-var-exclamation-circle; } +.#{$fa-css-prefix}-gift:before { content: $fa-var-gift; } +.#{$fa-css-prefix}-leaf:before { content: $fa-var-leaf; } +.#{$fa-css-prefix}-fire:before { content: $fa-var-fire; } +.#{$fa-css-prefix}-eye:before { content: $fa-var-eye; } +.#{$fa-css-prefix}-eye-slash:before { content: $fa-var-eye-slash; } +.#{$fa-css-prefix}-warning:before, +.#{$fa-css-prefix}-exclamation-triangle:before { content: $fa-var-exclamation-triangle; } +.#{$fa-css-prefix}-plane:before { content: $fa-var-plane; } +.#{$fa-css-prefix}-calendar:before { content: $fa-var-calendar; } +.#{$fa-css-prefix}-random:before { content: $fa-var-random; } +.#{$fa-css-prefix}-comment:before { content: $fa-var-comment; } +.#{$fa-css-prefix}-magnet:before { content: $fa-var-magnet; } +.#{$fa-css-prefix}-chevron-up:before { content: $fa-var-chevron-up; } +.#{$fa-css-prefix}-chevron-down:before { content: $fa-var-chevron-down; } +.#{$fa-css-prefix}-retweet:before { content: $fa-var-retweet; } +.#{$fa-css-prefix}-shopping-cart:before { content: $fa-var-shopping-cart; } +.#{$fa-css-prefix}-folder:before { content: $fa-var-folder; } +.#{$fa-css-prefix}-folder-open:before { content: $fa-var-folder-open; } +.#{$fa-css-prefix}-arrows-v:before { content: $fa-var-arrows-v; } +.#{$fa-css-prefix}-arrows-h:before { content: $fa-var-arrows-h; } +.#{$fa-css-prefix}-bar-chart-o:before { content: $fa-var-bar-chart-o; } +.#{$fa-css-prefix}-twitter-square:before { content: $fa-var-twitter-square; } +.#{$fa-css-prefix}-facebook-square:before { content: $fa-var-facebook-square; } +.#{$fa-css-prefix}-camera-retro:before { content: $fa-var-camera-retro; } +.#{$fa-css-prefix}-key:before { content: $fa-var-key; } +.#{$fa-css-prefix}-gears:before, +.#{$fa-css-prefix}-cogs:before { content: $fa-var-cogs; } +.#{$fa-css-prefix}-comments:before { content: $fa-var-comments; } +.#{$fa-css-prefix}-thumbs-o-up:before { content: $fa-var-thumbs-o-up; } +.#{$fa-css-prefix}-thumbs-o-down:before { content: $fa-var-thumbs-o-down; } +.#{$fa-css-prefix}-star-half:before { content: $fa-var-star-half; } +.#{$fa-css-prefix}-heart-o:before { content: $fa-var-heart-o; } +.#{$fa-css-prefix}-sign-out:before { content: $fa-var-sign-out; } +.#{$fa-css-prefix}-linkedin-square:before { content: $fa-var-linkedin-square; } +.#{$fa-css-prefix}-thumb-tack:before { content: $fa-var-thumb-tack; } +.#{$fa-css-prefix}-external-link:before { content: $fa-var-external-link; } +.#{$fa-css-prefix}-sign-in:before { content: $fa-var-sign-in; } +.#{$fa-css-prefix}-trophy:before { content: $fa-var-trophy; } +.#{$fa-css-prefix}-github-square:before { content: $fa-var-github-square; } +.#{$fa-css-prefix}-upload:before { content: $fa-var-upload; } +.#{$fa-css-prefix}-lemon-o:before { content: $fa-var-lemon-o; } +.#{$fa-css-prefix}-phone:before { content: $fa-var-phone; } +.#{$fa-css-prefix}-square-o:before { content: $fa-var-square-o; } +.#{$fa-css-prefix}-bookmark-o:before { content: $fa-var-bookmark-o; } +.#{$fa-css-prefix}-phone-square:before { content: $fa-var-phone-square; } +.#{$fa-css-prefix}-twitter:before { content: $fa-var-twitter; } +.#{$fa-css-prefix}-facebook:before { content: $fa-var-facebook; } +.#{$fa-css-prefix}-github:before { content: $fa-var-github; } +.#{$fa-css-prefix}-unlock:before { content: $fa-var-unlock; } +.#{$fa-css-prefix}-credit-card:before { content: $fa-var-credit-card; } +.#{$fa-css-prefix}-rss:before { content: $fa-var-rss; } +.#{$fa-css-prefix}-hdd-o:before { content: $fa-var-hdd-o; } +.#{$fa-css-prefix}-bullhorn:before { content: $fa-var-bullhorn; } +.#{$fa-css-prefix}-bell:before { content: $fa-var-bell; } +.#{$fa-css-prefix}-certificate:before { content: $fa-var-certificate; } +.#{$fa-css-prefix}-hand-o-right:before { content: $fa-var-hand-o-right; } +.#{$fa-css-prefix}-hand-o-left:before { content: $fa-var-hand-o-left; } +.#{$fa-css-prefix}-hand-o-up:before { content: $fa-var-hand-o-up; } +.#{$fa-css-prefix}-hand-o-down:before { content: $fa-var-hand-o-down; } +.#{$fa-css-prefix}-arrow-circle-left:before { content: $fa-var-arrow-circle-left; } +.#{$fa-css-prefix}-arrow-circle-right:before { content: $fa-var-arrow-circle-right; } +.#{$fa-css-prefix}-arrow-circle-up:before { content: $fa-var-arrow-circle-up; } +.#{$fa-css-prefix}-arrow-circle-down:before { content: $fa-var-arrow-circle-down; } +.#{$fa-css-prefix}-globe:before { content: $fa-var-globe; } +.#{$fa-css-prefix}-wrench:before { content: $fa-var-wrench; } +.#{$fa-css-prefix}-tasks:before { content: $fa-var-tasks; } +.#{$fa-css-prefix}-filter:before { content: $fa-var-filter; } +.#{$fa-css-prefix}-briefcase:before { content: $fa-var-briefcase; } +.#{$fa-css-prefix}-arrows-alt:before { content: $fa-var-arrows-alt; } +.#{$fa-css-prefix}-group:before, +.#{$fa-css-prefix}-users:before { content: $fa-var-users; } +.#{$fa-css-prefix}-chain:before, +.#{$fa-css-prefix}-link:before { content: $fa-var-link; } +.#{$fa-css-prefix}-cloud:before { content: $fa-var-cloud; } +.#{$fa-css-prefix}-flask:before { content: $fa-var-flask; } +.#{$fa-css-prefix}-cut:before, +.#{$fa-css-prefix}-scissors:before { content: $fa-var-scissors; } +.#{$fa-css-prefix}-copy:before, +.#{$fa-css-prefix}-files-o:before { content: $fa-var-files-o; } +.#{$fa-css-prefix}-paperclip:before { content: $fa-var-paperclip; } +.#{$fa-css-prefix}-save:before, +.#{$fa-css-prefix}-floppy-o:before { content: $fa-var-floppy-o; } +.#{$fa-css-prefix}-square:before { content: $fa-var-square; } +.#{$fa-css-prefix}-navicon:before, +.#{$fa-css-prefix}-reorder:before, +.#{$fa-css-prefix}-bars:before { content: $fa-var-bars; } +.#{$fa-css-prefix}-list-ul:before { content: $fa-var-list-ul; } +.#{$fa-css-prefix}-list-ol:before { content: $fa-var-list-ol; } +.#{$fa-css-prefix}-strikethrough:before { content: $fa-var-strikethrough; } +.#{$fa-css-prefix}-underline:before { content: $fa-var-underline; } +.#{$fa-css-prefix}-table:before { content: $fa-var-table; } +.#{$fa-css-prefix}-magic:before { content: $fa-var-magic; } +.#{$fa-css-prefix}-truck:before { content: $fa-var-truck; } +.#{$fa-css-prefix}-pinterest:before { content: $fa-var-pinterest; } +.#{$fa-css-prefix}-pinterest-square:before { content: $fa-var-pinterest-square; } +.#{$fa-css-prefix}-google-plus-square:before { content: $fa-var-google-plus-square; } +.#{$fa-css-prefix}-google-plus:before { content: $fa-var-google-plus; } +.#{$fa-css-prefix}-money:before { content: $fa-var-money; } +.#{$fa-css-prefix}-caret-down:before { content: $fa-var-caret-down; } +.#{$fa-css-prefix}-caret-up:before { content: $fa-var-caret-up; } +.#{$fa-css-prefix}-caret-left:before { content: $fa-var-caret-left; } +.#{$fa-css-prefix}-caret-right:before { content: $fa-var-caret-right; } +.#{$fa-css-prefix}-columns:before { content: $fa-var-columns; } +.#{$fa-css-prefix}-unsorted:before, +.#{$fa-css-prefix}-sort:before { content: $fa-var-sort; } +.#{$fa-css-prefix}-sort-down:before, +.#{$fa-css-prefix}-sort-desc:before { content: $fa-var-sort-desc; } +.#{$fa-css-prefix}-sort-up:before, +.#{$fa-css-prefix}-sort-asc:before { content: $fa-var-sort-asc; } +.#{$fa-css-prefix}-envelope:before { content: $fa-var-envelope; } +.#{$fa-css-prefix}-linkedin:before { content: $fa-var-linkedin; } +.#{$fa-css-prefix}-rotate-left:before, +.#{$fa-css-prefix}-undo:before { content: $fa-var-undo; } +.#{$fa-css-prefix}-legal:before, +.#{$fa-css-prefix}-gavel:before { content: $fa-var-gavel; } +.#{$fa-css-prefix}-dashboard:before, +.#{$fa-css-prefix}-tachometer:before { content: $fa-var-tachometer; } +.#{$fa-css-prefix}-comment-o:before { content: $fa-var-comment-o; } +.#{$fa-css-prefix}-comments-o:before { content: $fa-var-comments-o; } +.#{$fa-css-prefix}-flash:before, +.#{$fa-css-prefix}-bolt:before { content: $fa-var-bolt; } +.#{$fa-css-prefix}-sitemap:before { content: $fa-var-sitemap; } +.#{$fa-css-prefix}-umbrella:before { content: $fa-var-umbrella; } +.#{$fa-css-prefix}-paste:before, +.#{$fa-css-prefix}-clipboard:before { content: $fa-var-clipboard; } +.#{$fa-css-prefix}-lightbulb-o:before { content: $fa-var-lightbulb-o; } +.#{$fa-css-prefix}-exchange:before { content: $fa-var-exchange; } +.#{$fa-css-prefix}-cloud-download:before { content: $fa-var-cloud-download; } +.#{$fa-css-prefix}-cloud-upload:before { content: $fa-var-cloud-upload; } +.#{$fa-css-prefix}-user-md:before { content: $fa-var-user-md; } +.#{$fa-css-prefix}-stethoscope:before { content: $fa-var-stethoscope; } +.#{$fa-css-prefix}-suitcase:before { content: $fa-var-suitcase; } +.#{$fa-css-prefix}-bell-o:before { content: $fa-var-bell-o; } +.#{$fa-css-prefix}-coffee:before { content: $fa-var-coffee; } +.#{$fa-css-prefix}-cutlery:before { content: $fa-var-cutlery; } +.#{$fa-css-prefix}-file-text-o:before { content: $fa-var-file-text-o; } +.#{$fa-css-prefix}-building-o:before { content: $fa-var-building-o; } +.#{$fa-css-prefix}-hospital-o:before { content: $fa-var-hospital-o; } +.#{$fa-css-prefix}-ambulance:before { content: $fa-var-ambulance; } +.#{$fa-css-prefix}-medkit:before { content: $fa-var-medkit; } +.#{$fa-css-prefix}-fighter-jet:before { content: $fa-var-fighter-jet; } +.#{$fa-css-prefix}-beer:before { content: $fa-var-beer; } +.#{$fa-css-prefix}-h-square:before { content: $fa-var-h-square; } +.#{$fa-css-prefix}-plus-square:before { content: $fa-var-plus-square; } +.#{$fa-css-prefix}-angle-double-left:before { content: $fa-var-angle-double-left; } +.#{$fa-css-prefix}-angle-double-right:before { content: $fa-var-angle-double-right; } +.#{$fa-css-prefix}-angle-double-up:before { content: $fa-var-angle-double-up; } +.#{$fa-css-prefix}-angle-double-down:before { content: $fa-var-angle-double-down; } +.#{$fa-css-prefix}-angle-left:before { content: $fa-var-angle-left; } +.#{$fa-css-prefix}-angle-right:before { content: $fa-var-angle-right; } +.#{$fa-css-prefix}-angle-up:before { content: $fa-var-angle-up; } +.#{$fa-css-prefix}-angle-down:before { content: $fa-var-angle-down; } +.#{$fa-css-prefix}-desktop:before { content: $fa-var-desktop; } +.#{$fa-css-prefix}-laptop:before { content: $fa-var-laptop; } +.#{$fa-css-prefix}-tablet:before { content: $fa-var-tablet; } +.#{$fa-css-prefix}-mobile-phone:before, +.#{$fa-css-prefix}-mobile:before { content: $fa-var-mobile; } +.#{$fa-css-prefix}-circle-o:before { content: $fa-var-circle-o; } +.#{$fa-css-prefix}-quote-left:before { content: $fa-var-quote-left; } +.#{$fa-css-prefix}-quote-right:before { content: $fa-var-quote-right; } +.#{$fa-css-prefix}-spinner:before { content: $fa-var-spinner; } +.#{$fa-css-prefix}-circle:before { content: $fa-var-circle; } +.#{$fa-css-prefix}-mail-reply:before, +.#{$fa-css-prefix}-reply:before { content: $fa-var-reply; } +.#{$fa-css-prefix}-github-alt:before { content: $fa-var-github-alt; } +.#{$fa-css-prefix}-folder-o:before { content: $fa-var-folder-o; } +.#{$fa-css-prefix}-folder-open-o:before { content: $fa-var-folder-open-o; } +.#{$fa-css-prefix}-smile-o:before { content: $fa-var-smile-o; } +.#{$fa-css-prefix}-frown-o:before { content: $fa-var-frown-o; } +.#{$fa-css-prefix}-meh-o:before { content: $fa-var-meh-o; } +.#{$fa-css-prefix}-gamepad:before { content: $fa-var-gamepad; } +.#{$fa-css-prefix}-keyboard-o:before { content: $fa-var-keyboard-o; } +.#{$fa-css-prefix}-flag-o:before { content: $fa-var-flag-o; } +.#{$fa-css-prefix}-flag-checkered:before { content: $fa-var-flag-checkered; } +.#{$fa-css-prefix}-terminal:before { content: $fa-var-terminal; } +.#{$fa-css-prefix}-code:before { content: $fa-var-code; } +.#{$fa-css-prefix}-mail-reply-all:before, +.#{$fa-css-prefix}-reply-all:before { content: $fa-var-reply-all; } +.#{$fa-css-prefix}-star-half-empty:before, +.#{$fa-css-prefix}-star-half-full:before, +.#{$fa-css-prefix}-star-half-o:before { content: $fa-var-star-half-o; } +.#{$fa-css-prefix}-location-arrow:before { content: $fa-var-location-arrow; } +.#{$fa-css-prefix}-crop:before { content: $fa-var-crop; } +.#{$fa-css-prefix}-code-fork:before { content: $fa-var-code-fork; } +.#{$fa-css-prefix}-unlink:before, +.#{$fa-css-prefix}-chain-broken:before { content: $fa-var-chain-broken; } +.#{$fa-css-prefix}-question:before { content: $fa-var-question; } +.#{$fa-css-prefix}-info:before { content: $fa-var-info; } +.#{$fa-css-prefix}-exclamation:before { content: $fa-var-exclamation; } +.#{$fa-css-prefix}-superscript:before { content: $fa-var-superscript; } +.#{$fa-css-prefix}-subscript:before { content: $fa-var-subscript; } +.#{$fa-css-prefix}-eraser:before { content: $fa-var-eraser; } +.#{$fa-css-prefix}-puzzle-piece:before { content: $fa-var-puzzle-piece; } +.#{$fa-css-prefix}-microphone:before { content: $fa-var-microphone; } +.#{$fa-css-prefix}-microphone-slash:before { content: $fa-var-microphone-slash; } +.#{$fa-css-prefix}-shield:before { content: $fa-var-shield; } +.#{$fa-css-prefix}-calendar-o:before { content: $fa-var-calendar-o; } +.#{$fa-css-prefix}-fire-extinguisher:before { content: $fa-var-fire-extinguisher; } +.#{$fa-css-prefix}-rocket:before { content: $fa-var-rocket; } +.#{$fa-css-prefix}-maxcdn:before { content: $fa-var-maxcdn; } +.#{$fa-css-prefix}-chevron-circle-left:before { content: $fa-var-chevron-circle-left; } +.#{$fa-css-prefix}-chevron-circle-right:before { content: $fa-var-chevron-circle-right; } +.#{$fa-css-prefix}-chevron-circle-up:before { content: $fa-var-chevron-circle-up; } +.#{$fa-css-prefix}-chevron-circle-down:before { content: $fa-var-chevron-circle-down; } +.#{$fa-css-prefix}-html5:before { content: $fa-var-html5; } +.#{$fa-css-prefix}-css3:before { content: $fa-var-css3; } +.#{$fa-css-prefix}-anchor:before { content: $fa-var-anchor; } +.#{$fa-css-prefix}-unlock-alt:before { content: $fa-var-unlock-alt; } +.#{$fa-css-prefix}-bullseye:before { content: $fa-var-bullseye; } +.#{$fa-css-prefix}-ellipsis-h:before { content: $fa-var-ellipsis-h; } +.#{$fa-css-prefix}-ellipsis-v:before { content: $fa-var-ellipsis-v; } +.#{$fa-css-prefix}-rss-square:before { content: $fa-var-rss-square; } +.#{$fa-css-prefix}-play-circle:before { content: $fa-var-play-circle; } +.#{$fa-css-prefix}-ticket:before { content: $fa-var-ticket; } +.#{$fa-css-prefix}-minus-square:before { content: $fa-var-minus-square; } +.#{$fa-css-prefix}-minus-square-o:before { content: $fa-var-minus-square-o; } +.#{$fa-css-prefix}-level-up:before { content: $fa-var-level-up; } +.#{$fa-css-prefix}-level-down:before { content: $fa-var-level-down; } +.#{$fa-css-prefix}-check-square:before { content: $fa-var-check-square; } +.#{$fa-css-prefix}-pencil-square:before { content: $fa-var-pencil-square; } +.#{$fa-css-prefix}-external-link-square:before { content: $fa-var-external-link-square; } +.#{$fa-css-prefix}-share-square:before { content: $fa-var-share-square; } +.#{$fa-css-prefix}-compass:before { content: $fa-var-compass; } +.#{$fa-css-prefix}-toggle-down:before, +.#{$fa-css-prefix}-caret-square-o-down:before { content: $fa-var-caret-square-o-down; } +.#{$fa-css-prefix}-toggle-up:before, +.#{$fa-css-prefix}-caret-square-o-up:before { content: $fa-var-caret-square-o-up; } +.#{$fa-css-prefix}-toggle-right:before, +.#{$fa-css-prefix}-caret-square-o-right:before { content: $fa-var-caret-square-o-right; } +.#{$fa-css-prefix}-euro:before, +.#{$fa-css-prefix}-eur:before { content: $fa-var-eur; } +.#{$fa-css-prefix}-gbp:before { content: $fa-var-gbp; } +.#{$fa-css-prefix}-dollar:before, +.#{$fa-css-prefix}-usd:before { content: $fa-var-usd; } +.#{$fa-css-prefix}-rupee:before, +.#{$fa-css-prefix}-inr:before { content: $fa-var-inr; } +.#{$fa-css-prefix}-cny:before, +.#{$fa-css-prefix}-rmb:before, +.#{$fa-css-prefix}-yen:before, +.#{$fa-css-prefix}-jpy:before { content: $fa-var-jpy; } +.#{$fa-css-prefix}-ruble:before, +.#{$fa-css-prefix}-rouble:before, +.#{$fa-css-prefix}-rub:before { content: $fa-var-rub; } +.#{$fa-css-prefix}-won:before, +.#{$fa-css-prefix}-krw:before { content: $fa-var-krw; } +.#{$fa-css-prefix}-bitcoin:before, +.#{$fa-css-prefix}-btc:before { content: $fa-var-btc; } +.#{$fa-css-prefix}-file:before { content: $fa-var-file; } +.#{$fa-css-prefix}-file-text:before { content: $fa-var-file-text; } +.#{$fa-css-prefix}-sort-alpha-asc:before { content: $fa-var-sort-alpha-asc; } +.#{$fa-css-prefix}-sort-alpha-desc:before { content: $fa-var-sort-alpha-desc; } +.#{$fa-css-prefix}-sort-amount-asc:before { content: $fa-var-sort-amount-asc; } +.#{$fa-css-prefix}-sort-amount-desc:before { content: $fa-var-sort-amount-desc; } +.#{$fa-css-prefix}-sort-numeric-asc:before { content: $fa-var-sort-numeric-asc; } +.#{$fa-css-prefix}-sort-numeric-desc:before { content: $fa-var-sort-numeric-desc; } +.#{$fa-css-prefix}-thumbs-up:before { content: $fa-var-thumbs-up; } +.#{$fa-css-prefix}-thumbs-down:before { content: $fa-var-thumbs-down; } +.#{$fa-css-prefix}-youtube-square:before { content: $fa-var-youtube-square; } +.#{$fa-css-prefix}-youtube:before { content: $fa-var-youtube; } +.#{$fa-css-prefix}-xing:before { content: $fa-var-xing; } +.#{$fa-css-prefix}-xing-square:before { content: $fa-var-xing-square; } +.#{$fa-css-prefix}-youtube-play:before { content: $fa-var-youtube-play; } +.#{$fa-css-prefix}-dropbox:before { content: $fa-var-dropbox; } +.#{$fa-css-prefix}-stack-overflow:before { content: $fa-var-stack-overflow; } +.#{$fa-css-prefix}-instagram:before { content: $fa-var-instagram; } +.#{$fa-css-prefix}-flickr:before { content: $fa-var-flickr; } +.#{$fa-css-prefix}-adn:before { content: $fa-var-adn; } +.#{$fa-css-prefix}-bitbucket:before { content: $fa-var-bitbucket; } +.#{$fa-css-prefix}-bitbucket-square:before { content: $fa-var-bitbucket-square; } +.#{$fa-css-prefix}-tumblr:before { content: $fa-var-tumblr; } +.#{$fa-css-prefix}-tumblr-square:before { content: $fa-var-tumblr-square; } +.#{$fa-css-prefix}-long-arrow-down:before { content: $fa-var-long-arrow-down; } +.#{$fa-css-prefix}-long-arrow-up:before { content: $fa-var-long-arrow-up; } +.#{$fa-css-prefix}-long-arrow-left:before { content: $fa-var-long-arrow-left; } +.#{$fa-css-prefix}-long-arrow-right:before { content: $fa-var-long-arrow-right; } +.#{$fa-css-prefix}-apple:before { content: $fa-var-apple; } +.#{$fa-css-prefix}-windows:before { content: $fa-var-windows; } +.#{$fa-css-prefix}-android:before { content: $fa-var-android; } +.#{$fa-css-prefix}-linux:before { content: $fa-var-linux; } +.#{$fa-css-prefix}-dribbble:before { content: $fa-var-dribbble; } +.#{$fa-css-prefix}-skype:before { content: $fa-var-skype; } +.#{$fa-css-prefix}-foursquare:before { content: $fa-var-foursquare; } +.#{$fa-css-prefix}-trello:before { content: $fa-var-trello; } +.#{$fa-css-prefix}-female:before { content: $fa-var-female; } +.#{$fa-css-prefix}-male:before { content: $fa-var-male; } +.#{$fa-css-prefix}-gittip:before { content: $fa-var-gittip; } +.#{$fa-css-prefix}-sun-o:before { content: $fa-var-sun-o; } +.#{$fa-css-prefix}-moon-o:before { content: $fa-var-moon-o; } +.#{$fa-css-prefix}-archive:before { content: $fa-var-archive; } +.#{$fa-css-prefix}-bug:before { content: $fa-var-bug; } +.#{$fa-css-prefix}-vk:before { content: $fa-var-vk; } +.#{$fa-css-prefix}-weibo:before { content: $fa-var-weibo; } +.#{$fa-css-prefix}-renren:before { content: $fa-var-renren; } +.#{$fa-css-prefix}-pagelines:before { content: $fa-var-pagelines; } +.#{$fa-css-prefix}-stack-exchange:before { content: $fa-var-stack-exchange; } +.#{$fa-css-prefix}-arrow-circle-o-right:before { content: $fa-var-arrow-circle-o-right; } +.#{$fa-css-prefix}-arrow-circle-o-left:before { content: $fa-var-arrow-circle-o-left; } +.#{$fa-css-prefix}-toggle-left:before, +.#{$fa-css-prefix}-caret-square-o-left:before { content: $fa-var-caret-square-o-left; } +.#{$fa-css-prefix}-dot-circle-o:before { content: $fa-var-dot-circle-o; } +.#{$fa-css-prefix}-wheelchair:before { content: $fa-var-wheelchair; } +.#{$fa-css-prefix}-vimeo-square:before { content: $fa-var-vimeo-square; } +.#{$fa-css-prefix}-turkish-lira:before, +.#{$fa-css-prefix}-try:before { content: $fa-var-try; } +.#{$fa-css-prefix}-plus-square-o:before { content: $fa-var-plus-square-o; } +.#{$fa-css-prefix}-space-shuttle:before { content: $fa-var-space-shuttle; } +.#{$fa-css-prefix}-slack:before { content: $fa-var-slack; } +.#{$fa-css-prefix}-envelope-square:before { content: $fa-var-envelope-square; } +.#{$fa-css-prefix}-wordpress:before { content: $fa-var-wordpress; } +.#{$fa-css-prefix}-openid:before { content: $fa-var-openid; } +.#{$fa-css-prefix}-institution:before, +.#{$fa-css-prefix}-bank:before, +.#{$fa-css-prefix}-university:before { content: $fa-var-university; } +.#{$fa-css-prefix}-mortar-board:before, +.#{$fa-css-prefix}-graduation-cap:before { content: $fa-var-graduation-cap; } +.#{$fa-css-prefix}-yahoo:before { content: $fa-var-yahoo; } +.#{$fa-css-prefix}-google:before { content: $fa-var-google; } +.#{$fa-css-prefix}-reddit:before { content: $fa-var-reddit; } +.#{$fa-css-prefix}-reddit-square:before { content: $fa-var-reddit-square; } +.#{$fa-css-prefix}-stumbleupon-circle:before { content: $fa-var-stumbleupon-circle; } +.#{$fa-css-prefix}-stumbleupon:before { content: $fa-var-stumbleupon; } +.#{$fa-css-prefix}-delicious:before { content: $fa-var-delicious; } +.#{$fa-css-prefix}-digg:before { content: $fa-var-digg; } +.#{$fa-css-prefix}-pied-piper-square:before, +.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; } +.#{$fa-css-prefix}-pied-piper-alt:before { content: $fa-var-pied-piper-alt; } +.#{$fa-css-prefix}-drupal:before { content: $fa-var-drupal; } +.#{$fa-css-prefix}-joomla:before { content: $fa-var-joomla; } +.#{$fa-css-prefix}-language:before { content: $fa-var-language; } +.#{$fa-css-prefix}-fax:before { content: $fa-var-fax; } +.#{$fa-css-prefix}-building:before { content: $fa-var-building; } +.#{$fa-css-prefix}-child:before { content: $fa-var-child; } +.#{$fa-css-prefix}-paw:before { content: $fa-var-paw; } +.#{$fa-css-prefix}-spoon:before { content: $fa-var-spoon; } +.#{$fa-css-prefix}-cube:before { content: $fa-var-cube; } +.#{$fa-css-prefix}-cubes:before { content: $fa-var-cubes; } +.#{$fa-css-prefix}-behance:before { content: $fa-var-behance; } +.#{$fa-css-prefix}-behance-square:before { content: $fa-var-behance-square; } +.#{$fa-css-prefix}-steam:before { content: $fa-var-steam; } +.#{$fa-css-prefix}-steam-square:before { content: $fa-var-steam-square; } +.#{$fa-css-prefix}-recycle:before { content: $fa-var-recycle; } +.#{$fa-css-prefix}-automobile:before, +.#{$fa-css-prefix}-car:before { content: $fa-var-car; } +.#{$fa-css-prefix}-cab:before, +.#{$fa-css-prefix}-taxi:before { content: $fa-var-taxi; } +.#{$fa-css-prefix}-tree:before { content: $fa-var-tree; } +.#{$fa-css-prefix}-spotify:before { content: $fa-var-spotify; } +.#{$fa-css-prefix}-deviantart:before { content: $fa-var-deviantart; } +.#{$fa-css-prefix}-soundcloud:before { content: $fa-var-soundcloud; } +.#{$fa-css-prefix}-database:before { content: $fa-var-database; } +.#{$fa-css-prefix}-file-pdf-o:before { content: $fa-var-file-pdf-o; } +.#{$fa-css-prefix}-file-word-o:before { content: $fa-var-file-word-o; } +.#{$fa-css-prefix}-file-excel-o:before { content: $fa-var-file-excel-o; } +.#{$fa-css-prefix}-file-powerpoint-o:before { content: $fa-var-file-powerpoint-o; } +.#{$fa-css-prefix}-file-photo-o:before, +.#{$fa-css-prefix}-file-picture-o:before, +.#{$fa-css-prefix}-file-image-o:before { content: $fa-var-file-image-o; } +.#{$fa-css-prefix}-file-zip-o:before, +.#{$fa-css-prefix}-file-archive-o:before { content: $fa-var-file-archive-o; } +.#{$fa-css-prefix}-file-sound-o:before, +.#{$fa-css-prefix}-file-audio-o:before { content: $fa-var-file-audio-o; } +.#{$fa-css-prefix}-file-movie-o:before, +.#{$fa-css-prefix}-file-video-o:before { content: $fa-var-file-video-o; } +.#{$fa-css-prefix}-file-code-o:before { content: $fa-var-file-code-o; } +.#{$fa-css-prefix}-vine:before { content: $fa-var-vine; } +.#{$fa-css-prefix}-codepen:before { content: $fa-var-codepen; } +.#{$fa-css-prefix}-jsfiddle:before { content: $fa-var-jsfiddle; } +.#{$fa-css-prefix}-life-bouy:before, +.#{$fa-css-prefix}-life-saver:before, +.#{$fa-css-prefix}-support:before, +.#{$fa-css-prefix}-life-ring:before { content: $fa-var-life-ring; } +.#{$fa-css-prefix}-circle-o-notch:before { content: $fa-var-circle-o-notch; } +.#{$fa-css-prefix}-ra:before, +.#{$fa-css-prefix}-rebel:before { content: $fa-var-rebel; } +.#{$fa-css-prefix}-ge:before, +.#{$fa-css-prefix}-empire:before { content: $fa-var-empire; } +.#{$fa-css-prefix}-git-square:before { content: $fa-var-git-square; } +.#{$fa-css-prefix}-git:before { content: $fa-var-git; } +.#{$fa-css-prefix}-hacker-news:before { content: $fa-var-hacker-news; } +.#{$fa-css-prefix}-tencent-weibo:before { content: $fa-var-tencent-weibo; } +.#{$fa-css-prefix}-qq:before { content: $fa-var-qq; } +.#{$fa-css-prefix}-wechat:before, +.#{$fa-css-prefix}-weixin:before { content: $fa-var-weixin; } +.#{$fa-css-prefix}-send:before, +.#{$fa-css-prefix}-paper-plane:before { content: $fa-var-paper-plane; } +.#{$fa-css-prefix}-send-o:before, +.#{$fa-css-prefix}-paper-plane-o:before { content: $fa-var-paper-plane-o; } +.#{$fa-css-prefix}-history:before { content: $fa-var-history; } +.#{$fa-css-prefix}-circle-thin:before { content: $fa-var-circle-thin; } +.#{$fa-css-prefix}-header:before { content: $fa-var-header; } +.#{$fa-css-prefix}-paragraph:before { content: $fa-var-paragraph; } +.#{$fa-css-prefix}-sliders:before { content: $fa-var-sliders; } +.#{$fa-css-prefix}-share-alt:before { content: $fa-var-share-alt; } +.#{$fa-css-prefix}-share-alt-square:before { content: $fa-var-share-alt-square; } +.#{$fa-css-prefix}-bomb:before { content: $fa-var-bomb; } diff --git a/third_party/font-awesome/sass/font-awesome/_larger.scss b/third_party/font-awesome/sass/font-awesome/_larger.scss new file mode 100644 index 00000000..41e9a818 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_larger.scss @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.#{$fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.#{$fa-css-prefix}-2x { font-size: 2em; } +.#{$fa-css-prefix}-3x { font-size: 3em; } +.#{$fa-css-prefix}-4x { font-size: 4em; } +.#{$fa-css-prefix}-5x { font-size: 5em; } diff --git a/third_party/font-awesome/sass/font-awesome/_list.scss b/third_party/font-awesome/sass/font-awesome/_list.scss new file mode 100644 index 00000000..7d1e4d54 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_list.scss @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.#{$fa-css-prefix}-ul { + padding-left: 0; + margin-left: $fa-li-width; + list-style-type: none; + > li { position: relative; } +} +.#{$fa-css-prefix}-li { + position: absolute; + left: -$fa-li-width; + width: $fa-li-width; + top: (2em / 14); + text-align: center; + &.#{$fa-css-prefix}-lg { + left: -$fa-li-width + (4em / 14); + } +} diff --git a/third_party/font-awesome/sass/font-awesome/_mixins.scss b/third_party/font-awesome/sass/font-awesome/_mixins.scss new file mode 100644 index 00000000..3354e695 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_mixins.scss @@ -0,0 +1,20 @@ +// Mixins +// -------------------------- + +@mixin fa-icon-rotate($degrees, $rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -webkit-transform: rotate($degrees); + -moz-transform: rotate($degrees); + -ms-transform: rotate($degrees); + -o-transform: rotate($degrees); + transform: rotate($degrees); +} + +@mixin fa-icon-flip($horiz, $vert, $rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -webkit-transform: scale($horiz, $vert); + -moz-transform: scale($horiz, $vert); + -ms-transform: scale($horiz, $vert); + -o-transform: scale($horiz, $vert); + transform: scale($horiz, $vert); +} diff --git a/third_party/font-awesome/sass/font-awesome/_path.scss b/third_party/font-awesome/sass/font-awesome/_path.scss new file mode 100644 index 00000000..fd21c351 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_path.scss @@ -0,0 +1,14 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'FontAwesome'; + src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); + src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), + url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), + url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), + url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); + //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + font-weight: normal; + font-style: normal; +} diff --git a/third_party/font-awesome/sass/font-awesome/_rotated-flipped.scss b/third_party/font-awesome/sass/font-awesome/_rotated-flipped.scss new file mode 100644 index 00000000..343fa550 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_rotated-flipped.scss @@ -0,0 +1,9 @@ +// Rotated & Flipped Icons +// ------------------------- + +.#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } +.#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } +.#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } + +.#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } +.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } diff --git a/third_party/font-awesome/sass/font-awesome/_spinning.scss b/third_party/font-awesome/sass/font-awesome/_spinning.scss new file mode 100644 index 00000000..c3787446 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_spinning.scss @@ -0,0 +1,32 @@ +// Spinning Icons +// -------------------------- + +.#{$fa-css-prefix}-spin { + -webkit-animation: spin 2s infinite linear; + -moz-animation: spin 2s infinite linear; + -o-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; +} + +@-moz-keyframes spin { + 0% { -moz-transform: rotate(0deg); } + 100% { -moz-transform: rotate(359deg); } +} +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(359deg); } +} +@-o-keyframes spin { + 0% { -o-transform: rotate(0deg); } + 100% { -o-transform: rotate(359deg); } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/third_party/font-awesome/sass/font-awesome/_stacked.scss b/third_party/font-awesome/sass/font-awesome/_stacked.scss new file mode 100644 index 00000000..aef74036 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_stacked.scss @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.#{$fa-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.#{$fa-css-prefix}-stack-1x { line-height: inherit; } +.#{$fa-css-prefix}-stack-2x { font-size: 2em; } +.#{$fa-css-prefix}-inverse { color: $fa-inverse; } diff --git a/third_party/font-awesome/sass/font-awesome/_variables.scss b/third_party/font-awesome/sass/font-awesome/_variables.scss new file mode 100644 index 00000000..ac2b5051 --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/_variables.scss @@ -0,0 +1,515 @@ +// Variables +// -------------------------- + +$fa-font-path: "../fonts" !default; +//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts" !default; // for referencing Bootstrap CDN font files directly +$fa-css-prefix: fa !default; +$fa-version: "4.1.0" !default; +$fa-border-color: #eee !default; +$fa-inverse: #fff !default; +$fa-li-width: (30em / 14) !default; + +$fa-var-adjust: "\f042"; +$fa-var-adn: "\f170"; +$fa-var-align-center: "\f037"; +$fa-var-align-justify: "\f039"; +$fa-var-align-left: "\f036"; +$fa-var-align-right: "\f038"; +$fa-var-ambulance: "\f0f9"; +$fa-var-anchor: "\f13d"; +$fa-var-android: "\f17b"; +$fa-var-angle-double-down: "\f103"; +$fa-var-angle-double-left: "\f100"; +$fa-var-angle-double-right: "\f101"; +$fa-var-angle-double-up: "\f102"; +$fa-var-angle-down: "\f107"; +$fa-var-angle-left: "\f104"; +$fa-var-angle-right: "\f105"; +$fa-var-angle-up: "\f106"; +$fa-var-apple: "\f179"; +$fa-var-archive: "\f187"; +$fa-var-arrow-circle-down: "\f0ab"; +$fa-var-arrow-circle-left: "\f0a8"; +$fa-var-arrow-circle-o-down: "\f01a"; +$fa-var-arrow-circle-o-left: "\f190"; +$fa-var-arrow-circle-o-right: "\f18e"; +$fa-var-arrow-circle-o-up: "\f01b"; +$fa-var-arrow-circle-right: "\f0a9"; +$fa-var-arrow-circle-up: "\f0aa"; +$fa-var-arrow-down: "\f063"; +$fa-var-arrow-left: "\f060"; +$fa-var-arrow-right: "\f061"; +$fa-var-arrow-up: "\f062"; +$fa-var-arrows: "\f047"; +$fa-var-arrows-alt: "\f0b2"; +$fa-var-arrows-h: "\f07e"; +$fa-var-arrows-v: "\f07d"; +$fa-var-asterisk: "\f069"; +$fa-var-automobile: "\f1b9"; +$fa-var-backward: "\f04a"; +$fa-var-ban: "\f05e"; +$fa-var-bank: "\f19c"; +$fa-var-bar-chart-o: "\f080"; +$fa-var-barcode: "\f02a"; +$fa-var-bars: "\f0c9"; +$fa-var-beer: "\f0fc"; +$fa-var-behance: "\f1b4"; +$fa-var-behance-square: "\f1b5"; +$fa-var-bell: "\f0f3"; +$fa-var-bell-o: "\f0a2"; +$fa-var-bitbucket: "\f171"; +$fa-var-bitbucket-square: "\f172"; +$fa-var-bitcoin: "\f15a"; +$fa-var-bold: "\f032"; +$fa-var-bolt: "\f0e7"; +$fa-var-bomb: "\f1e2"; +$fa-var-book: "\f02d"; +$fa-var-bookmark: "\f02e"; +$fa-var-bookmark-o: "\f097"; +$fa-var-briefcase: "\f0b1"; +$fa-var-btc: "\f15a"; +$fa-var-bug: "\f188"; +$fa-var-building: "\f1ad"; +$fa-var-building-o: "\f0f7"; +$fa-var-bullhorn: "\f0a1"; +$fa-var-bullseye: "\f140"; +$fa-var-cab: "\f1ba"; +$fa-var-calendar: "\f073"; +$fa-var-calendar-o: "\f133"; +$fa-var-camera: "\f030"; +$fa-var-camera-retro: "\f083"; +$fa-var-car: "\f1b9"; +$fa-var-caret-down: "\f0d7"; +$fa-var-caret-left: "\f0d9"; +$fa-var-caret-right: "\f0da"; +$fa-var-caret-square-o-down: "\f150"; +$fa-var-caret-square-o-left: "\f191"; +$fa-var-caret-square-o-right: "\f152"; +$fa-var-caret-square-o-up: "\f151"; +$fa-var-caret-up: "\f0d8"; +$fa-var-certificate: "\f0a3"; +$fa-var-chain: "\f0c1"; +$fa-var-chain-broken: "\f127"; +$fa-var-check: "\f00c"; +$fa-var-check-circle: "\f058"; +$fa-var-check-circle-o: "\f05d"; +$fa-var-check-square: "\f14a"; +$fa-var-check-square-o: "\f046"; +$fa-var-chevron-circle-down: "\f13a"; +$fa-var-chevron-circle-left: "\f137"; +$fa-var-chevron-circle-right: "\f138"; +$fa-var-chevron-circle-up: "\f139"; +$fa-var-chevron-down: "\f078"; +$fa-var-chevron-left: "\f053"; +$fa-var-chevron-right: "\f054"; +$fa-var-chevron-up: "\f077"; +$fa-var-child: "\f1ae"; +$fa-var-circle: "\f111"; +$fa-var-circle-o: "\f10c"; +$fa-var-circle-o-notch: "\f1ce"; +$fa-var-circle-thin: "\f1db"; +$fa-var-clipboard: "\f0ea"; +$fa-var-clock-o: "\f017"; +$fa-var-cloud: "\f0c2"; +$fa-var-cloud-download: "\f0ed"; +$fa-var-cloud-upload: "\f0ee"; +$fa-var-cny: "\f157"; +$fa-var-code: "\f121"; +$fa-var-code-fork: "\f126"; +$fa-var-codepen: "\f1cb"; +$fa-var-coffee: "\f0f4"; +$fa-var-cog: "\f013"; +$fa-var-cogs: "\f085"; +$fa-var-columns: "\f0db"; +$fa-var-comment: "\f075"; +$fa-var-comment-o: "\f0e5"; +$fa-var-comments: "\f086"; +$fa-var-comments-o: "\f0e6"; +$fa-var-compass: "\f14e"; +$fa-var-compress: "\f066"; +$fa-var-copy: "\f0c5"; +$fa-var-credit-card: "\f09d"; +$fa-var-crop: "\f125"; +$fa-var-crosshairs: "\f05b"; +$fa-var-css3: "\f13c"; +$fa-var-cube: "\f1b2"; +$fa-var-cubes: "\f1b3"; +$fa-var-cut: "\f0c4"; +$fa-var-cutlery: "\f0f5"; +$fa-var-dashboard: "\f0e4"; +$fa-var-database: "\f1c0"; +$fa-var-dedent: "\f03b"; +$fa-var-delicious: "\f1a5"; +$fa-var-desktop: "\f108"; +$fa-var-deviantart: "\f1bd"; +$fa-var-digg: "\f1a6"; +$fa-var-dollar: "\f155"; +$fa-var-dot-circle-o: "\f192"; +$fa-var-download: "\f019"; +$fa-var-dribbble: "\f17d"; +$fa-var-dropbox: "\f16b"; +$fa-var-drupal: "\f1a9"; +$fa-var-edit: "\f044"; +$fa-var-eject: "\f052"; +$fa-var-ellipsis-h: "\f141"; +$fa-var-ellipsis-v: "\f142"; +$fa-var-empire: "\f1d1"; +$fa-var-envelope: "\f0e0"; +$fa-var-envelope-o: "\f003"; +$fa-var-envelope-square: "\f199"; +$fa-var-eraser: "\f12d"; +$fa-var-eur: "\f153"; +$fa-var-euro: "\f153"; +$fa-var-exchange: "\f0ec"; +$fa-var-exclamation: "\f12a"; +$fa-var-exclamation-circle: "\f06a"; +$fa-var-exclamation-triangle: "\f071"; +$fa-var-expand: "\f065"; +$fa-var-external-link: "\f08e"; +$fa-var-external-link-square: "\f14c"; +$fa-var-eye: "\f06e"; +$fa-var-eye-slash: "\f070"; +$fa-var-facebook: "\f09a"; +$fa-var-facebook-square: "\f082"; +$fa-var-fast-backward: "\f049"; +$fa-var-fast-forward: "\f050"; +$fa-var-fax: "\f1ac"; +$fa-var-female: "\f182"; +$fa-var-fighter-jet: "\f0fb"; +$fa-var-file: "\f15b"; +$fa-var-file-archive-o: "\f1c6"; +$fa-var-file-audio-o: "\f1c7"; +$fa-var-file-code-o: "\f1c9"; +$fa-var-file-excel-o: "\f1c3"; +$fa-var-file-image-o: "\f1c5"; +$fa-var-file-movie-o: "\f1c8"; +$fa-var-file-o: "\f016"; +$fa-var-file-pdf-o: "\f1c1"; +$fa-var-file-photo-o: "\f1c5"; +$fa-var-file-picture-o: "\f1c5"; +$fa-var-file-powerpoint-o: "\f1c4"; +$fa-var-file-sound-o: "\f1c7"; +$fa-var-file-text: "\f15c"; +$fa-var-file-text-o: "\f0f6"; +$fa-var-file-video-o: "\f1c8"; +$fa-var-file-word-o: "\f1c2"; +$fa-var-file-zip-o: "\f1c6"; +$fa-var-files-o: "\f0c5"; +$fa-var-film: "\f008"; +$fa-var-filter: "\f0b0"; +$fa-var-fire: "\f06d"; +$fa-var-fire-extinguisher: "\f134"; +$fa-var-flag: "\f024"; +$fa-var-flag-checkered: "\f11e"; +$fa-var-flag-o: "\f11d"; +$fa-var-flash: "\f0e7"; +$fa-var-flask: "\f0c3"; +$fa-var-flickr: "\f16e"; +$fa-var-floppy-o: "\f0c7"; +$fa-var-folder: "\f07b"; +$fa-var-folder-o: "\f114"; +$fa-var-folder-open: "\f07c"; +$fa-var-folder-open-o: "\f115"; +$fa-var-font: "\f031"; +$fa-var-forward: "\f04e"; +$fa-var-foursquare: "\f180"; +$fa-var-frown-o: "\f119"; +$fa-var-gamepad: "\f11b"; +$fa-var-gavel: "\f0e3"; +$fa-var-gbp: "\f154"; +$fa-var-ge: "\f1d1"; +$fa-var-gear: "\f013"; +$fa-var-gears: "\f085"; +$fa-var-gift: "\f06b"; +$fa-var-git: "\f1d3"; +$fa-var-git-square: "\f1d2"; +$fa-var-github: "\f09b"; +$fa-var-github-alt: "\f113"; +$fa-var-github-square: "\f092"; +$fa-var-gittip: "\f184"; +$fa-var-glass: "\f000"; +$fa-var-globe: "\f0ac"; +$fa-var-google: "\f1a0"; +$fa-var-google-plus: "\f0d5"; +$fa-var-google-plus-square: "\f0d4"; +$fa-var-graduation-cap: "\f19d"; +$fa-var-group: "\f0c0"; +$fa-var-h-square: "\f0fd"; +$fa-var-hacker-news: "\f1d4"; +$fa-var-hand-o-down: "\f0a7"; +$fa-var-hand-o-left: "\f0a5"; +$fa-var-hand-o-right: "\f0a4"; +$fa-var-hand-o-up: "\f0a6"; +$fa-var-hdd-o: "\f0a0"; +$fa-var-header: "\f1dc"; +$fa-var-headphones: "\f025"; +$fa-var-heart: "\f004"; +$fa-var-heart-o: "\f08a"; +$fa-var-history: "\f1da"; +$fa-var-home: "\f015"; +$fa-var-hospital-o: "\f0f8"; +$fa-var-html5: "\f13b"; +$fa-var-image: "\f03e"; +$fa-var-inbox: "\f01c"; +$fa-var-indent: "\f03c"; +$fa-var-info: "\f129"; +$fa-var-info-circle: "\f05a"; +$fa-var-inr: "\f156"; +$fa-var-instagram: "\f16d"; +$fa-var-institution: "\f19c"; +$fa-var-italic: "\f033"; +$fa-var-joomla: "\f1aa"; +$fa-var-jpy: "\f157"; +$fa-var-jsfiddle: "\f1cc"; +$fa-var-key: "\f084"; +$fa-var-keyboard-o: "\f11c"; +$fa-var-krw: "\f159"; +$fa-var-language: "\f1ab"; +$fa-var-laptop: "\f109"; +$fa-var-leaf: "\f06c"; +$fa-var-legal: "\f0e3"; +$fa-var-lemon-o: "\f094"; +$fa-var-level-down: "\f149"; +$fa-var-level-up: "\f148"; +$fa-var-life-bouy: "\f1cd"; +$fa-var-life-ring: "\f1cd"; +$fa-var-life-saver: "\f1cd"; +$fa-var-lightbulb-o: "\f0eb"; +$fa-var-link: "\f0c1"; +$fa-var-linkedin: "\f0e1"; +$fa-var-linkedin-square: "\f08c"; +$fa-var-linux: "\f17c"; +$fa-var-list: "\f03a"; +$fa-var-list-alt: "\f022"; +$fa-var-list-ol: "\f0cb"; +$fa-var-list-ul: "\f0ca"; +$fa-var-location-arrow: "\f124"; +$fa-var-lock: "\f023"; +$fa-var-long-arrow-down: "\f175"; +$fa-var-long-arrow-left: "\f177"; +$fa-var-long-arrow-right: "\f178"; +$fa-var-long-arrow-up: "\f176"; +$fa-var-magic: "\f0d0"; +$fa-var-magnet: "\f076"; +$fa-var-mail-forward: "\f064"; +$fa-var-mail-reply: "\f112"; +$fa-var-mail-reply-all: "\f122"; +$fa-var-male: "\f183"; +$fa-var-map-marker: "\f041"; +$fa-var-maxcdn: "\f136"; +$fa-var-medkit: "\f0fa"; +$fa-var-meh-o: "\f11a"; +$fa-var-microphone: "\f130"; +$fa-var-microphone-slash: "\f131"; +$fa-var-minus: "\f068"; +$fa-var-minus-circle: "\f056"; +$fa-var-minus-square: "\f146"; +$fa-var-minus-square-o: "\f147"; +$fa-var-mobile: "\f10b"; +$fa-var-mobile-phone: "\f10b"; +$fa-var-money: "\f0d6"; +$fa-var-moon-o: "\f186"; +$fa-var-mortar-board: "\f19d"; +$fa-var-music: "\f001"; +$fa-var-navicon: "\f0c9"; +$fa-var-openid: "\f19b"; +$fa-var-outdent: "\f03b"; +$fa-var-pagelines: "\f18c"; +$fa-var-paper-plane: "\f1d8"; +$fa-var-paper-plane-o: "\f1d9"; +$fa-var-paperclip: "\f0c6"; +$fa-var-paragraph: "\f1dd"; +$fa-var-paste: "\f0ea"; +$fa-var-pause: "\f04c"; +$fa-var-paw: "\f1b0"; +$fa-var-pencil: "\f040"; +$fa-var-pencil-square: "\f14b"; +$fa-var-pencil-square-o: "\f044"; +$fa-var-phone: "\f095"; +$fa-var-phone-square: "\f098"; +$fa-var-photo: "\f03e"; +$fa-var-picture-o: "\f03e"; +$fa-var-pied-piper: "\f1a7"; +$fa-var-pied-piper-alt: "\f1a8"; +$fa-var-pied-piper-square: "\f1a7"; +$fa-var-pinterest: "\f0d2"; +$fa-var-pinterest-square: "\f0d3"; +$fa-var-plane: "\f072"; +$fa-var-play: "\f04b"; +$fa-var-play-circle: "\f144"; +$fa-var-play-circle-o: "\f01d"; +$fa-var-plus: "\f067"; +$fa-var-plus-circle: "\f055"; +$fa-var-plus-square: "\f0fe"; +$fa-var-plus-square-o: "\f196"; +$fa-var-power-off: "\f011"; +$fa-var-print: "\f02f"; +$fa-var-puzzle-piece: "\f12e"; +$fa-var-qq: "\f1d6"; +$fa-var-qrcode: "\f029"; +$fa-var-question: "\f128"; +$fa-var-question-circle: "\f059"; +$fa-var-quote-left: "\f10d"; +$fa-var-quote-right: "\f10e"; +$fa-var-ra: "\f1d0"; +$fa-var-random: "\f074"; +$fa-var-rebel: "\f1d0"; +$fa-var-recycle: "\f1b8"; +$fa-var-reddit: "\f1a1"; +$fa-var-reddit-square: "\f1a2"; +$fa-var-refresh: "\f021"; +$fa-var-renren: "\f18b"; +$fa-var-reorder: "\f0c9"; +$fa-var-repeat: "\f01e"; +$fa-var-reply: "\f112"; +$fa-var-reply-all: "\f122"; +$fa-var-retweet: "\f079"; +$fa-var-rmb: "\f157"; +$fa-var-road: "\f018"; +$fa-var-rocket: "\f135"; +$fa-var-rotate-left: "\f0e2"; +$fa-var-rotate-right: "\f01e"; +$fa-var-rouble: "\f158"; +$fa-var-rss: "\f09e"; +$fa-var-rss-square: "\f143"; +$fa-var-rub: "\f158"; +$fa-var-ruble: "\f158"; +$fa-var-rupee: "\f156"; +$fa-var-save: "\f0c7"; +$fa-var-scissors: "\f0c4"; +$fa-var-search: "\f002"; +$fa-var-search-minus: "\f010"; +$fa-var-search-plus: "\f00e"; +$fa-var-send: "\f1d8"; +$fa-var-send-o: "\f1d9"; +$fa-var-share: "\f064"; +$fa-var-share-alt: "\f1e0"; +$fa-var-share-alt-square: "\f1e1"; +$fa-var-share-square: "\f14d"; +$fa-var-share-square-o: "\f045"; +$fa-var-shield: "\f132"; +$fa-var-shopping-cart: "\f07a"; +$fa-var-sign-in: "\f090"; +$fa-var-sign-out: "\f08b"; +$fa-var-signal: "\f012"; +$fa-var-sitemap: "\f0e8"; +$fa-var-skype: "\f17e"; +$fa-var-slack: "\f198"; +$fa-var-sliders: "\f1de"; +$fa-var-smile-o: "\f118"; +$fa-var-sort: "\f0dc"; +$fa-var-sort-alpha-asc: "\f15d"; +$fa-var-sort-alpha-desc: "\f15e"; +$fa-var-sort-amount-asc: "\f160"; +$fa-var-sort-amount-desc: "\f161"; +$fa-var-sort-asc: "\f0de"; +$fa-var-sort-desc: "\f0dd"; +$fa-var-sort-down: "\f0dd"; +$fa-var-sort-numeric-asc: "\f162"; +$fa-var-sort-numeric-desc: "\f163"; +$fa-var-sort-up: "\f0de"; +$fa-var-soundcloud: "\f1be"; +$fa-var-space-shuttle: "\f197"; +$fa-var-spinner: "\f110"; +$fa-var-spoon: "\f1b1"; +$fa-var-spotify: "\f1bc"; +$fa-var-square: "\f0c8"; +$fa-var-square-o: "\f096"; +$fa-var-stack-exchange: "\f18d"; +$fa-var-stack-overflow: "\f16c"; +$fa-var-star: "\f005"; +$fa-var-star-half: "\f089"; +$fa-var-star-half-empty: "\f123"; +$fa-var-star-half-full: "\f123"; +$fa-var-star-half-o: "\f123"; +$fa-var-star-o: "\f006"; +$fa-var-steam: "\f1b6"; +$fa-var-steam-square: "\f1b7"; +$fa-var-step-backward: "\f048"; +$fa-var-step-forward: "\f051"; +$fa-var-stethoscope: "\f0f1"; +$fa-var-stop: "\f04d"; +$fa-var-strikethrough: "\f0cc"; +$fa-var-stumbleupon: "\f1a4"; +$fa-var-stumbleupon-circle: "\f1a3"; +$fa-var-subscript: "\f12c"; +$fa-var-suitcase: "\f0f2"; +$fa-var-sun-o: "\f185"; +$fa-var-superscript: "\f12b"; +$fa-var-support: "\f1cd"; +$fa-var-table: "\f0ce"; +$fa-var-tablet: "\f10a"; +$fa-var-tachometer: "\f0e4"; +$fa-var-tag: "\f02b"; +$fa-var-tags: "\f02c"; +$fa-var-tasks: "\f0ae"; +$fa-var-taxi: "\f1ba"; +$fa-var-tencent-weibo: "\f1d5"; +$fa-var-terminal: "\f120"; +$fa-var-text-height: "\f034"; +$fa-var-text-width: "\f035"; +$fa-var-th: "\f00a"; +$fa-var-th-large: "\f009"; +$fa-var-th-list: "\f00b"; +$fa-var-thumb-tack: "\f08d"; +$fa-var-thumbs-down: "\f165"; +$fa-var-thumbs-o-down: "\f088"; +$fa-var-thumbs-o-up: "\f087"; +$fa-var-thumbs-up: "\f164"; +$fa-var-ticket: "\f145"; +$fa-var-times: "\f00d"; +$fa-var-times-circle: "\f057"; +$fa-var-times-circle-o: "\f05c"; +$fa-var-tint: "\f043"; +$fa-var-toggle-down: "\f150"; +$fa-var-toggle-left: "\f191"; +$fa-var-toggle-right: "\f152"; +$fa-var-toggle-up: "\f151"; +$fa-var-trash-o: "\f014"; +$fa-var-tree: "\f1bb"; +$fa-var-trello: "\f181"; +$fa-var-trophy: "\f091"; +$fa-var-truck: "\f0d1"; +$fa-var-try: "\f195"; +$fa-var-tumblr: "\f173"; +$fa-var-tumblr-square: "\f174"; +$fa-var-turkish-lira: "\f195"; +$fa-var-twitter: "\f099"; +$fa-var-twitter-square: "\f081"; +$fa-var-umbrella: "\f0e9"; +$fa-var-underline: "\f0cd"; +$fa-var-undo: "\f0e2"; +$fa-var-university: "\f19c"; +$fa-var-unlink: "\f127"; +$fa-var-unlock: "\f09c"; +$fa-var-unlock-alt: "\f13e"; +$fa-var-unsorted: "\f0dc"; +$fa-var-upload: "\f093"; +$fa-var-usd: "\f155"; +$fa-var-user: "\f007"; +$fa-var-user-md: "\f0f0"; +$fa-var-users: "\f0c0"; +$fa-var-video-camera: "\f03d"; +$fa-var-vimeo-square: "\f194"; +$fa-var-vine: "\f1ca"; +$fa-var-vk: "\f189"; +$fa-var-volume-down: "\f027"; +$fa-var-volume-off: "\f026"; +$fa-var-volume-up: "\f028"; +$fa-var-warning: "\f071"; +$fa-var-wechat: "\f1d7"; +$fa-var-weibo: "\f18a"; +$fa-var-weixin: "\f1d7"; +$fa-var-wheelchair: "\f193"; +$fa-var-windows: "\f17a"; +$fa-var-won: "\f159"; +$fa-var-wordpress: "\f19a"; +$fa-var-wrench: "\f0ad"; +$fa-var-xing: "\f168"; +$fa-var-xing-square: "\f169"; +$fa-var-yahoo: "\f19e"; +$fa-var-yen: "\f157"; +$fa-var-youtube: "\f167"; +$fa-var-youtube-play: "\f16a"; +$fa-var-youtube-square: "\f166"; + diff --git a/third_party/font-awesome/sass/font-awesome/font-awesome.scss b/third_party/font-awesome/sass/font-awesome/font-awesome.scss new file mode 100644 index 00000000..2307dbda --- /dev/null +++ b/third_party/font-awesome/sass/font-awesome/font-awesome.scss @@ -0,0 +1,17 @@ +/*! + * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables"; +@import "mixins"; +@import "path"; +@import "core"; +@import "larger"; +@import "fixed-width"; +@import "list"; +@import "bordered-pulled"; +@import "spinning"; +@import "rotated-flipped"; +@import "stacked"; +@import "icons"; diff --git a/third_party/handlebars/LICENSE b/third_party/handlebars/LICENSE new file mode 100644 index 00000000..e90e0596 --- /dev/null +++ b/third_party/handlebars/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011-2014 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/third_party/handlebars/handlebars.runtime-v2.0.0.js b/third_party/handlebars/handlebars.runtime-v2.0.0.js new file mode 100644 index 00000000..f65a01eb --- /dev/null +++ b/third_party/handlebars/handlebars.runtime-v2.0.0.js @@ -0,0 +1,27 @@ +/**! + + @license + handlebars v4.0.5 + +Copyright (C) 2011-2016 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +!function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.Handlebars=b():a.Handlebars=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){"use strict";function d(a){return a&&a.__esModule?a:{"default":a}}function e(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b["default"]=a,b}function f(){var a=new h.HandlebarsEnvironment;return n.extend(a,h),a.SafeString=j["default"],a.Exception=l["default"],a.Utils=n,a.escapeExpression=n.escapeExpression,a.VM=p,a.template=function(b){return p.template(b,a)},a}b.__esModule=!0;var g=c(1),h=e(g),i=c(15),j=d(i),k=c(3),l=d(k),m=c(2),n=e(m),o=c(16),p=e(o),q=c(17),r=d(q),s=f();s.create=f,r["default"](s),s["default"]=s,b["default"]=s,a.exports=b["default"]},function(a,b,c){"use strict";function d(a){return a&&a.__esModule?a:{"default":a}}function e(a,b,c){this.helpers=a||{},this.partials=b||{},this.decorators=c||{},i.registerDefaultHelpers(this),j.registerDefaultDecorators(this)}b.__esModule=!0,b.HandlebarsEnvironment=e;var f=c(2),g=c(3),h=d(g),i=c(4),j=c(12),k=c(14),l=d(k),m="4.0.5";b.VERSION=m;var n=7;b.COMPILER_REVISION=n;var o={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:"== 1.x.x",5:"== 2.0.0-alpha.x",6:">= 2.0.0-beta.1",7:">= 4.0.0"};b.REVISION_CHANGES=o;var p="[object Object]";e.prototype={constructor:e,logger:l["default"],log:l["default"].log,registerHelper:function(a,b){if(f.toString.call(a)===p){if(b)throw new h["default"]("Arg not supported with multiple helpers");f.extend(this.helpers,a)}else this.helpers[a]=b},unregisterHelper:function(a){delete this.helpers[a]},registerPartial:function(a,b){if(f.toString.call(a)===p)f.extend(this.partials,a);else{if("undefined"==typeof b)throw new h["default"]('Attempting to register a partial called "'+a+'" as undefined');this.partials[a]=b}},unregisterPartial:function(a){delete this.partials[a]},registerDecorator:function(a,b){if(f.toString.call(a)===p){if(b)throw new h["default"]("Arg not supported with multiple decorators");f.extend(this.decorators,a)}else this.decorators[a]=b},unregisterDecorator:function(a){delete this.decorators[a]}};var q=l["default"].log;b.log=q,b.createFrame=f.createFrame,b.logger=l["default"]},function(a,b){"use strict";function c(a){return i[a]}function d(a){for(var b=1;bc;c++)if(a[c]===b)return c;return-1}function f(a){if("string"!=typeof a){if(a&&a.toHTML)return a.toHTML();if(null==a)return"";if(!a)return a+"";a=""+a}return k.test(a)?a.replace(j,c):a}function g(a){return a||0===a?!(!n(a)||0!==a.length):!0}function h(a){var b=d({},a);return b._parent=a,b}b.__esModule=!0,b.extend=d,b.indexOf=e,b.escapeExpression=f,b.isEmpty=g,b.createFrame=h;var i={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"="},j=/[&<>"'`=]/g,k=/[&<>"'`=]/,l=Object.prototype.toString;b.toString=l;var m=function(a){return"function"==typeof a};m(/x/)&&(b.isFunction=m=function(a){return"function"==typeof a&&"[object Function]"===l.call(a)}),b.isFunction=m;var n=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===l.call(a):!1};b.isArray=n},function(a,b){"use strict";function c(a,b){var e=b&&b.loc,f=void 0,g=void 0;e&&(f=e.start.line,g=e.start.column,a+=" - "+f+":"+g);for(var h=Error.prototype.constructor.call(this,a),i=0;i0?a.helpers.each(b,c):e(this):f(b,c)})},a.exports=b["default"]},function(a,b,c){"use strict";function d(a){return a&&a.__esModule?a:{"default":a}}b.__esModule=!0;var e=c(2),f=c(3),g=d(f);b["default"]=function(a){a.registerHelper("each",function(a,b){function c(b,c,e){j&&(j.key=b,j.index=c,j.first=0===c,j.last=!!e),i+=d(a[b],{data:j,blockParams:[a[b],b]})}if(!b)throw new g["default"]("Must pass iterator to #each");var d=b.fn,f=b.inverse,h=0,i="",j=void 0;if(e.isFunction(a)&&(a=a.call(this)),b.data&&(j=e.createFrame(b.data)),a&&"object"==typeof a)if(e.isArray(a))for(var k=a.length;k>h;h++)h in a&&c(h,h,h===a.length-1);else{var l=void 0;for(var m in a)a.hasOwnProperty(m)&&(void 0!==l&&c(l,h-1),l=m,h++);void 0!==l&&c(l,h-1,!0)}return 0===h&&(i=f(this)),i})},a.exports=b["default"]},function(a,b,c){"use strict";function d(a){return a&&a.__esModule?a:{"default":a}}b.__esModule=!0;var e=c(3),f=d(e);b["default"]=function(a){a.registerHelper("helperMissing",function(){if(1!==arguments.length)throw new f["default"]('Missing helper: "'+arguments[arguments.length-1].name+'"')})},a.exports=b["default"]},function(a,b,c){"use strict";b.__esModule=!0;var d=c(2);b["default"]=function(a){a.registerHelper("if",function(a,b){return d.isFunction(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||d.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})})},a.exports=b["default"]},function(a,b){"use strict";b.__esModule=!0,b["default"]=function(a){a.registerHelper("log",function(){for(var b=[void 0],c=arguments[arguments.length-1],d=0;d=0?b:parseInt(a,10)}return a},log:function(a){if(a=e.lookupLevel(a),"undefined"!=typeof console&&e.lookupLevel(e.level)<=a){var b=e.methodMap[a];console[b]||(b="log");for(var c=arguments.length,d=Array(c>1?c-1:0),f=1;c>f;f++)d[f-1]=arguments[f];console[b].apply(console,d)}}};b["default"]=e,a.exports=b["default"]},function(a,b){"use strict";function c(a){this.string=a}b.__esModule=!0,c.prototype.toString=c.prototype.toHTML=function(){return""+this.string},b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(a){return a&&a.__esModule?a:{"default":a}}function e(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b["default"]=a,b}function f(a){var b=a&&a[0]||1,c=r.COMPILER_REVISION;if(b!==c){if(c>b){var d=r.REVISION_CHANGES[c],e=r.REVISION_CHANGES[b];throw new q["default"]("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+d+") or downgrade your runtime to an older version ("+e+").")}throw new q["default"]("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function g(a,b){function c(c,d,e){e.hash&&(d=o.extend({},d,e.hash)),c=b.VM.resolvePartial.call(this,c,d,e);var f=b.VM.invokePartial.call(this,c,d,e);if(null==f&&b.compile&&(e.partials[e.name]=b.compile(c,a.compilerOptions,b),f=e.partials[e.name](d,e)),null!=f){if(e.indent){for(var g=f.split("\n"),h=0,i=g.length;i>h&&(g[h]||h+1!==i);h++)g[h]=e.indent+g[h];f=g.join("\n")}return f}throw new q["default"]("The partial "+e.name+" could not be compiled when running in runtime-only mode")}function d(b){function c(b){return""+a.main(f,b,f.helpers,f.partials,g,i,h)}var d=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],g=d.data;e(d),!d.partial&&a.useData&&(g=l(b,g));var h=void 0,i=a.useBlockParams?[]:void 0;return a.useDepths&&(h=d.depths?b!=d.depths[0]?[b].concat(d.depths):d.depths:[b]),(c=m(a.main,c,f,d.depths||[],g,i))(b,d)}function e(c){c.partial?(f.helpers=c.helpers,f.partials=c.partials,f.decorators=c.decorators):(f.helpers=f.merge(c.helpers,b.helpers),a.usePartial&&(f.partials=f.merge(c.partials,b.partials)),(a.usePartial||a.useDecorators)&&(f.decorators=f.merge(c.decorators,b.decorators)))}if(!b)throw new q["default"]("No environment passed to template");if(!a||!a.main)throw new q["default"]("Unknown template object: "+typeof a);a.main.decorator=a.main_d,b.VM.checkRevision(a.compiler);var f={strict:function(a,b){if(!(b in a))throw new q["default"]('"'+b+'" not defined in '+a);return a[b]},lookup:function(a,b){for(var c=a.length,d=0;c>d;d++)if(a[d]&&null!=a[d][b])return a[d][b]},lambda:function(a,b){return"function"==typeof a?a.call(b):a},escapeExpression:o.escapeExpression,invokePartial:c,fn:function(b){var c=a[b];return c.decorator=a[b+"_d"],c},programs:[],program:function(a,b,c,d,e){var f=this.programs[a],g=this.fn(a);return b||e||d||c?f=h(this,a,g,b,c,d,e):f||(f=this.programs[a]=h(this,a,g)),f},data:function(a,b){for(;a&&b--;)a=a._parent;return a},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c=o.extend({},b,a)),c},noop:b.VM.noop,compilerInfo:a.compiler};return d.isTop=!0,d}function h(a,b,c,d,e,f,g){function h(b){var e=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],h=g;return g&&b!=g[0]&&(h=[b].concat(g)),c(a,b,a.helpers,a.partials,e.data||d,f&&[e.blockParams].concat(f),h)}return h=m(c,h,a,g,d,f),h.program=b,h.depth=g?g.length:0,h.blockParams=e||0,h}function i(a,b,c){return a?a.call||c.name||(c.name=a,a=c.partials[a]):a="@partial-block"===c.name?c.data["partial-block"]:c.partials[c.name],a}function j(a,b,c){c.partial=!0;var d=void 0;if(c.fn&&c.fn!==k&&(c.data=r.createFrame(c.data),d=c.data["partial-block"]=c.fn,d.partials&&(c.partials=o.extend({},c.partials,d.partials))),void 0===a&&d&&(a=d),void 0===a)throw new q["default"]("The partial "+c.name+" could not be found");return a instanceof Function?a(b,c):void 0}function k(){return""}function l(a,b){return b&&"root"in b||(b=b?r.createFrame(b):{},b.root=a),b}function m(a,b,c,d,e,f){if(a.decorator){var g={};b=a.decorator(b,g,c,d&&d[0],e,f,d),o.extend(b,g)}return b}b.__esModule=!0,b.checkRevision=f,b.template=g,b.wrapProgram=h,b.resolvePartial=i,b.invokePartial=j,b.noop=k;var n=c(2),o=e(n),p=c(3),q=d(p),r=c(1)},function(a,b){(function(c){"use strict";b.__esModule=!0,b["default"]=function(a){var b="undefined"!=typeof c?c:window,d=b.Handlebars;a.noConflict=function(){return b.Handlebars===a&&(b.Handlebars=d),a}},a.exports=b["default"]}).call(b,function(){return this}())}])}); \ No newline at end of file diff --git a/third_party/hubspot/LICENSE b/third_party/hubspot/LICENSE new file mode 100644 index 00000000..61eed257 --- /dev/null +++ b/third_party/hubspot/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2013 HubSpot, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/hubspot/select-theme-default.css b/third_party/hubspot/select-theme-default.css new file mode 100755 index 00000000..5fc824f9 --- /dev/null +++ b/third_party/hubspot/select-theme-default.css @@ -0,0 +1,155 @@ +.select-select { + display: none; + /* For when we are on a small touch device and want to use native controls */ + -webkit-pointer-events: none; + -moz-pointer-events: none; + pointer-events: none; + position: absolute; + opacity: 0; } + +.select-element, .select-element:after, .select-element:before, .select-element *, .select-element *:after, .select-element *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.select-element { + position: absolute; + display: none; } + .select-element.select-open { + display: block; } + +.select-theme-default, .select-theme-default *, .select-theme-default *:after, .select-theme-default *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.select.select-theme-default { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; } + .select.select-theme-default .select-content { + -webkit-border-radius: 0.25em; + -moz-border-radius: 0.25em; + -ms-border-radius: 0.25em; + -o-border-radius: 0.25em; + border-radius: 0.25em; + -webkit-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + background: white; + font-family: inherit; + color: inherit; + overflow: auto; + max-width: 18rem; + max-height: 18rem; + -webkit-overflow-scrolling: touch; } + @media (max-width: 27rem), (max-height: 27rem) { + .select.select-theme-default .select-content { + max-width: 11.25rem; + max-height: 11.25rem; } } + .select.select-theme-default .select-options { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + margin: 0; + padding: 0; } + .select.select-theme-default .select-options .select-option { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + position: relative; + list-style: none; + margin: 0; + line-height: 1.25rem; + padding: 0.5rem 1em 0.5rem 2.5em; + display: block; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } + .select.select-theme-default .select-options .select-option.select-option-selected:before { + content: url("data:image/svg+xml;utf8,"); + position: absolute; + left: 1em; + top: 0; + bottom: 0.2em; + height: 1em; + width: 1em; + margin: auto; } + .select.select-theme-default .select-options .select-option:hover, .select.select-theme-default .select-options .select-option.select-option-highlight { + background: #63a2f1; + color: white; } + .select.select-theme-default .select-options .select-option:hover.select-option-selected:before, .select.select-theme-default .select-options .select-option.select-option-highlight.select-option-selected:before { + content: url("data:image/svg+xml;utf8,"); } + .select.select-theme-default .select-options .select-option:first-child { + -webkit-border-radius: 0.25em 0.25em 0 0; + -moz-border-radius: 0.25em 0.25em 0 0; + -ms-border-radius: 0.25em 0.25em 0 0; + -o-border-radius: 0.25em 0.25em 0 0; + border-radius: 0.25em 0.25em 0 0; } + .select.select-theme-default .select-options .select-option:last-child { + -webkit-border-radius: 0 0 0.25em 0.25em; + -moz-border-radius: 0 0 0.25em 0.25em; + -ms-border-radius: 0 0 0.25em 0.25em; + -o-border-radius: 0 0 0.25em 0.25em; + border-radius: 0 0 0.25em 0.25em; } + +.select-target.select-theme-default { + display: -moz-inline-stack; + display: inline-block; + vertical-align: middle; + *vertical-align: auto; + zoom: 1; + *display: inline; + -webkit-border-radius: 0.25em; + -moz-border-radius: 0.25em; + -ms-border-radius: 0.25em; + -o-border-radius: 0.25em; + border-radius: 0.25em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + position: relative; + padding: 0.5rem 3em 0.5rem 1em; + background: #f6f6f6; + border: 0.18em solid #dddddd; + cursor: pointer; + color: #444444; + text-decoration: none; + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; } + .select-target.select-theme-default:hover { + border-color: #aaaaaa; + color: black; } + .select-target.select-theme-default.select-target-focused, .select-target.select-theme-default.select-target-focused:focus { + border-color: #63a2f1; + outline: none; } + .select-target.select-theme-default b { + position: absolute; + right: 1em; + top: 0; + bottom: 0; + margin: auto; + height: 1.25rem; + width: 2em; } + .select-target.select-theme-default b:before, .select-target.select-theme-default b:after { + content: ""; + display: block; + position: absolute; + margin: auto; + right: 0; + height: 0; + width: 0; + border: 0.263em solid transparent; } + .select-target.select-theme-default b:before { + top: 0; + border-bottom-color: inherit; } + .select-target.select-theme-default b:after { + bottom: 0; + border-top-color: inherit; } diff --git a/third_party/hubspot/select.js b/third_party/hubspot/select.js new file mode 100755 index 00000000..3a30ea5f --- /dev/null +++ b/third_party/hubspot/select.js @@ -0,0 +1,511 @@ +(function() { + var DOWN, ENTER, ESCAPE, Evented, SPACE, Select, UP, addClass, clickEvent, extend, getBounds, getFocusedSelect, hasClass, isRepeatedChar, lastCharacter, removeClass, searchText, searchTextTimeout, touchDevice, useNative, _ref, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + _ref = Tether.Utils, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, hasClass = _ref.hasClass, getBounds = _ref.getBounds, Evented = _ref.Evented; + + ENTER = 13; + + ESCAPE = 27; + + SPACE = 32; + + UP = 38; + + DOWN = 40; + + touchDevice = 'ontouchstart' in document.documentElement; + + clickEvent = touchDevice ? 'touchstart' : 'click'; + + useNative = function() { + return touchDevice && (innerWidth <= 640 || innerHeight <= 640); + }; + + isRepeatedChar = function(str) { + return Array.prototype.reduce.call(str, function(a, b) { + if (a === b) { + return b; + } else { + return false; + } + }); + }; + + getFocusedSelect = function() { + var _ref1; + return (_ref1 = document.querySelector('.select-target-focused')) != null ? _ref1.selectInstance : void 0; + }; + + searchText = ''; + + searchTextTimeout = void 0; + + lastCharacter = void 0; + + document.addEventListener('keypress', function(e) { + var options, repeatedOptions, select, selected; + if (!(select = getFocusedSelect())) { + return; + } + if (e.charCode === 0) { + return; + } + if (e.keyCode === SPACE) { + e.preventDefault(); + } + clearTimeout(searchTextTimeout); + searchTextTimeout = setTimeout(function() { + return searchText = ''; + }, 500); + searchText += String.fromCharCode(e.charCode); + options = select.findOptionsByPrefix(searchText); + if (options.length === 1) { + select.selectOption(options[0]); + return; + } + if (searchText.length > 1 && isRepeatedChar(searchText)) { + repeatedOptions = select.findOptionsByPrefix(searchText[0]); + if (repeatedOptions.length) { + selected = repeatedOptions.indexOf(select.getChosen()); + selected += 1; + selected = selected % repeatedOptions.length; + select.selectOption(repeatedOptions[selected]); + return; + } + } + if (options.length) { + select.selectOption(options[0]); + } + }); + + document.addEventListener('keydown', function(e) { + var select, _ref1, _ref2; + if (!(select = getFocusedSelect())) { + return; + } + if ((_ref1 = e.keyCode) === UP || _ref1 === DOWN || _ref1 === ESCAPE) { + e.preventDefault(); + } + if (select.isOpen()) { + switch (e.keyCode) { + case UP: + case DOWN: + return select.moveHighlight(e.keyCode); + case ENTER: + return select.selectHighlightedOption(); + case ESCAPE: + select.close(); + return select.target.focus(); + } + } else { + if ((_ref2 = e.keyCode) === UP || _ref2 === DOWN || _ref2 === SPACE) { + return select.open(); + } + } + }); + + Select = (function(_super) { + __extends(Select, _super); + + Select.defaults = { + alignToHighlighed: 'auto', + className: 'select-theme-default' + }; + + function Select(options) { + this.options = options; + this.update = __bind(this.update, this); + this.options = extend({}, Select.defaults, this.options); + this.select = this.options.el; + if (this.select.selectInstance != null) { + throw new Error("This element has already been turned into a Select"); + } + this.setupTarget(); + this.renderTarget(); + this.setupDrop(); + this.renderDrop(); + this.setupSelect(); + this.setupTether(); + this.bindClick(); + this.bindMutationEvents(); + this.value = this.select.value; + } + + Select.prototype.useNative = function() { + return this.options.useNative === true || (useNative() && this.options.useNative !== false); + }; + + Select.prototype.setupTarget = function() { + var tabIndex, + _this = this; + this.target = document.createElement('a'); + this.target.href = 'javascript:;'; + addClass(this.target, 'select-target'); + tabIndex = this.select.getAttribute('tabindex') || 0; + this.target.setAttribute('tabindex', tabIndex); + if (this.options.className) { + addClass(this.target, this.options.className); + } + this.target.selectInstance = this; + this.target.addEventListener('click', function() { + if (!_this.isOpen()) { + return _this.target.focus(); + } else { + return _this.target.blur(); + } + }); + this.target.addEventListener('focus', function() { + return addClass(_this.target, 'select-target-focused'); + }); + this.target.addEventListener('blur', function(e) { + if (_this.isOpen()) { + if (e.relatedTarget && !_this.drop.contains(e.relatedTarget)) { + _this.close(); + } + } + return removeClass(_this.target, 'select-target-focused'); + }); + return this.select.parentNode.insertBefore(this.target, this.select.nextSibling); + }; + + Select.prototype.setupDrop = function() { + var _this = this; + this.drop = document.createElement('div'); + addClass(this.drop, 'select'); + if (this.options.className) { + addClass(this.drop, this.options.className); + } + document.body.appendChild(this.drop); + this.drop.addEventListener('click', function(e) { + if (hasClass(e.target, 'select-option')) { + _this.pickOption(e.target); + } + return e.stopPropagation(); + }); + this.drop.addEventListener('mousemove', function(e) { + if (hasClass(e.target, 'select-option')) { + return _this.highlightOption(e.target); + } + }); + this.content = document.createElement('div'); + addClass(this.content, 'select-content'); + return this.drop.appendChild(this.content); + }; + + Select.prototype.open = function() { + var positionSelectStyle, selectedOption, + _this = this; + addClass(this.target, 'select-open'); + if (this.useNative()) { + this.select.style.display = 'block'; + setTimeout(function() { + var event; + event = document.createEvent("MouseEvents"); + event.initEvent("mousedown", true, true); + return _this.select.dispatchEvent(event); + }); + return; + } + addClass(this.drop, 'select-open'); + setTimeout(function() { + return _this.tether.enable(); + }); + selectedOption = this.drop.querySelector('.select-option-selected'); + if (!selectedOption) { + return; + } + this.highlightOption(selectedOption); + this.scrollDropContentToOption(selectedOption); + positionSelectStyle = function() { + var dropBounds, offset, optionBounds; + if (hasClass(_this.drop, 'tether-abutted-left') || hasClass(_this.drop, 'tether-abutted-bottom')) { + dropBounds = getBounds(_this.drop); + optionBounds = getBounds(selectedOption); + offset = dropBounds.top - (optionBounds.top + optionBounds.height); + return _this.drop.style.top = (parseFloat(_this.drop.style.top) || 0) + offset + 'px'; + } + }; + if (this.options.alignToHighlighted === 'always' || (this.options.alignToHighlighted === 'auto' && this.content.scrollHeight <= this.content.clientHeight)) { + setTimeout(positionSelectStyle); + } + return this.trigger('open'); + }; + + Select.prototype.close = function() { + removeClass(this.target, 'select-open'); + if (this.useNative()) { + this.select.style.display = 'none'; + return; + } + this.tether.disable(); + removeClass(this.drop, 'select-open'); + return this.trigger('close'); + }; + + Select.prototype.toggle = function() { + if (this.isOpen()) { + return this.close(); + } else { + return this.open(); + } + }; + + Select.prototype.isOpen = function() { + return hasClass(this.drop, 'select-open'); + }; + + Select.prototype.bindClick = function() { + var _this = this; + this.target.addEventListener(clickEvent, function(e) { + e.preventDefault(); + return _this.toggle(); + }); + return document.addEventListener(clickEvent, function(event) { + if (!_this.isOpen()) { + return; + } + if (event.target === _this.drop || _this.drop.contains(event.target)) { + return; + } + if (event.target === _this.target || _this.target.contains(event.target)) { + return; + } + return _this.close(); + }); + }; + + Select.prototype.setupTether = function() { + return this.tether = new Tether(extend({ + element: this.drop, + target: this.target, + attachment: 'top left', + targetAttachment: 'bottom left', + classPrefix: 'select', + constraints: [ + { + to: 'window', + attachment: 'together' + } + ] + }, this.options.tetherOptions)); + }; + + Select.prototype.renderTarget = function() { + var option, _i, _len, _ref1; + this.target.innerHTML = ''; + _ref1 = this.select.querySelectorAll('option'); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + option = _ref1[_i]; + if (option.selected) { + this.target.innerHTML = option.innerHTML; + break; + } + } + return this.target.appendChild(document.createElement('b')); + }; + + Select.prototype.renderDrop = function() { + var el, option, optionList, _i, _len, _ref1; + optionList = document.createElement('ul'); + addClass(optionList, 'select-options'); + _ref1 = this.select.querySelectorAll('option'); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + el = _ref1[_i]; + option = document.createElement('li'); + addClass(option, 'select-option'); + option.setAttribute('data-value', el.value); + option.innerHTML = el.innerHTML; + if (el.selected) { + addClass(option, 'select-option-selected'); + } + optionList.appendChild(option); + } + this.content.innerHTML = ''; + return this.content.appendChild(optionList); + }; + + Select.prototype.update = function() { + this.renderDrop(); + return this.renderTarget(); + }; + + Select.prototype.setupSelect = function() { + this.select.selectInstance = this; + addClass(this.select, 'select-select'); + return this.select.addEventListener('change', this.update); + }; + + Select.prototype.bindMutationEvents = function() { + if (window.MutationObserver != null) { + this.observer = new MutationObserver(this.update); + return this.observer.observe(this.select, { + childList: true, + attributes: true, + characterData: true, + subtree: true + }); + } else { + return this.select.addEventListener('DOMSubtreeModified', this.update); + } + }; + + Select.prototype.findOptionsByPrefix = function(text) { + var options; + options = this.drop.querySelectorAll('.select-option'); + text = text.toLowerCase(); + return Array.prototype.filter.call(options, function(option) { + return option.innerHTML.toLowerCase().substr(0, text.length) === text; + }); + }; + + Select.prototype.findOptionsByValue = function(val) { + var options; + options = this.drop.querySelectorAll('.select-option'); + return Array.prototype.filter.call(options, function(option) { + return option.getAttribute('data-value') === val; + }); + }; + + Select.prototype.getChosen = function() { + if (this.isOpen()) { + return this.drop.querySelector('.select-option-highlight'); + } else { + return this.drop.querySelector('.select-option-selected'); + } + }; + + Select.prototype.selectOption = function(option) { + if (this.isOpen()) { + this.highlightOption(option); + return this.scrollDropContentToOption(option); + } else { + return this.pickOption(option, false); + } + }; + + Select.prototype.resetSelection = function() { + return this.selectOption(this.drop.querySelector('.select-option')); + }; + + Select.prototype.highlightOption = function(option) { + var highlighted; + highlighted = this.drop.querySelector('.select-option-highlight'); + if (highlighted != null) { + removeClass(highlighted, 'select-option-highlight'); + } + addClass(option, 'select-option-highlight'); + return this.trigger('highlight', { + option: option + }); + }; + + Select.prototype.moveHighlight = function(directionKeyCode) { + var highlighted, highlightedIndex, newHighlight, options; + if (!(highlighted = this.drop.querySelector('.select-option-highlight'))) { + this.highlightOption(this.drop.querySelector('.select-option')); + return; + } + options = this.drop.querySelectorAll('.select-option'); + highlightedIndex = Array.prototype.indexOf.call(options, highlighted); + if (!(highlightedIndex >= 0)) { + return; + } + if (directionKeyCode === UP) { + highlightedIndex -= 1; + } else { + highlightedIndex += 1; + } + if (highlightedIndex < 0 || highlightedIndex >= options.length) { + return; + } + newHighlight = options[highlightedIndex]; + this.highlightOption(newHighlight); + return this.scrollDropContentToOption(newHighlight); + }; + + Select.prototype.scrollDropContentToOption = function(option) { + var contentBounds, optionBounds; + if (this.content.scrollHeight > this.content.clientHeight) { + contentBounds = getBounds(this.content); + optionBounds = getBounds(option); + return this.content.scrollTop = optionBounds.top - (contentBounds.top - this.content.scrollTop); + } + }; + + Select.prototype.selectHighlightedOption = function() { + return this.pickOption(this.drop.querySelector('.select-option-highlight')); + }; + + Select.prototype.pickOption = function(option, close) { + var _this = this; + if (close == null) { + close = true; + } + this.value = this.select.value = option.getAttribute('data-value'); + this.triggerChange(); + if (close) { + return setTimeout(function() { + _this.close(); + return _this.target.focus(); + }); + } + }; + + Select.prototype.triggerChange = function() { + var event; + event = document.createEvent("HTMLEvents"); + event.initEvent("change", true, false); + this.select.dispatchEvent(event); + return this.trigger('change', { + value: this.select.value + }); + }; + + Select.prototype.change = function(val) { + var options; + options = this.findOptionsByValue(val); + if (!options.length) { + throw new Error("Select Error: An option with the value \"" + val + "\" doesn't exist"); + } + return this.pickOption(options[0], false); + }; + + return Select; + + })(Evented); + + Select.init = function(options) { + var el, _i, _len, _ref1, _results; + if (options == null) { + options = {}; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + return Select.init(options); + }); + return; + } + if (options.selector == null) { + options.selector = 'select'; + } + _ref1 = document.querySelectorAll(options.selector); + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + el = _ref1[_i]; + if (!el.selectInstance) { + _results.push(new Select(extend({ + el: el + }, options))); + } else { + _results.push(void 0); + } + } + return _results; + }; + + window.Select = Select; + +}).call(this); diff --git a/third_party/hubspot/tether.js b/third_party/hubspot/tether.js new file mode 100755 index 00000000..92f64854 --- /dev/null +++ b/third_party/hubspot/tether.js @@ -0,0 +1,686 @@ +(function() { + var MIRROR_LR, MIRROR_TB, OFFSET_MAP, Tether, addClass, addOffset, attachmentToOffset, autoToFixedAttachment, defer, extend, flush, getBounds, getOffsetParent, getOuterSize, getScrollBarSize, getScrollParent, getSize, now, offsetToPx, parseAttachment, parseOffset, position, removeClass, tethers, transformKey, updateClasses, within, _Tether, _ref, + __slice = [].slice, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + if (this.Tether == null) { + throw new Error("You must include the utils.js file before tether.js"); + } + + Tether = this.Tether; + + _ref = Tether.Utils, getScrollParent = _ref.getScrollParent, getSize = _ref.getSize, getOuterSize = _ref.getOuterSize, getBounds = _ref.getBounds, getOffsetParent = _ref.getOffsetParent, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, updateClasses = _ref.updateClasses, defer = _ref.defer, flush = _ref.flush, getScrollBarSize = _ref.getScrollBarSize; + + within = function(a, b, diff) { + if (diff == null) { + diff = 1; + } + return (a + diff >= b && b >= a - diff); + }; + + transformKey = (function() { + var el, key, _i, _len, _ref1; + el = document.createElement('div'); + _ref1 = ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + key = _ref1[_i]; + if (el.style[key] !== void 0) { + return key; + } + } + })(); + + tethers = []; + + position = function() { + var tether, _i, _len; + for (_i = 0, _len = tethers.length; _i < _len; _i++) { + tether = tethers[_i]; + tether.position(false); + } + return flush(); + }; + + now = function() { + var _ref1; + return (_ref1 = typeof performance !== "undefined" && performance !== null ? typeof performance.now === "function" ? performance.now() : void 0 : void 0) != null ? _ref1 : +(new Date); + }; + + (function() { + var event, lastCall, lastDuration, pendingTimeout, tick, _i, _len, _ref1, _results; + lastCall = null; + lastDuration = null; + pendingTimeout = null; + tick = function() { + if ((lastDuration != null) && lastDuration > 16) { + lastDuration = Math.min(lastDuration - 16, 250); + pendingTimeout = setTimeout(tick, 250); + return; + } + if ((lastCall != null) && (now() - lastCall) < 10) { + return; + } + if (pendingTimeout != null) { + clearTimeout(pendingTimeout); + pendingTimeout = null; + } + lastCall = now(); + position(); + return lastDuration = now() - lastCall; + }; + _ref1 = ['resize', 'scroll', 'touchmove']; + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + event = _ref1[_i]; + _results.push(window.addEventListener(event, tick)); + } + return _results; + })(); + + MIRROR_LR = { + center: 'center', + left: 'right', + right: 'left' + }; + + MIRROR_TB = { + middle: 'middle', + top: 'bottom', + bottom: 'top' + }; + + OFFSET_MAP = { + top: 0, + left: 0, + middle: '50%', + center: '50%', + bottom: '100%', + right: '100%' + }; + + autoToFixedAttachment = function(attachment, relativeToAttachment) { + var left, top; + left = attachment.left, top = attachment.top; + if (left === 'auto') { + left = MIRROR_LR[relativeToAttachment.left]; + } + if (top === 'auto') { + top = MIRROR_TB[relativeToAttachment.top]; + } + return { + left: left, + top: top + }; + }; + + attachmentToOffset = function(attachment) { + var _ref1, _ref2; + return { + left: (_ref1 = OFFSET_MAP[attachment.left]) != null ? _ref1 : attachment.left, + top: (_ref2 = OFFSET_MAP[attachment.top]) != null ? _ref2 : attachment.top + }; + }; + + addOffset = function() { + var left, offsets, out, top, _i, _len, _ref1; + offsets = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + out = { + top: 0, + left: 0 + }; + for (_i = 0, _len = offsets.length; _i < _len; _i++) { + _ref1 = offsets[_i], top = _ref1.top, left = _ref1.left; + if (typeof top === 'string') { + top = parseFloat(top, 10); + } + if (typeof left === 'string') { + left = parseFloat(left, 10); + } + out.top += top; + out.left += left; + } + return out; + }; + + offsetToPx = function(offset, size) { + if (typeof offset.left === 'string' && offset.left.indexOf('%') !== -1) { + offset.left = parseFloat(offset.left, 10) / 100 * size.width; + } + if (typeof offset.top === 'string' && offset.top.indexOf('%') !== -1) { + offset.top = parseFloat(offset.top, 10) / 100 * size.height; + } + return offset; + }; + + parseAttachment = parseOffset = function(value) { + var left, top, _ref1; + _ref1 = value.split(' '), top = _ref1[0], left = _ref1[1]; + return { + top: top, + left: left + }; + }; + + _Tether = (function() { + _Tether.modules = []; + + function _Tether(options) { + this.position = __bind(this.position, this); + var module, _i, _len, _ref1, _ref2; + tethers.push(this); + this.history = []; + this.setOptions(options, false); + _ref1 = Tether.modules; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + module = _ref1[_i]; + if ((_ref2 = module.initialize) != null) { + _ref2.call(this); + } + } + this.position(); + } + + _Tether.prototype.getClass = function(key) { + var _ref1, _ref2; + if ((_ref1 = this.options.classes) != null ? _ref1[key] : void 0) { + return this.options.classes[key]; + } else if (((_ref2 = this.options.classes) != null ? _ref2[key] : void 0) !== false) { + if (this.options.classPrefix) { + return "" + this.options.classPrefix + "-" + key; + } else { + return key; + } + } else { + return ''; + } + }; + + _Tether.prototype.setOptions = function(options, position) { + var defaults, key, _i, _len, _ref1, _ref2; + this.options = options; + if (position == null) { + position = true; + } + defaults = { + offset: '0 0', + targetOffset: '0 0', + targetAttachment: 'auto auto', + classPrefix: 'tether' + }; + this.options = extend(defaults, this.options); + _ref1 = this.options, this.element = _ref1.element, this.target = _ref1.target, this.targetModifier = _ref1.targetModifier; + if (this.target === 'viewport') { + this.target = document.body; + this.targetModifier = 'visible'; + } else if (this.target === 'scroll-handle') { + this.target = document.body; + this.targetModifier = 'scroll-handle'; + } + _ref2 = ['element', 'target']; + for (_i = 0, _len = _ref2.length; _i < _len; _i++) { + key = _ref2[_i]; + if (this[key] == null) { + throw new Error("Tether Error: Both element and target must be defined"); + } + if (this[key].jquery != null) { + this[key] = this[key][0]; + } else if (typeof this[key] === 'string') { + this[key] = document.querySelector(this[key]); + } + } + addClass(this.element, this.getClass('element')); + addClass(this.target, this.getClass('target')); + if (!this.options.attachment) { + throw new Error("Tether Error: You must provide an attachment"); + } + this.targetAttachment = parseAttachment(this.options.targetAttachment); + this.attachment = parseAttachment(this.options.attachment); + this.offset = parseOffset(this.options.offset); + this.targetOffset = parseOffset(this.options.targetOffset); + if (this.scrollParent != null) { + this.disable(); + } + if (this.targetModifier === 'scroll-handle') { + this.scrollParent = this.target; + } else { + this.scrollParent = getScrollParent(this.target); + } + if (this.options.enabled !== false) { + return this.enable(position); + } + }; + + _Tether.prototype.getTargetBounds = function() { + var bounds, fitAdj, hasBottomScroll, height, out, scrollBottom, scrollPercentage, style, target; + if (this.targetModifier != null) { + switch (this.targetModifier) { + case 'visible': + if (this.target === document.body) { + return { + top: pageYOffset, + left: pageXOffset, + height: innerHeight, + width: innerWidth + }; + } else { + bounds = getBounds(this.target); + out = { + height: bounds.height, + width: bounds.width, + top: bounds.top, + left: bounds.left + }; + out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top)); + out.height = Math.min(out.height, bounds.height - ((bounds.top + bounds.height) - (pageYOffset + innerHeight))); + out.height = Math.min(innerHeight, out.height); + out.height -= 2; + out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left)); + out.width = Math.min(out.width, bounds.width - ((bounds.left + bounds.width) - (pageXOffset + innerWidth))); + out.width = Math.min(innerWidth, out.width); + out.width -= 2; + if (out.top < pageYOffset) { + out.top = pageYOffset; + } + if (out.left < pageXOffset) { + out.left = pageXOffset; + } + return out; + } + break; + case 'scroll-handle': + target = this.target; + if (target === document.body) { + target = document.documentElement; + bounds = { + left: pageXOffset, + top: pageYOffset, + height: innerHeight, + width: innerWidth + }; + } else { + bounds = getBounds(target); + } + style = getComputedStyle(target); + hasBottomScroll = target.scrollWidth > target.clientWidth || 'scroll' === [style.overflow, style.overflowX] || this.target !== document.body; + scrollBottom = 0; + if (hasBottomScroll) { + scrollBottom = 15; + } + height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom; + out = { + width: 15, + height: height * 0.975 * (height / target.scrollHeight), + left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15 + }; + fitAdj = 0; + if (height < 408 && this.target === document.body) { + fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58; + } + if (this.target !== document.body) { + out.height = Math.max(out.height, 24); + } + scrollPercentage = this.target.scrollTop / (target.scrollHeight - height); + out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth); + if (this.target === document.body) { + out.height = Math.max(out.height, 24); + } + return out; + } + } else { + return getBounds(this.target); + } + }; + + _Tether.prototype.clearCache = function() { + return this._cache = {}; + }; + + _Tether.prototype.cache = function(k, getter) { + if (this._cache == null) { + this._cache = {}; + } + if (this._cache[k] == null) { + this._cache[k] = getter.call(this); + } + return this._cache[k]; + }; + + _Tether.prototype.enable = function(position) { + if (position == null) { + position = true; + } + addClass(this.target, this.getClass('enabled')); + addClass(this.element, this.getClass('enabled')); + this.enabled = true; + if (this.scrollParent !== document) { + this.scrollParent.addEventListener('scroll', this.position); + } + if (position) { + return this.position(); + } + }; + + _Tether.prototype.disable = function() { + removeClass(this.target, this.getClass('enabled')); + removeClass(this.element, this.getClass('enabled')); + this.enabled = false; + if (this.scrollParent != null) { + return this.scrollParent.removeEventListener('scroll', this.position); + } + }; + + _Tether.prototype.destroy = function() { + var i, tether, _i, _len, _results; + this.disable(); + _results = []; + for (i = _i = 0, _len = tethers.length; _i < _len; i = ++_i) { + tether = tethers[i]; + if (tether === this) { + tethers.splice(i, 1); + break; + } else { + _results.push(void 0); + } + } + return _results; + }; + + _Tether.prototype.updateAttachClasses = function(elementAttach, targetAttach) { + var add, all, side, sides, _i, _j, _len, _len1, _ref1, + _this = this; + if (elementAttach == null) { + elementAttach = this.attachment; + } + if (targetAttach == null) { + targetAttach = this.targetAttachment; + } + sides = ['left', 'top', 'bottom', 'right', 'middle', 'center']; + if ((_ref1 = this._addAttachClasses) != null ? _ref1.length : void 0) { + this._addAttachClasses.splice(0, this._addAttachClasses.length); + } + add = this._addAttachClasses != null ? this._addAttachClasses : this._addAttachClasses = []; + if (elementAttach.top) { + add.push("" + (this.getClass('element-attached')) + "-" + elementAttach.top); + } + if (elementAttach.left) { + add.push("" + (this.getClass('element-attached')) + "-" + elementAttach.left); + } + if (targetAttach.top) { + add.push("" + (this.getClass('target-attached')) + "-" + targetAttach.top); + } + if (targetAttach.left) { + add.push("" + (this.getClass('target-attached')) + "-" + targetAttach.left); + } + all = []; + for (_i = 0, _len = sides.length; _i < _len; _i++) { + side = sides[_i]; + all.push("" + (this.getClass('element-attached')) + "-" + side); + } + for (_j = 0, _len1 = sides.length; _j < _len1; _j++) { + side = sides[_j]; + all.push("" + (this.getClass('target-attached')) + "-" + side); + } + return defer(function() { + if (_this._addAttachClasses == null) { + return; + } + updateClasses(_this.element, _this._addAttachClasses, all); + updateClasses(_this.target, _this._addAttachClasses, all); + return _this._addAttachClasses = void 0; + }); + }; + + _Tether.prototype.position = function(flushChanges) { + var elementPos, elementStyle, height, left, manualOffset, manualTargetOffset, module, next, offset, offsetBorder, offsetParent, offsetParentSize, offsetParentStyle, offsetPosition, ret, scrollLeft, scrollTop, scrollbarSize, side, targetAttachment, targetOffset, targetPos, targetSize, top, width, _i, _j, _len, _len1, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, + _this = this; + if (flushChanges == null) { + flushChanges = true; + } + if (!this.enabled) { + return; + } + this.clearCache(); + targetAttachment = autoToFixedAttachment(this.targetAttachment, this.attachment); + this.updateAttachClasses(this.attachment, targetAttachment); + elementPos = this.cache('element-bounds', function() { + return getBounds(_this.element); + }); + width = elementPos.width, height = elementPos.height; + if (width === 0 && height === 0 && (this.lastSize != null)) { + _ref1 = this.lastSize, width = _ref1.width, height = _ref1.height; + } else { + this.lastSize = { + width: width, + height: height + }; + } + targetSize = targetPos = this.cache('target-bounds', function() { + return _this.getTargetBounds(); + }); + offset = offsetToPx(attachmentToOffset(this.attachment), { + width: width, + height: height + }); + targetOffset = offsetToPx(attachmentToOffset(targetAttachment), targetSize); + manualOffset = offsetToPx(this.offset, { + width: width, + height: height + }); + manualTargetOffset = offsetToPx(this.targetOffset, targetSize); + offset = addOffset(offset, manualOffset); + targetOffset = addOffset(targetOffset, manualTargetOffset); + left = targetPos.left + targetOffset.left - offset.left; + top = targetPos.top + targetOffset.top - offset.top; + _ref2 = Tether.modules; + for (_i = 0, _len = _ref2.length; _i < _len; _i++) { + module = _ref2[_i]; + ret = module.position.call(this, { + left: left, + top: top, + targetAttachment: targetAttachment, + targetPos: targetPos, + attachment: this.attachment, + elementPos: elementPos, + offset: offset, + targetOffset: targetOffset, + manualOffset: manualOffset, + manualTargetOffset: manualTargetOffset, + scrollbarSize: scrollbarSize + }); + if ((ret == null) || typeof ret !== 'object') { + continue; + } else if (ret === false) { + return false; + } else { + top = ret.top, left = ret.left; + } + } + next = { + page: { + top: top, + left: left + }, + viewport: { + top: top - pageYOffset, + bottom: pageYOffset - top - height + innerHeight, + left: left - pageXOffset, + right: pageXOffset - left - width + innerWidth + } + }; + if (document.body.scrollWidth > window.innerWidth) { + scrollbarSize = this.cache('scrollbar-size', getScrollBarSize); + next.viewport.bottom -= scrollbarSize.height; + } + if (document.body.scrollHeight > window.innerHeight) { + scrollbarSize = this.cache('scrollbar-size', getScrollBarSize); + next.viewport.right -= scrollbarSize.width; + } + if (((_ref3 = document.body.style.position) !== '' && _ref3 !== 'static') || ((_ref4 = document.body.parentElement.style.position) !== '' && _ref4 !== 'static')) { + next.page.bottom = document.body.scrollHeight - top - height; + next.page.right = document.body.scrollWidth - left - width; + } + if (((_ref5 = this.options.optimizations) != null ? _ref5.moveElement : void 0) !== false && (this.targetModifier == null)) { + offsetParent = this.cache('target-offsetparent', function() { + return getOffsetParent(_this.target); + }); + offsetPosition = this.cache('target-offsetparent-bounds', function() { + return getBounds(offsetParent); + }); + offsetParentStyle = getComputedStyle(offsetParent); + elementStyle = getComputedStyle(this.element); + offsetParentSize = offsetPosition; + offsetBorder = {}; + _ref6 = ['Top', 'Left', 'Bottom', 'Right']; + for (_j = 0, _len1 = _ref6.length; _j < _len1; _j++) { + side = _ref6[_j]; + offsetBorder[side.toLowerCase()] = parseFloat(offsetParentStyle["border" + side + "Width"]); + } + offsetPosition.right = document.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right; + offsetPosition.bottom = document.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom; + if (next.page.top >= (offsetPosition.top + offsetBorder.top) && next.page.bottom >= offsetPosition.bottom) { + if (next.page.left >= (offsetPosition.left + offsetBorder.left) && next.page.right >= offsetPosition.right) { + scrollTop = offsetParent.scrollTop; + scrollLeft = offsetParent.scrollLeft; + next.offset = { + top: next.page.top - offsetPosition.top + scrollTop - offsetBorder.top, + left: next.page.left - offsetPosition.left + scrollLeft - offsetBorder.left + }; + } + } + } + this.move(next); + this.history.unshift(next); + if (this.history.length > 3) { + this.history.pop(); + } + if (flushChanges) { + flush(); + } + return true; + }; + + _Tether.prototype.move = function(position) { + var css, elVal, found, key, moved, offsetParent, point, same, transcribe, type, val, write, writeCSS, _i, _len, _ref1, _ref2, + _this = this; + if (this.element.parentNode == null) { + return; + } + same = {}; + for (type in position) { + same[type] = {}; + for (key in position[type]) { + found = false; + _ref1 = this.history; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + point = _ref1[_i]; + if (!within((_ref2 = point[type]) != null ? _ref2[key] : void 0, position[type][key])) { + found = true; + break; + } + } + if (!found) { + same[type][key] = true; + } + } + } + css = { + top: '', + left: '', + right: '', + bottom: '' + }; + transcribe = function(same, pos) { + var xPos, yPos, _ref3; + if (((_ref3 = _this.options.optimizations) != null ? _ref3.gpu : void 0) !== false) { + if (same.top) { + css.top = 0; + yPos = pos.top; + } else { + css.bottom = 0; + yPos = -pos.bottom; + } + if (same.left) { + css.left = 0; + xPos = pos.left; + } else { + css.right = 0; + xPos = -pos.right; + } + css[transformKey] = "translateX(" + (Math.round(xPos)) + "px) translateY(" + (Math.round(yPos)) + "px)"; + if (transformKey !== 'msTransform') { + return css[transformKey] += " translateZ(0)"; + } + } else { + if (same.top) { + css.top = "" + pos.top + "px"; + } else { + css.bottom = "" + pos.bottom + "px"; + } + if (same.left) { + return css.left = "" + pos.left + "px"; + } else { + return css.right = "" + pos.right + "px"; + } + } + }; + moved = false; + if ((same.page.top || same.page.bottom) && (same.page.left || same.page.right)) { + css.position = 'absolute'; + transcribe(same.page, position.page); + } else if ((same.viewport.top || same.viewport.bottom) && (same.viewport.left || same.viewport.right)) { + css.position = 'fixed'; + transcribe(same.viewport, position.viewport); + } else if ((same.offset != null) && same.offset.top && same.offset.left) { + css.position = 'absolute'; + offsetParent = this.cache('target-offsetparent', function() { + return getOffsetParent(_this.target); + }); + if (getOffsetParent(this.element) !== offsetParent) { + defer(function() { + _this.element.parentNode.removeChild(_this.element); + return offsetParent.appendChild(_this.element); + }); + } + transcribe(same.offset, position.offset); + moved = true; + } else { + css.position = 'absolute'; + transcribe({ + top: true, + left: true + }, position.page); + } + if (!moved && this.element.parentNode.tagName !== 'BODY') { + this.element.parentNode.removeChild(this.element); + document.body.appendChild(this.element); + } + writeCSS = {}; + write = false; + for (key in css) { + val = css[key]; + elVal = this.element.style[key]; + if (elVal !== '' && val !== '' && (key === 'top' || key === 'left' || key === 'bottom' || key === 'right')) { + elVal = parseFloat(elVal); + val = parseFloat(val); + } + if (elVal !== val) { + write = true; + writeCSS[key] = css[key]; + } + } + if (write) { + return defer(function() { + return extend(_this.element.style, writeCSS); + }); + } + }; + + return _Tether; + + })(); + + Tether.position = position; + + this.Tether = extend(_Tether, Tether); + +}).call(this); diff --git a/third_party/hubspot/utils.js b/third_party/hubspot/utils.js new file mode 100755 index 00000000..b2551039 --- /dev/null +++ b/third_party/hubspot/utils.js @@ -0,0 +1,331 @@ +(function() { + var Evented, addClass, defer, deferred, extend, flush, getBounds, getOffsetParent, getOrigin, getScrollBarSize, getScrollParent, hasClass, node, removeClass, uniqueId, updateClasses, zeroPosCache, + __hasProp = {}.hasOwnProperty, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + __slice = [].slice; + + if (this.Tether == null) { + this.Tether = { + modules: [] + }; + } + + getScrollParent = function(el) { + var parent, position, scrollParent, style, _ref; + position = getComputedStyle(el).position; + if (position === 'fixed') { + return el; + } + scrollParent = void 0; + parent = el; + while (parent = parent.parentNode) { + try { + style = getComputedStyle(parent); + } catch (_error) {} + if (style == null) { + return parent; + } + if (/(auto|scroll)/.test(style['overflow'] + style['overflow-y'] + style['overflow-x'])) { + if (position !== 'absolute' || ((_ref = style['position']) === 'relative' || _ref === 'absolute' || _ref === 'fixed')) { + return parent; + } + } + } + return document.body; + }; + + uniqueId = (function() { + var id; + id = 0; + return function() { + return id++; + }; + })(); + + zeroPosCache = {}; + + getOrigin = function(doc) { + var id, k, node, v, _ref; + node = doc._tetherZeroElement; + if (node == null) { + node = doc.createElement('div'); + node.setAttribute('data-tether-id', uniqueId()); + extend(node.style, { + top: 0, + left: 0, + position: 'absolute' + }); + doc.body.appendChild(node); + doc._tetherZeroElement = node; + } + id = node.getAttribute('data-tether-id'); + if (zeroPosCache[id] == null) { + zeroPosCache[id] = {}; + _ref = node.getBoundingClientRect(); + for (k in _ref) { + v = _ref[k]; + zeroPosCache[id][k] = v; + } + defer(function() { + return zeroPosCache[id] = void 0; + }); + } + return zeroPosCache[id]; + }; + + node = null; + + getBounds = function(el) { + var box, doc, docEl, k, origin, v, _ref; + if (el === document) { + doc = document; + el = document.documentElement; + } else { + doc = el.ownerDocument; + } + docEl = doc.documentElement; + box = {}; + _ref = el.getBoundingClientRect(); + for (k in _ref) { + v = _ref[k]; + box[k] = v; + } + origin = getOrigin(doc); + box.top -= origin.top; + box.left -= origin.left; + if (box.width == null) { + box.width = document.body.scrollWidth - box.left - box.right; + } + if (box.height == null) { + box.height = document.body.scrollHeight - box.top - box.bottom; + } + box.top = box.top - docEl.clientTop; + box.left = box.left - docEl.clientLeft; + box.right = doc.body.clientWidth - box.width - box.left; + box.bottom = doc.body.clientHeight - box.height - box.top; + return box; + }; + + getOffsetParent = function(el) { + return el.offsetParent || document.documentElement; + }; + + getScrollBarSize = function() { + var inner, outer, width, widthContained, widthScroll; + inner = document.createElement('div'); + inner.style.width = '100%'; + inner.style.height = '200px'; + outer = document.createElement('div'); + extend(outer.style, { + position: 'absolute', + top: 0, + left: 0, + pointerEvents: 'none', + visibility: 'hidden', + width: '200px', + height: '150px', + overflow: 'hidden' + }); + outer.appendChild(inner); + document.body.appendChild(outer); + widthContained = inner.offsetWidth; + outer.style.overflow = 'scroll'; + widthScroll = inner.offsetWidth; + if (widthContained === widthScroll) { + widthScroll = outer.clientWidth; + } + document.body.removeChild(outer); + width = widthContained - widthScroll; + return { + width: width, + height: width + }; + }; + + extend = function(out) { + var args, key, obj, val, _i, _len, _ref; + if (out == null) { + out = {}; + } + args = []; + Array.prototype.push.apply(args, arguments); + _ref = args.slice(1); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + obj = _ref[_i]; + if (obj) { + for (key in obj) { + if (!__hasProp.call(obj, key)) continue; + val = obj[key]; + out[key] = val; + } + } + } + return out; + }; + + removeClass = function(el, name) { + var cls, _i, _len, _ref, _results; + if (el.classList != null) { + _ref = name.split(' '); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cls = _ref[_i]; + if (cls.trim()) { + _results.push(el.classList.remove(cls)); + } + } + return _results; + } else { + return el.className = el.className.replace(new RegExp("(^| )" + (name.split(' ').join('|')) + "( |$)", 'gi'), ' '); + } + }; + + addClass = function(el, name) { + var cls, _i, _len, _ref, _results; + if (el.classList != null) { + _ref = name.split(' '); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cls = _ref[_i]; + if (cls.trim()) { + _results.push(el.classList.add(cls)); + } + } + return _results; + } else { + removeClass(el, name); + return el.className += " " + name; + } + }; + + hasClass = function(el, name) { + if (el.classList != null) { + return el.classList.contains(name); + } else { + return new RegExp("(^| )" + name + "( |$)", 'gi').test(el.className); + } + }; + + updateClasses = function(el, add, all) { + var cls, _i, _j, _len, _len1, _results; + for (_i = 0, _len = all.length; _i < _len; _i++) { + cls = all[_i]; + if (__indexOf.call(add, cls) < 0) { + if (hasClass(el, cls)) { + removeClass(el, cls); + } + } + } + _results = []; + for (_j = 0, _len1 = add.length; _j < _len1; _j++) { + cls = add[_j]; + if (!hasClass(el, cls)) { + _results.push(addClass(el, cls)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + deferred = []; + + defer = function(fn) { + return deferred.push(fn); + }; + + flush = function() { + var fn, _results; + _results = []; + while (fn = deferred.pop()) { + _results.push(fn()); + } + return _results; + }; + + Evented = (function() { + function Evented() {} + + Evented.prototype.on = function(event, handler, ctx, once) { + var _base; + if (once == null) { + once = false; + } + if (this.bindings == null) { + this.bindings = {}; + } + if ((_base = this.bindings)[event] == null) { + _base[event] = []; + } + return this.bindings[event].push({ + handler: handler, + ctx: ctx, + once: once + }); + }; + + Evented.prototype.once = function(event, handler, ctx) { + return this.on(event, handler, ctx, true); + }; + + Evented.prototype.off = function(event, handler) { + var i, _ref, _results; + if (((_ref = this.bindings) != null ? _ref[event] : void 0) == null) { + return; + } + if (handler == null) { + return delete this.bindings[event]; + } else { + i = 0; + _results = []; + while (i < this.bindings[event].length) { + if (this.bindings[event][i].handler === handler) { + _results.push(this.bindings[event].splice(i, 1)); + } else { + _results.push(i++); + } + } + return _results; + } + }; + + Evented.prototype.trigger = function() { + var args, ctx, event, handler, i, once, _ref, _ref1, _results; + event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + if ((_ref = this.bindings) != null ? _ref[event] : void 0) { + i = 0; + _results = []; + while (i < this.bindings[event].length) { + _ref1 = this.bindings[event][i], handler = _ref1.handler, ctx = _ref1.ctx, once = _ref1.once; + handler.apply(ctx != null ? ctx : this, args); + if (once) { + _results.push(this.bindings[event].splice(i, 1)); + } else { + _results.push(i++); + } + } + return _results; + } + }; + + return Evented; + + })(); + + this.Tether.Utils = { + getScrollParent: getScrollParent, + getBounds: getBounds, + getOffsetParent: getOffsetParent, + extend: extend, + addClass: addClass, + removeClass: removeClass, + hasClass: hasClass, + updateClasses: updateClasses, + defer: defer, + flush: flush, + uniqueId: uniqueId, + Evented: Evented, + getScrollBarSize: getScrollBarSize + }; + +}).call(this); diff --git a/third_party/jquery-fitvids/LICENSE b/third_party/jquery-fitvids/LICENSE new file mode 100644 index 00000000..5c93f456 --- /dev/null +++ b/third_party/jquery-fitvids/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/third_party/jquery-fitvids/jquery.fitvids.js b/third_party/jquery-fitvids/jquery.fitvids.js new file mode 100644 index 00000000..1ad3c4c1 --- /dev/null +++ b/third_party/jquery-fitvids/jquery.fitvids.js @@ -0,0 +1,72 @@ +/*global jQuery */ +/*jshint browser:true */ +/*! +* FitVids 1.1 +* +* Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com +* Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ +* Released under the WTFPL license - http://sam.zoy.org/wtfpl/ +* +*/ + +(function( $ ){ + + "use strict"; + + $.fn.fitVids = function( options ) { + var settings = { + customSelector: null + }; + + if(!document.getElementById('fit-vids-style')) { + // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js + var head = document.head || document.getElementsByTagName('head')[0]; + var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}'; + var div = document.createElement('div'); + div.innerHTML = '

    x

    '; + head.appendChild(div.childNodes[1]); + } + + if ( options ) { + $.extend( settings, options ); + } + + return this.each(function(){ + var selectors = [ + "iframe[src*='player.vimeo.com']", + "iframe[src*='youtube.com']", + "iframe[src*='youtube-nocookie.com']", + "iframe[src*='kickstarter.com'][src*='video.html']", + "object", + "embed" + ]; + + if (settings.customSelector) { + selectors.push(settings.customSelector); + } + + var $allVideos = $(this).find(selectors.join(',')); + $allVideos = $allVideos.not("object object"); // SwfObj conflict patch + + $allVideos.each(function(){ + var $this = $(this); + if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } + if ((!$this.css('height') && !$this.css('width')) && (isNaN($this.attr('height')) || isNaN($this.attr('width')))) + { + $this.attr('height', 9); + $this.attr('width', 16); + } + var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), + width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), + aspectRatio = height / width; + if(!$this.attr('id')){ + var videoID = 'fitvid' + Math.floor(Math.random()*999999); + $this.attr('id', videoID); + } + $this.wrap('
    ').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); + $this.removeAttr('height').removeAttr('width'); + }); + }); + }; +// Works with either jQuery or Zepto +})( window.jQuery || window.Zepto ); diff --git a/third_party/jquery-mmenu/LICENSE b/third_party/jquery-mmenu/LICENSE new file mode 100644 index 00000000..6bef4291 --- /dev/null +++ b/third_party/jquery-mmenu/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fred Heusschen + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/jquery-mmenu/js/jquery.mmenu.footer.js b/third_party/jquery-mmenu/js/jquery.mmenu.footer.js new file mode 100755 index 00000000..be27ca41 --- /dev/null +++ b/third_party/jquery-mmenu/js/jquery.mmenu.footer.js @@ -0,0 +1,138 @@ +/* + * jQuery mmenu footer addon + * mmenu.frebsite.nl + * + * Copyright (c) Fred Heusschen + */ + + +(function( $ ) { + + var _PLUGIN_ = 'mmenu', + _ADDON_ = 'footer'; + + + $[ _PLUGIN_ ].addons[ _ADDON_ ] = { + + // _init: fired when (re)initiating the plugin + _init: function( $panels ) + { + var that = this, + opts = this.opts[ _ADDON_ ]; + + + // Update content + var $footer = $('div.' + _c.footer, this.$menu); + if ( $footer.length ) + { + // Auto-update the footer content + if ( opts.update ) + { + $panels + .each( + function() + { + var $panl = $(this); + + // Find content + var $cnt = $('.' + that.conf.classNames[ _ADDON_ ].panelFooter, $panl), + _cnt = $cnt.html(); + + if ( !_cnt ) + { + _cnt = opts.title; + } + + // Update footer info + var updateFooter = function() + { + $footer[ _cnt ? 'show' : 'hide' ](); + $footer.html( _cnt ); + }; + + $panl.on( _e.open, updateFooter ); + + if ( $panl.hasClass( _c.current ) ) + { + updateFooter(); + } + } + ); + } + + // Init other add-ons + if ( $[ _PLUGIN_ ].addons.buttonbars ) + { + $[ _PLUGIN_ ].addons.buttonbars._init.call( this, $footer ); + } + } + }, + + // _setup: fired once per menu + _setup: function() + { + var opts = this.opts[ _ADDON_ ]; + + + // Extend shortcut options + if ( typeof opts == 'boolean' ) + { + opts = { + add : opts, + update : opts + }; + } + if ( typeof opts != 'object' ) + { + opts = {}; + } + opts = $.extend( true, {}, $[ _PLUGIN_ ].defaults[ _ADDON_ ], opts ); + + + this.opts[ _ADDON_ ] = opts; + + + // Add markup + if ( opts.add ) + { + var content = opts.content + ? opts.content + : opts.title; + + $( '
    ' ) + .appendTo( this.$menu ) + .append( content ); + + this.$menu.addClass( _c.hasfooter ); + } + }, + + // _add: fired once per page load + _add: function() + { + _c = $[ _PLUGIN_ ]._c; + _d = $[ _PLUGIN_ ]._d; + _e = $[ _PLUGIN_ ]._e; + + _c.add( 'footer hasfooter' ); + + glbl = $[ _PLUGIN_ ].glbl; + } + }; + + + // Default options and configuration + $[ _PLUGIN_ ].defaults[ _ADDON_ ] = { + add : false, + content : false, + title : '', + update : false + }; + $[ _PLUGIN_ ].configuration.classNames[ _ADDON_ ] = { + panelFooter: 'Footer' + }; + + + var _c, _d, _e, glbl; + +})( jQuery ); \ No newline at end of file diff --git a/third_party/jquery-mmenu/js/jquery.mmenu.offcanvas.js b/third_party/jquery-mmenu/js/jquery.mmenu.offcanvas.js new file mode 100755 index 00000000..0405eecc --- /dev/null +++ b/third_party/jquery-mmenu/js/jquery.mmenu.offcanvas.js @@ -0,0 +1,386 @@ +/* + * jQuery mmenu offCanvas addon + * mmenu.frebsite.nl + * + * Copyright (c) Fred Heusschen + */ + + +(function( $ ) { + + var _PLUGIN_ = 'mmenu', + _ADDON_ = 'offCanvas'; + + + $[ _PLUGIN_ ].addons[ _ADDON_ ] = { + + // _init: fired when (re)initiating the plugin + _init: function( $panels ) {}, + + // _setup: fired once per menu + _setup: function() + { + if ( !this.opts[ _ADDON_ ] ) + { + return; + } + + var that = this, + opts = this.opts[ _ADDON_ ], + conf = this.conf[ _ADDON_ ]; + + + // Extend shortcut configuration + if ( typeof conf.pageSelector != 'string' ) + { + conf.pageSelector = '> ' + conf.pageNodetype; + } + + + glbl.$allMenus = ( glbl.$allMenus || $() ).add( this.$menu ); + + + // Setup the menu + this.vars.opened = false; + + var clsn = [ _c.offcanvas ]; + + if ( opts.position != 'left' ) + { + clsn.push( _c.mm( opts.position ) ); + } + if ( opts.zposition != 'back' ) + { + clsn.push( _c.mm( opts.zposition ) ); + } + + this.$menu + .addClass( clsn.join( ' ' ) ) + .parent() + .removeClass( _c.wrapper ); + + + // Setup the page + this.setPage( glbl.$page ); + + + // Setup the UI blocker and the window + this[ _ADDON_ + '_initBlocker' ](); + this[ _ADDON_ + '_initWindow' ](); + + + // Add events + this.$menu + .on( _e.open + ' ' + _e.opening + ' ' + _e.opened + ' ' + + _e.close + ' ' + _e.closing + ' ' + _e.closed + ' ' + _e.setPage, + function( e ) + { + e.stopPropagation(); + } + ) + .on( _e.open + ' ' + _e.close + ' ' + _e.setPage, + function( e ) + { + that[ e.type ](); + } + ); + + + // Append to the body + this.$menu[ conf.menuInjectMethod + 'To' ]( conf.menuWrapperSelector ); + }, + + // _add: fired once per page load + _add: function() + { + _c = $[ _PLUGIN_ ]._c; + _d = $[ _PLUGIN_ ]._d; + _e = $[ _PLUGIN_ ]._e; + + _c.add( 'offcanvas slideout modal background opening blocker page' ); + _d.add( 'style' ); + _e.add( 'opening opened closing closed setPage' ); + + glbl = $[ _PLUGIN_ ].glbl; + }, + + // _clickAnchor: prevents default behavior when clicking an anchor + _clickAnchor: function( $a, inMenu ) + { + if ( !this.opts[ _ADDON_ ] ) + { + return false; + } + + // Open menu + var id = this.$menu.attr( 'id' ); + if ( id && id.length ) + { + if ( this.conf.clone ) + { + id = _c.umm( id ); + } + if ( $a.is( '[href="#' + id + '"]' ) ) + { + this.open(); + return true; + } + } + + // Close menu + if ( !glbl.$page ) + { + return; + } + var id = glbl.$page.attr( 'id' ); + if ( id && id.length ) + { + if ( $a.is( '[href="#' + id + '"]' ) ) + { + this.close(); + return true; + } + } + + return false; + } + }; + + + // Default options and configuration + $[ _PLUGIN_ ].defaults[ _ADDON_ ] = { + position : 'left', + zposition : 'back', + modal : false, + moveBackground : true + }; + $[ _PLUGIN_ ].configuration[ _ADDON_ ] = { + pageNodetype : 'div', + pageSelector : null, + menuWrapperSelector : 'body', + menuInjectMethod : 'prepend' + }; + + + // Methods + $[ _PLUGIN_ ].prototype.open = function() + { + if ( this.vars.opened ) + { + return false; + } + + var that = this; + + this._openSetup(); + + // Without the timeout, the animation won't work because the element had display: none; + setTimeout( + function() + { + that._openFinish(); + }, this.conf.openingInterval + ); + + return 'open'; + }; + + $[ _PLUGIN_ ].prototype._openSetup = function() + { + var that = this; + + // Close other menus + glbl.$allMenus.not( this.$menu ).trigger( _e.close ); + + // Store style and position + glbl.$page.data( _d.style, glbl.$page.attr( 'style' ) || '' ); + + // Trigger window-resize to measure height + glbl.$wndw.trigger( _e.resize, [ true ] ); + + var clsn = [ _c.opened ]; + + // Add options + if ( this.opts[ _ADDON_ ].modal ) + { + clsn.push( _c.modal ); + } + if ( this.opts[ _ADDON_ ].moveBackground ) + { + clsn.push( _c.background ); + } + if ( this.opts[ _ADDON_ ].position != 'left' ) + { + clsn.push( _c.mm( this.opts[ _ADDON_ ].position ) ); + } + if ( this.opts[ _ADDON_ ].zposition != 'back' ) + { + clsn.push( _c.mm( this.opts[ _ADDON_ ].zposition ) ); + } + if ( this.opts.classes ) + { + clsn.push( this.opts.classes ); + } + glbl.$html.addClass( clsn.join( ' ' ) ); + + // Open + setTimeout(function(){ + that.vars.opened = true; + },this.conf.openingInterval); + + this.$menu.addClass( _c.current + ' ' + _c.opened ); + }; + + $[ _PLUGIN_ ].prototype._openFinish = function() + { + var that = this; + + // Callback + this.__transitionend( glbl.$page, + function() + { + that.$menu.trigger( _e.opened ); + }, this.conf.transitionDuration + ); + + // Opening + glbl.$html.addClass( _c.opening ); + this.$menu.trigger( _e.opening ); + }; + + $[ _PLUGIN_ ].prototype.close = function() + { + if ( !this.vars.opened ) + { + return false; + } + + var that = this; + + // Callback + this.__transitionend( glbl.$page, + function() + { + that.$menu + .removeClass( _c.current ) + .removeClass( _c.opened ); + + glbl.$html + .removeClass( _c.opened ) + .removeClass( _c.modal ) + .removeClass( _c.background ) + .removeClass( _c.mm( that.opts[ _ADDON_ ].position ) ) + .removeClass( _c.mm( that.opts[ _ADDON_ ].zposition ) ); + + if ( that.opts.classes ) + { + glbl.$html.removeClass( that.opts.classes ); + } + + // Restore style and position + glbl.$page.attr( 'style', glbl.$page.data( _d.style ) ); + + that.vars.opened = false; + that.$menu.trigger( _e.closed ); + + }, this.conf.transitionDuration + ); + + // Closing + glbl.$html.removeClass( _c.opening ); + this.$menu.trigger( _e.closing ); + + return 'close'; + }; + + $[ _PLUGIN_ ].prototype.setPage = function( $page ) + { + if ( !$page ) + { + $page = $(this.conf[ _ADDON_ ].pageSelector, glbl.$body); + if ( $page.length > 1 ) + { + $page = $page.wrapAll( '<' + this.conf[ _ADDON_ ].pageNodetype + ' />' ).parent(); + } + } + + $page.addClass( _c.page + ' ' + _c.slideout ); + glbl.$page = $page; + }; + + $[ _PLUGIN_ ].prototype[ _ADDON_ + '_initWindow' ] = function() + { + // Prevent tabbing + glbl.$wndw + .on( _e.keydown, + function( e ) + { + if ( glbl.$html.hasClass( _c.opened ) ) + { + if ( e.keyCode == 9 ) + { + e.preventDefault(); + return false; + } + } + } + ); + + // Set page min-height to window height + var _h = 0; + glbl.$wndw + .on( _e.resize, + function( e, force ) + { + if ( force || glbl.$html.hasClass( _c.opened ) ) + { + var nh = glbl.$wndw.height(); + if ( force || nh != _h ) + { + _h = nh; + glbl.$page.css( 'minHeight', nh ); + } + } + } + ); + + + // Once fired, it can be removed + $[ _PLUGIN_ ].prototype[ _ADDON_ + '_initWindow' ] = function() {}; + }; + + $[ _PLUGIN_ ].prototype[ _ADDON_ + '_initBlocker' ] = function() + { + var that = this; + var $blck = $( '
    ' ) + .appendTo( glbl.$body ); + + $blck + .on( _e.touchstart, + function( e ) + { + e.preventDefault(); + e.stopPropagation(); + $blck.trigger( _e.mousedown ); + } + ) + .on( _e.mousedown, + function( e ) + { + e.preventDefault(); + if ( !glbl.$html.hasClass( _c.modal ) ) + { + glbl.$allMenus.trigger( _e.close ); + } + } + ); + + + // Once fired, it can be removed + $[ _PLUGIN_ ].prototype[ _ADDON_ + '_initBlocker' ] = function() {}; + }; + + + var _c, _d, _e, glbl; + +})( jQuery ); \ No newline at end of file diff --git a/third_party/jquery-mmenu/js/jquery.mmenu.oncanvas.js b/third_party/jquery-mmenu/js/jquery.mmenu.oncanvas.js new file mode 100755 index 00000000..3856542a --- /dev/null +++ b/third_party/jquery-mmenu/js/jquery.mmenu.oncanvas.js @@ -0,0 +1,664 @@ +/* + * jQuery mmenu v4.7.5 + * @requires jQuery 1.7.0 or later + * + * mmenu.frebsite.nl + * + * Copyright (c) Fred Heusschen + * www.frebsite.nl + * + * Licensed under the MIT license: + * http://en.wikipedia.org/wiki/MIT_License + */ + + +(function( $ ) { + + var _PLUGIN_ = 'mmenu', + _VERSION_ = '4.7.5'; + + + // Plugin already excists + if ( $[ _PLUGIN_ ] ) + { + return; + } + + + // Global variables + var _c = {}, _d = {}, _e = {}, + plugin_initiated = false; + + var glbl = { + $wndw: null, + $html: null, + $body: null + }; + + + /* + Class + */ + $[ _PLUGIN_ ] = function( $menu, opts, conf ) + { + this.$menu = $menu; + this.opts = opts; + this.conf = conf; + this.vars = {}; + + if ( typeof this.___deprecated == 'function' ) + { + this.___deprecated(); + } + + this._initMenu(); + this._initAnchors(); + this._initEvents(); + + var $panels = this.$menu.children( this.conf.panelNodetype ); + + for ( var a in $[ _PLUGIN_ ].addons ) + { + // Add add-ons to plugin + $[ _PLUGIN_ ].addons[ a ]._add.call( this ); + $[ _PLUGIN_ ].addons[ a ]._add = function() {}; + + // Setup adds-on for menu + $[ _PLUGIN_ ].addons[ a ]._setup.call( this ); + } + + this._init( $panels ); + + if ( typeof this.___debug == 'function' ) + { + this.___debug(); + } + + return this; + }; + + $[ _PLUGIN_ ].version = _VERSION_; + + $[ _PLUGIN_ ].addons = {}; + + $[ _PLUGIN_ ].uniqueId = 0; + + $[ _PLUGIN_ ].defaults = { + classes : '', + slidingSubmenus : true, + onClick : { +// close : true, +// blockUI : null, +// preventDefault : null, + setSelected : true + } + }; + + $[ _PLUGIN_ ].configuration = { + panelNodetype : 'ul, ol, div', + transitionDuration : 400, + openingInterval : 25, + classNames : { + panel : 'Panel', + selected : 'Selected', + label : 'Label', + spacer : 'Spacer' + } + }; + $[ _PLUGIN_ ].prototype = { + + _init: function( $panels ) + { + $panels = $panels.not( '.' + _c.nopanel ); + $panels = this._initPanels( $panels ); + + for ( var a in $[ _PLUGIN_ ].addons ) + { + $[ _PLUGIN_ ].addons[ a ]._init.call( this, $panels ); + } + this._update(); + }, + + _initMenu: function() + { + var that = this; + + // Clone if needed + if ( this.opts.offCanvas && this.conf.clone ) + { + this.$menu = this.$menu.clone( true ); + this.$menu.add( this.$menu.find( '*' ) ).filter( '[id]' ).each( + function() + { + $(this).attr( 'id', _c.mm( $(this).attr( 'id' ) ) ); + } + ); + } + + // Strip whitespace + this.$menu.contents().each( + function() + { + if ( $(this)[ 0 ].nodeType == 3 ) + { + $(this).remove(); + } + } + ); + + this.$menu + .parent() + .addClass( _c.wrapper ); + + var clsn = [ _c.menu ]; + + // Add direction class + clsn.push( _c.mm( this.opts.slidingSubmenus ? 'horizontal' : 'vertical' ) ); + + // Add options classes + if ( this.opts.classes ) + { + clsn.push( this.opts.classes ); + } + + this.$menu.addClass( clsn.join( ' ' ) ); + }, + + _initPanels: function( $panels ) + { + var that = this; + + // Add List class + this.__findAddBack( $panels, 'ul, ol' ) + .not( '.' + _c.nolist ) + .addClass( _c.list ); + + var $lis = this.__findAddBack( $panels, '.' + _c.list ).find( '> li' ); + + // Refactor Selected class + this.__refactorClass( $lis, this.conf.classNames.selected, 'selected' ); + + // Refactor Label class + this.__refactorClass( $lis, this.conf.classNames.label, 'label' ); + + // Refactor Spacer class + this.__refactorClass( $lis, this.conf.classNames.spacer, 'spacer' ); + + // setSelected-event + $lis + .off( _e.setSelected ) + .on( _e.setSelected, + function( e, selected ) + { + e.stopPropagation(); + + $lis.removeClass( _c.selected ); + if ( typeof selected != 'boolean' ) + { + selected = true; + } + if ( selected ) + { + $(this).addClass( _c.selected ); + } + } + ); + + // Refactor Panel class + this.__refactorClass( this.__findAddBack( $panels, '.' + this.conf.classNames.panel ), this.conf.classNames.panel, 'panel' ); + + // Add Panel class + $panels + .add( this.__findAddBack( $panels, '.' + _c.list ).children().children().filter( this.conf.panelNodetype ).not( '.' + _c.nopanel ) ) + .addClass( _c.panel ); + + var $curpanels = this.__findAddBack( $panels, '.' + _c.panel ), + $allpanels = $('.' + _c.panel, this.$menu); + + // Add an ID to all panels + $curpanels + .each( + function( i ) + { + var $t = $(this), + id = $t.attr( 'id' ) || that.__getUniqueId(); + + $t.attr( 'id', id ); + } + ); + + // Add open and close links to menu items + $curpanels + .each( + function( i ) + { + var $t = $(this), + $u = $t.is( 'ul, ol' ) ? $t : $t.find( 'ul ,ol' ).first(), + $l = $t.parent(), + $a = $l.children( 'a, span' ), + $p = $l.closest( '.' + _c.panel ); + + if ( $l.parent().is( '.' + _c.list ) && !$t.data( _d.parent) ) + { + $t.data( _d.parent, $l ); + + var $btn = $( '' ).insertBefore( $a ); + if ( !$a.is( 'a' ) ) + { + $btn.addClass( _c.fullsubopen ); + } + if ( that.opts.slidingSubmenus ) + { + $u.prepend( '
  • ' + $a.text() + '
  • ' ); + } + } + } + ); + + if ( this.opts.slidingSubmenus ) + { + // Add opened-classes + var $selected = this.__findAddBack( $panels, '.' + _c.list ).find( '> li.' + _c.selected ); + $selected + .parents( 'li' ) + .removeClass( _c.selected ) + .end() + .add( $selected.parents( 'li' ) ) + .each( + function() + { + var $t = $(this), + $u = $t.find( '> .' + _c.panel ); + + if ( $u.length ) + { + $t.parents( '.' + _c.panel ).addClass( _c.subopened ); + $u.addClass( _c.opened ); + } + } + ) + .closest( '.' + _c.panel ) + .addClass( _c.opened ) + .parents( '.' + _c.panel ) + .addClass( _c.subopened ); + } + else + { + // Replace Selected-class with opened-class in parents from .Selected + var $selected = $('li.' + _c.selected, $allpanels); + $selected + .parents( 'li' ) + .removeClass( _c.selected ) + .end() + .add( $selected.parents( 'li' ) ) + .addClass( _c.opened ); + } + + // Set current opened + var $current = $allpanels.filter( '.' + _c.opened ); + if ( !$current.length ) + { + $current = $curpanels.first(); + } + $current + .addClass( _c.opened ) + .last() + .addClass( _c.current ); + + // Rearrange markup + if ( this.opts.slidingSubmenus ) + { + $curpanels + .not( $current.last() ) + .addClass( _c.hidden ) + .end() + .appendTo( this.$menu ); + } + + return $curpanels; + }, + + _initAnchors: function() + { + var that = this; + + glbl.$body + .on( _e.click, + 'a', + function( e ) + { + var $t = $(this), + fired = false, + inMenu = that.$menu.find( $t ).length; + + + // Find behavior for addons + for ( var a in $[ _PLUGIN_ ].addons ) + { + if ( $[ _PLUGIN_ ].addons[ a ]._clickAnchor && + ( fired = $[ _PLUGIN_ ].addons[ a ]._clickAnchor.call( that, $t, inMenu ) ) + ) { + break; + } + } + + // Open/Close panel + if ( !fired && inMenu ) + { + var _h = $t.attr( 'href' ) || ''; + if ( _h.slice( 0, 1 ) == '#' ) + { + try + { + if ( $(_h, that.$menu).is( '.' + _c.panel ) ) + { + fired = true; + $(_h).trigger( that.opts.slidingSubmenus ? _e.open : _e.toggle ); + } + } + catch( error ) {} + } + } + + if ( fired ) + { + e.preventDefault(); + } + + + // All other anchors in lists + if ( !fired && inMenu ) + { + if ( $t.is( '.' + _c.list + ' > li > a' ) + && !$t.is( '[rel="external"]' ) + && !$t.is( '[target="_blank"]' ) ) + { + + // Set selected item + if ( that.__valueOrFn( that.opts.onClick.setSelected, $t ) ) + { + $t.parent().trigger( _e.setSelected ); + } + + // Prevent default / don't follow link. Default: false + var preventDefault = that.__valueOrFn( that.opts.onClick.preventDefault, $t, _h.slice( 0, 1 ) == '#' ); + if ( preventDefault ) + { + e.preventDefault(); + } + + // Block UI. Default: false if preventDefault, true otherwise + if ( that.__valueOrFn( that.opts.onClick.blockUI, $t, !preventDefault ) ) + { + glbl.$html.addClass( _c.blocking ); + } + + // Close menu. Default: true if preventDefault, false otherwise + if ( that.__valueOrFn( that.opts.onClick.close, $t, preventDefault ) ) + { + that.$menu.trigger( _e.close ); + } + } + } + } + ); + }, + + _initEvents: function() + { + var that = this; + + this.$menu + .on( _e.toggle + ' ' + _e.open + ' ' + _e.close, + '.' + _c.panel, + function( e ) + { + e.stopPropagation(); + } + ); + + if ( this.opts.slidingSubmenus ) + { + this.$menu + .on( _e.open, + '.' + _c.panel, + function( e ) + { + return that._openSubmenuHorizontal( $(this) ); + } + ); + } + else + { + this.$menu + .on( _e.toggle, + '.' + _c.panel, + function( e ) + { + var $t = $(this); + $t.trigger( $t.parent().hasClass( _c.opened ) ? _e.close : _e.open ); + } + ) + .on( _e.open, + '.' + _c.panel, + function( e ) + { + $(this).parent().addClass( _c.opened ); + } + ) + .on( _e.close, + '.' + _c.panel, + function( e ) + { + $(this).parent().removeClass( _c.opened ); + } + ); + } + }, + + _openSubmenuHorizontal: function( $opening ) + { + if ( $opening.hasClass( _c.current ) ) + { + return false; + } + + var $panels = $('.' + _c.panel, this.$menu), + $current = $panels.filter( '.' + _c.current ); + + $panels + .removeClass( _c.highest ) + .removeClass( _c.current ) + .not( $opening ) + .not( $current ) + .addClass( _c.hidden ); + + if ( $opening.hasClass( _c.opened ) ) + { + $current + .addClass( _c.highest ) + .removeClass( _c.opened ) + .removeClass( _c.subopened ); + } + else + { + $opening + .addClass( _c.highest ); + + $current + .addClass( _c.subopened ); + } + + $opening + .removeClass( _c.hidden ) + .addClass( _c.current ); + + // Without the timeout, the animation won't work because the element had display: none; + setTimeout( + function() + { + $opening + .removeClass( _c.subopened ) + .addClass( _c.opened ); + }, this.conf.openingInterval + ); + + return 'open'; + }, + + _update: function( fn ) + { + if ( !this.updates ) + { + this.updates = []; + } + if ( typeof fn == 'function' ) + { + this.updates.push( fn ); + } + else + { + for ( var u = 0, l = this.updates.length; u < l; u++ ) + { + this.updates[ u ].call( this, fn ); + } + } + }, + + __valueOrFn: function( o, $e, d ) + { + if ( typeof o == 'function' ) + { + return o.call( $e[ 0 ] ); + } + if ( typeof o == 'undefined' && typeof d != 'undefined' ) + { + return d; + } + return o; + }, + + __refactorClass: function( $e, o, c ) + { + return $e.filter( '.' + o ).removeClass( o ).addClass( _c[ c ] ); + }, + + __findAddBack: function( $e, s ) + { + return $e.find( s ).add( $e.filter( s ) ); + }, + + __transitionend: function( $e, fn, duration ) + { + var _ended = false, + _fn = function() + { + if ( !_ended ) + { + fn.call( $e[ 0 ] ); + } + _ended = true; + }; + + $e.one( _e.transitionend, _fn ); + $e.one( _e.webkitTransitionEnd, _fn ); + setTimeout( _fn, duration * 1.1 ); + }, + + __getUniqueId: function() + { + return _c.mm( $[ _PLUGIN_ ].uniqueId++ ); + } + }; + + + /* + jQuery plugin + */ + $.fn[ _PLUGIN_ ] = function( opts, conf ) + { + // First time plugin is fired + if ( !plugin_initiated ) + { + _initPlugin(); + } + + // Extend options + opts = $.extend( true, {}, $[ _PLUGIN_ ].defaults, opts ); + conf = $.extend( true, {}, $[ _PLUGIN_ ].configuration, conf ); + + return this.each( + function() + { + var $menu = $(this); + if ( $menu.data( _PLUGIN_ ) ) + { + return; + } + $menu.data( _PLUGIN_, new $[ _PLUGIN_ ]( $menu, opts, conf ) ); + } + ); + }; + + + /* + SUPPORT + */ + $[ _PLUGIN_ ].support = { + touch: 'ontouchstart' in window || navigator.msMaxTouchPoints + }; + + + + function _initPlugin() + { + plugin_initiated = true; + + glbl.$wndw = $(window); + glbl.$html = $('html'); + glbl.$body = $('body'); + + // Classnames, Datanames, Eventnames + $.each( [ _c, _d, _e ], + function( i, o ) + { + o.add = function( c ) + { + c = c.split( ' ' ); + for ( var d in c ) + { + o[ c[ d ] ] = o.mm( c[ d ] ); + } + }; + } + ); + + // Classnames + _c.mm = function( c ) { return 'mm-' + c; }; + _c.add( 'wrapper menu inline panel nopanel list nolist subtitle selected label spacer current highest hidden opened subopened subopen fullsubopen subclose' ); + _c.umm = function( c ) + { + if ( c.slice( 0, 3 ) == 'mm-' ) + { + c = c.slice( 3 ); + } + return c; + }; + + // Datanames + _d.mm = function( d ) { return 'mm-' + d; }; + _d.add( 'parent' ); + + // Eventnames + _e.mm = function( e ) { return e + '.mm'; }; + _e.add( 'toggle open close setSelected transitionend webkitTransitionEnd mousedown mouseup touchstart touchmove touchend scroll resize click keydown keyup' ); + + $[ _PLUGIN_ ]._c = _c; + $[ _PLUGIN_ ]._d = _d; + $[ _PLUGIN_ ]._e = _e; + + $[ _PLUGIN_ ].glbl = glbl; + } + + +})( jQuery ); \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.buttonbars.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.buttonbars.scss new file mode 100755 index 00000000..c87d5266 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.buttonbars.scss @@ -0,0 +1,99 @@ +/* + jQuery.mmenu buttonbars addon CSS +*/ + +@import "../inc/variables"; + + +.mm-buttonbar +{ + border: 1px solid transparent; + border-radius: $mm_padding / 2; + text-align: center; + line-height: $mm_buttonbarHeight; + overflow: hidden; + display: block; + padding: 0; + margin: 0; + position: relative; + + @include mm_clearfix; + + > * + { + border-left: 1px solid transparent; + box-sizing: border-box; + display: block; + width: 100%; + height: 100%; + float: left; + + @include mm_ellipsis; + } + > a + { + text-decoration: none; + } + > input + { + position: absolute; + left: -1000px; + top: -1000px; + } + > input:checked + label + { + border-color: transparent !important; + } + + > *:first-child, + > input:first-child + * + { + border-left: none; + } + + + &.mm-buttonbar-2 > * + { + width: 50%; + } + &.mm-buttonbar-3 > * + { + width: 33.33%; + } + &.mm-buttonbar-4 > * + { + width: 25%; + } + &.mm-buttonbar-5 > * + { + width: 20%; + } +} + +.mm-header .mm-buttonbar +{ + margin-top: $mm_headerHeight - ( $mm_buttonbarHeight * 2 ); + margin-left: -( $mm_btnSize - $mm_padding ); + margin-right: -( $mm_btnSize - $mm_padding ); +} + +.mm-footer .mm-buttonbar +{ + border: none; + border-radius: none; + line-height: $mm_footerHeight; + margin: ( -$mm_padding ) ( -$mm_padding ) 0 ( -( $mm_padding * 2 ) ); + + > * + { + border-left: none; + } +} + +.mm-list > li > .mm-buttonbar +{ + margin: $mm_padding ( $mm_padding * 2 ); +} + + +@include mm_colors_buttonbars; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.counters.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.counters.scss new file mode 100755 index 00000000..79684166 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.counters.scss @@ -0,0 +1,53 @@ +/* + jQuery.mmenu counters addon CSS +*/ + +@import "../inc/variables"; + + +em.mm-counter +{ + font: inherit; + font-size: $mm_fontSize; + font-style: normal; + text-indent: 0; + line-height: $mm_btnSize / 2; + display: block; + margin-top: -( $mm_btnSize / 4 ); + position: absolute; + right: $mm_subopenWidth; + top: 50%; + + + a.mm-subopen + { + padding-left: $mm_counterWidth; + + + a, + + span + { + margin-right: $mm_counterWidth + $mm_subopenWidth; + } + } + + a.mm-fullsubopen + { + padding-left: 0; + } +} + +// vertical submenu +.mm-vertical +{ + em.mm-counter + { + top: ( $mm_btnSize / 4 ) + 2; + margin-top: 0; + } +} + +// Search +.mm-nosubresults > em.mm-counter +{ + display: none; +} + +@include mm_colors_counters; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.dragopen.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.dragopen.scss new file mode 100755 index 00000000..006e5c50 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.dragopen.scss @@ -0,0 +1,17 @@ +/* + jQuery.mmenu dragOpen addon CSS +*/ + +@import "../inc/variables"; + +html.mm-opened.mm-dragging +{ + .mm-menu, + .mm-page, + .mm-fixed-top, + .mm-fixed-bottom, + #mm-blocker + { + @include mm_webkit-prefix( "transition-duration", 0s ); + } +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.footer.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.footer.scss new file mode 100755 index 00000000..085424de --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.footer.scss @@ -0,0 +1,34 @@ +/* + jQuery.mmenu footer addon CSS +*/ + +@import "../inc/variables"; + + +.mm-footer +{ + background: inherit; + border-top: 1px solid transparent; + text-align: center; + line-height: $mm_footerHeight - ( $mm_padding * 2 ); + + box-sizing: border-box; + width: 100%; + height: $mm_footerHeight; + padding: $mm_padding $mm_padding 0 ( $mm_padding * 2 ); + position: absolute; + z-index: 2; + bottom: 0; + left: 0; +} + +.mm-menu.mm-hasfooter +{ + > .mm-panel:after + { + height: $mm_footerHeight + $mm_btnSize; + } +} + + +@include mm_colors_footer; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.header.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.header.scss new file mode 100755 index 00000000..5759deaa --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.header.scss @@ -0,0 +1,142 @@ +/* + jQuery.mmenu header addon CSS +*/ + +@import "../inc/variables"; + + +.mm-header +{ + background: inherit; + border-bottom: 1px solid transparent; + text-align: center; + line-height: $mm_btnSize / 2; + + box-sizing: border-box; + width: 100%; + height: $mm_headerHeight; + padding: 0 ( $mm_btnSize + $mm_padding ); + position: absolute; + z-index: 2; + top: 0; + left: 0; + + .mm-title, + .mm-prev, + .mm-next, + .mm-close + { + padding-top: $mm_headerPaddingTop; + } + .mm-title + { + @include mm_ellipsis; + + display: inline-block; + width: 100%; + position: relative; + } + + .mm-prev, + .mm-next, + .mm-close + { + text-decoration: none; + display: block; + box-sizing: border-box; + min-width: $mm_padding; + height: 100%; + position: absolute; + top: 0; + z-index: 1; + } + .mm-prev + { + padding-left: ( $mm_padding * 2 ); + padding-right: $mm_padding; + left: 0; + } + .mm-next, + .mm-close + { + padding-left: $mm_padding; + padding-right: ( $mm_padding * 2 ); + right: 0; + } + [href] + { + &.mm-prev:before, + &.mm-next:after + { + @include mm_arrow; + } + &.mm-prev:before + { + @include mm_arrow-prev; + margin-left: 2px; + margin-right: $mm_padding / 2; + } + &.mm-next:after, + &.mm-close:after + { + margin-left: $mm_padding / 2; + margin-right: -2px; + } + &.mm-next:after + { + @include mm_arrow-next; + } + &.mm-close:after + { + content: 'x'; + } + } +} + +.mm-menu.mm-hassearch .mm-header +{ + height: $mm_headerHeight - $mm_padding; + top: $mm_searchHeight; + + .mm-title, + .mm-prev, + .mm-next, + .mm-close + { + padding-top: $mm_headerPaddingTop - $mm_padding; + } +} + +$mm_paddingBeneathHeader: $mm_padding * 2 !default; +.mm-menu.mm-hasheader +{ + li.mm-subtitle + { + display: none; + } + > .mm-panel + { + padding-top: $mm_headerHeight + $mm_paddingBeneathHeader; + &.mm-list + { + padding-top: $mm_headerHeight; + } + > .mm-list:first-child + { + margin-top: -$mm_paddingBeneathHeader; + } + } + &.mm-hassearch > .mm-panel + { + padding-top: $mm_headerHeight + $mm_searchHeight + $mm_padding; + + &.mm-list + { + padding-top: $mm_headerHeight + $mm_searchHeight - $mm_padding; + } + } +} + + + +@include mm_colors_header; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.labels.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.labels.scss new file mode 100755 index 00000000..2b2f296f --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.labels.scss @@ -0,0 +1,40 @@ +/* + jQuery.mmenu labels addon CSS +*/ + +@import "../inc/variables"; + +.mm-list +{ + li.mm-label + { + > span + { + @include mm_ellipsis; + padding: 0; + line-height: $mm_labelHeight; + } + + &.mm-opened a.mm-subopen:after + { + @include mm_webkit-prefix( "transform", rotate( 45deg ) ); + } + } + li.mm-collapsed:not( .mm-uncollapsed ) + { + display: none; + } +} + +.mm-menu.mm-vertical .mm-list +{ + > li.mm-label + { + > a.mm-subopen:after + { + top: ( $mm_labelHeight / 2 ) - 4; + } + } +} + +@include mm_colors_labels; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.offcanvas.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.offcanvas.scss new file mode 100755 index 00000000..1a94d479 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.offcanvas.scss @@ -0,0 +1,70 @@ +/* + jQuery.mmenu offcanvas addon CSS +*/ + +@import "../inc/variables"; + + +// Animations +.mm-page +{ + box-sizing: border-box; + position: relative; + + -webkit-transition: -webkit-transform $mm_transitionDuration $mm_transitionFunction; + -ms-transition: -ms-transform $mm_transitionDuration $mm_transitionFunction; + transition: transform $mm_transitionDuration $mm_transitionFunction; +} + +// Container, Page, Blocker +html.mm-opened +{ + overflow: hidden; + position: relative; + + body + { + overflow: hidden; + } +} + +html.mm-background .mm-page +{ + background: inherit; +} +#mm-blocker +{ + background: rgba( 3, 2, 1, 0 ); + display: none; + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + z-index: 999999; +} +html.mm-opened, +html.mm-blocking +{ + #mm-blocker + { + display: block; + } +} + +// Menu +.mm-menu +{ + &.mm-offcanvas + { + display: none; + position: fixed; + } + &.mm-current + { + display: block; + } +} + + +@include mm_sizing; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.searchfield.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.searchfield.scss new file mode 100755 index 00000000..552f3456 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.searchfield.scss @@ -0,0 +1,125 @@ +/* + jQuery.mmenu searchfield addon CSS +*/ + +@import "../inc/variables"; + +.mm-search, +.mm-search input +{ + box-sizing: border-box; +} +.mm-list +{ + > li.mm-search + { + padding: $mm_padding; + margin-top: -( $mm_padding * 2 ); + } + > li.mm-subtitle + li.mm-search + { + margin-top: 0; + } +} +div.mm-panel > div.mm-search +{ + padding: 0 0 $mm_padding 0; +} +.mm-menu.mm-hasheader .mm-list > li.mm-search +{ + margin-top: 0; +} +.mm-menu > .mm-search +{ + background: inherit; + width: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 2; +} +.mm-search +{ + padding: $mm_padding; + + input + { + border: none; + border-radius: $mm_searchfieldHeight; + font: inherit; + font-size: $mm_fontSize; + line-height: $mm_searchfieldHeight; + outline: none; + display: block; + width: 100%; + height: $mm_searchfieldHeight; + margin: 0; + padding: 0 $mm_padding; + } + input::-ms-clear + { + display: none; + } +} + +.mm-menu .mm-noresultsmsg +{ + text-align: center; + font-size: round( $mm_fontSize * 1.5 ); + display: none; + padding: ( $mm_btnSize * 1.5 ) 0; + + &:after + { + border: none !important; + } +} +.mm-noresults .mm-noresultsmsg +{ + display: block; +} + +$mm_paddingBeneathHeader: $mm_padding * 2 !default; +.mm-menu +{ + li.mm-nosubresults > a.mm-subopen + { + display: none; + + + a, + + span + { + padding-right: 10px; + } + } + &.mm-hassearch + { + > .mm-panel + { + padding-top: $mm_searchHeight + $mm_paddingBeneathHeader; + + > .mm-list:first-child + { + margin-top: -$mm_paddingBeneathHeader; + } + } + } + &.mm-hasheader + { + > .mm-panel + { + > div.mm-search:first-child + { + margin-top: -$mm_padding; + + + .mm-list + { + padding-top: 0; + } + } + } + } +} + + +@include mm_colors_searchfield; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.toggles.scss b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.toggles.scss new file mode 100755 index 00000000..c8683dd5 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/addons/jquery.mmenu.toggles.scss @@ -0,0 +1,178 @@ +/* + jQuery.mmenu toggles addon CSS +*/ + +@import "../inc/variables"; + + +input.mm-toggle, +input.mm-check +{ + position: absolute; + left: -10000px; +} + +label.mm-toggle, +label.mm-check +{ + margin: 0; + position: absolute; + bottom: 50%; + z-index: 2; + + &:before + { + content: ''; + display: block; + } +} + +// styling +label.mm-toggle +{ + border-radius: $mm_toggleHeight; + width: $mm_toggleWidth; + height: $mm_toggleHeight; + margin-bottom: -( $mm_toggleHeight / 2 ); + + &:before + { + border-radius: $mm_toggleHeight; + width: $mm_toggleHeight - 2; + height: $mm_toggleHeight - 2; + margin: 1px; + } +} +input.mm-toggle:checked ~ label.mm-toggle:before +{ + float: right; +} + +label.mm-check +{ + width: $mm_checkWidth; + height: $mm_checkHeight; + margin-bottom: -( $mm_checkHeight / 2 ); + + &:before + { + border-left: 3px solid; + border-bottom: 3px solid; + width: 40%; + height: 20%; + margin: 25% 0 0 20%; + opacity: 0.1; + + @include mm-webkit-prefix( 'transform', rotate( -45deg ) ); + } +} +input.mm-check:checked ~ label.mm-check:before +{ + opacity: 1; +} + + +// vertical submenu +.mm-menu.mm-vertical .mm-list +{ + > li label + { + &.mm-toggle, + &.mm-check + { + bottom: auto; + margin-bottom: 0; + } + &.mm-toggle + { + top: ( $mm_btnSize - $mm_toggleHeight ) / 2; + + } + &.mm-check + { + top: ( $mm_btnSize - $mm_checkHeight ) / 2; + } + } +} + + +// positioning +label +{ + &.mm-toggle, + &.mm-check + { + right: $mm_padding * 2; + } +} +label.mm-toggle +{ + + a, + + span + { + margin-right: $mm_toggleWidth + ( $mm_padding * 2 ); + } +} +label.mm-check +{ + + a, + + span + { + margin-right: $mm_checkWidth + ( $mm_padding * 2 ); + } +} + +// positioning with subopen +a.mm-subopen + label +{ + &.mm-toggle, + &.mm-check + { + right: $mm_subopenWidth + $mm_padding; + } +} +a.mm-subopen + label.mm-toggle +{ + + a, + + span + { + margin-right: $mm_subopenWidth + $mm_toggleWidth + $mm_padding; + } +} +a.mm-subopen + label.mm-check +{ + + a, + + span + { + margin-right: $mm_subopenWidth + $mm_checkWidth + $mm_padding; + } +} + +// positioning with counter +em.mm-counter + a.mm-subopen + label +{ + &.mm-toggle, + &.mm-check + { + right: $mm_counterWidth + $mm_subopenWidth + $mm_padding; + } +} +em.mm-counter + a.mm-subopen + label.mm-toggle +{ + + a, + + span + { + margin-right: $mm_counterWidth + $mm_subopenWidth + $mm_toggleWidth + $mm_padding; + } +} +em.mm-counter + a.mm-subopen + label.mm-check +{ + + a, + + span + { + margin-right: $mm_counterWidth + $mm_subopenWidth + $mm_checkWidth + $mm_padding; + } +} + +@include mm_colors_toggles; +@include mm_colors_checks; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.effects.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.effects.scss new file mode 100755 index 00000000..78f9d5f1 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.effects.scss @@ -0,0 +1,152 @@ +/* + jQuery.mmenu effects extension CSS +*/ + +@import "../inc/variables"; + + +// Slide +html.mm-slide +{ + .mm-menu + { + -webkit-transition: -webkit-transform $mm_transitionDuration $mm_transitionFunction; + transition: transform $mm_transitionDuration $mm_transitionFunction; + } + + // Left + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', translateX( -$mm_subpanelOffset ) ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', translateX( 0% ) ); + } + + // Right + &.mm-right + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', translateX( $mm_subpanelOffset ) ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', translateX( 0% ) ); + } + } + + // Top + &.mm-top + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', translateY( -$mm_subpanelOffset ) ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', translateY( 0% ) ); + } + } + + // Bottom + &.mm-bottom + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', translateY( $mm_subpanelOffset ) ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', translateY( 0% ) ); + } + } +} + + +// Zoom menu +$mm_scaleDown: 0.7; +$mm_scaleUp: 1.5; +html.mm-zoom-menu +{ + .mm-menu + { + -webkit-transition: -webkit-transform $mm_transitionDuration $mm_transitionFunction; + transition: transform $mm_transitionDuration $mm_transitionFunction; + } + + // Left + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( $mm_scaleDown, $mm_scaleDown ) translateX( -$mm_subpanelOffset ) ); + @include mm_webkit-prefix( 'transform-origin', left center ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( 1, 1 ) translateX( 0% ) ); + } + + // Right + &.mm-right + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( $mm_scaleDown, $mm_scaleDown) translateX( $mm_subpanelOffset ) ); + @include mm_webkit-prefix( 'transform-origin', right center ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( 1, 1 ) translateX( 0% ) ); + } + } + + // Top + &.mm-top + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( $mm_scaleDown, $mm_scaleDown ) translateY( -$mm_subpanelOffset ) ); + @include mm_webkit-prefix( 'transform-origin', center top ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( 1, 1 ) translateY( 0% ) ); + } + } + + // Bottom + &.mm-bottom + { + &.mm-opened .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( $mm_scaleDown, $mm_scaleDown ) translateY( $mm_subpanelOffset ) ); + @include mm_webkit-prefix( 'transform-origin', center bottom ); + } + &.mm-opening .mm-menu + { + @include mm_webkit-prefix( 'transform', scale( 1, 1 ) translateY( 0% ) ); + } + } +} + + +// Zoom panels +html.mm-zoom-panels .mm-menu.mm-horizontal > .mm-panel +{ + @include mm_webkit-prefix( 'transform', scale( $mm_scaleUp, $mm_scaleUp ) translateX( 100% ) ); + @include mm_webkit-prefix( 'transform-origin', left center ); + + -webkit-transition-property: -webkit-transform, left; + transition-property: transform, left; + + &.mm-opened + { + @include mm_webkit-prefix( 'transform', scale( 1, 1 ) translateX( 0% ) ); + + &.mm-subopened + { + @include mm_webkit-prefix( 'transform', scale( $mm_scaleDown, $mm_scaleDown ) translateX( -$mm_subpanelOffset ) ); + } + } +} diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.fullscreen.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.fullscreen.scss new file mode 100755 index 00000000..e53fc601 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.fullscreen.scss @@ -0,0 +1,24 @@ +/* + jQuery.mmenu fullscreen extension CSS +*/ + +@import "../inc/variables"; + +$mm_fs_class : ".mm-fullscreen"; +$mm_fs_full : 1 !default; +$mm_fs_min : 140px !default; +$mm_fs_max : 10000px !default; + +@include mm_sizing( $mm_fs_class, + $mm_fs_full, $mm_fs_min, $mm_fs_max ); + +@include mm_sizing_right( $mm_fs_class, + $mm_fs_full, $mm_fs_min, $mm_fs_max); + +@include mm_sizing_zposition( $mm_fs_class, + $mm_fs_full, $mm_fs_min, $mm_fs_max ); + +html.mm-opened#{$mm_fs_class} .mm-page +{ + box-shadow: none !important; +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.iconbar.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.iconbar.scss new file mode 100755 index 00000000..1b7fada4 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.iconbar.scss @@ -0,0 +1,31 @@ +/* + jQuery.mmenu iconbar extension CSS +*/ + +@import "../inc/variables"; + + +$mm_ib_pageOffset: $mm_btnSize + ( $mm_padding * 2 ) !default; + +body +{ + overflow-x: hidden; +} + +.mm-page +{ + background: inherit; + min-height: 100vh; + + padding-right: $mm_ib_pageOffset; + @include mm-webkit-prefix( 'transform', translateX( $mm_ib_pageOffset ) ); +} + +.mm-menu +{ + &:first-child, + &.mm-current + { + display: block; + } +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.positioning.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.positioning.scss new file mode 100755 index 00000000..db752665 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.positioning.scss @@ -0,0 +1,98 @@ +/* + jQuery.mmenu position extension CSS +*/ + +@import "../inc/variables"; + +// top +// bottom +.mm-menu.mm-top, +.mm-menu.mm-bottom +{ + width: 100%; + min-width: 100%; + max-width: 100%; +} + +// right +.mm-menu.mm-right +{ + left: auto; + right: 0; +} + +// bottom +.mm-menu.mm-bottom +{ + top: auto; + bottom: 0; +} + +@include mm_sizing_right; + + + +/* + jQuery.mmenu z-position extension CSS +*/ + +// reset defaults +html.mm-front +{ + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( 0, 0 ) !important ); + z-index: 0; + } +} + +// styling +.mm-menu.mm-front +{ + z-index: 1; + box-shadow: 0 0 15px rgba( 0, 0, 0, 0.5 ); +} +html.mm-opened.mm-next .mm-page +{ + box-shadow: none; +} + +// animations +.mm-menu +{ + &.mm-front, + &.mm-next + { + -webkit-transition: -webkit-transform $mm_transitionDuration $mm_transitionFunction; + transition: transform $mm_transitionDuration $mm_transitionFunction; + + @include mm-webkit-prefix( 'transform', translate( -100%, 0 ) ); + + &.mm-right + { + @include mm-webkit-prefix( 'transform', translate( 100%, 0 ) ); + } + } + &.mm-front + { + &.mm-top + { + @include mm-webkit-prefix( 'transform', translate( 0, -100% ) ); + } + &.mm-bottom + { + @include mm-webkit-prefix( 'transform', translate( 0, 100% ) ); + } + } +} +html.mm-opening .mm-menu +{ + &.mm-front, + &.mm-next + { + @include mm-webkit-prefix( 'transform', translate( 0, 0 ) ); + } +} + +@include mm_sizing_zposition; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.themes.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.themes.scss new file mode 100755 index 00000000..ac299bc8 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.themes.scss @@ -0,0 +1,82 @@ +/* + jQuery.mmenu themes extension CSS +*/ + +@import "../inc/variables"; + +@mixin mm_apply_theme() +{ + @include mm_colors( $mm_t_cls, + $mm_t_backgroundColor, $mm_t_pageShadow, + $mm_t_textColor, $mm_t_dimmedTextColor, + $mm_t_emphasizedBackgroundColor, $mm_t_highlightedBackgroundColor, + $mm_t_borderColor ); + + @include mm_colors_buttonbars( $mm_t_cls, + $mm_t_backgroundColor, + $mm_t_textColor ); + + @include mm_colors_checks( $mm_t_cls, + $mm_t_textColor ); + + @include mm_colors_counters( $mm_t_cls, + $mm_t_dimmedTextColor ); + + @include mm_colors_footer( $mm_t_cls, + $mm_t_dimmedTextColor, + $mm_t_borderColor ); + + @include mm_colors_header( $mm_t_cls, + $mm_t_dimmedTextColor, + $mm_t_borderColor ); + + @include mm_colors_labels( $mm_t_cls, + $mm_t_highlightedBackgroundColor ); + + @include mm_colors_searchfield( $mm_t_cls, + $mm_t_inputBackgroundColor, $mm_t_textColor, + $mm_t_dimmedTextColor ); + + @include mm_colors_toggles( $mm_t_cls, + $mm_t_backgroundColor, + $mm_t_borderColor ); +} + + +// Light +$mm_t_cls : ".mm-light"; +$mm_t_pageShadow : 0 0 10px rgba( 0, 0, 0, 0.3 ); +$mm_t_borderColor : rgba( 0, 0, 0, 0.1 ); +$mm_t_backgroundColor : #f3f3f3; +$mm_t_emphasizedBackgroundColor : rgba( 255, 255, 255, 0.6 ); +$mm_t_highlightedBackgroundColor: rgba( 0, 0, 0, 0.03 ); +$mm_t_textColor : rgba( 0, 0, 0, 0.6 ); +$mm_t_dimmedTextColor : rgba( 0, 0, 0, 0.3 ); +$mm_t_inputBackgroundColor : rgba( 0, 0, 0, 0.1 ); +@include mm_apply_theme; + + +// White +$mm_t_cls : ".mm-white"; +$mm_t_pageShadow : 0 0 10px rgba( 0, 0, 0, 0.3 ); +$mm_t_borderColor : rgba( 0, 0, 0, 0.1 ); +$mm_t_backgroundColor : #fff; +$mm_t_emphasizedBackgroundColor : rgba( 0, 0, 0, 0.06 ); +$mm_t_highlightedBackgroundColor: rgba( 0, 0, 0, 0.03 ); +$mm_t_textColor : rgba( 0, 0, 0, 0.6 ); +$mm_t_dimmedTextColor : rgba( 0, 0, 0, 0.3 ); +$mm_t_inputBackgroundColor : rgba( 0, 0, 0, 0.1 ); +@include mm_apply_theme; + + +// Black +$mm_t_cls : ".mm-black"; +$mm_t_pageShadow : none; +$mm_t_borderColor : rgba( 255, 255, 255, 0.2 ); +$mm_t_backgroundColor : #000; +$mm_t_emphasizedBackgroundColor : rgba( 255, 255, 255, 0.25 ); +$mm_t_highlightedBackgroundColor: rgba( 255, 255, 255, 0.15 ); +$mm_t_textColor : rgba( 255, 255, 255, 0.6 ); +$mm_t_dimmedTextColor : rgba( 255, 255, 255, 0.3 ); +$mm_t_inputBackgroundColor : rgba( 255, 255, 255, 0.3 ); +@include mm_apply_theme; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.widescreen.scss b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.widescreen.scss new file mode 100755 index 00000000..c6ad9841 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/extensions/jquery.mmenu.widescreen.scss @@ -0,0 +1,60 @@ +/* + jQuery.mmenu widescreen extension CSS + + To use on widescreens only, include it using a mediaquery: + +*/ + +@import "../inc/variables"; + +$mm_ws_menuWidth: 0.3 !default; + + +// Positioning and sizing +html, body +{ + overflow: auto; +} +body +{ + padding-left: percentage( $mm_ws_menuWidth ) !important; + position: relative; +} +#mm-blocker +{ + display: none !important; +} +.mm-page +{ + box-shadow: none !important; + background: inherit; + + box-sizing: border-box; + min-height: 100vh; + height: auto !important; + margin: 0 !important; + position: relative !important; + top: 0 !important; + z-index: 1; +} +.mm-menu +{ + width: percentage( $mm_ws_menuWidth ) !important; + z-index: 0; + + &.mm-top, + &.mm-right, + &.mm-bottom + { + top: 0 !important; + right: auto !important; + bottom: auto !important; + left: 0 !important; + } + + &:first-child, + &.mm-current + { + display: block; + } +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/inc/_colors.scss b/third_party/jquery-mmenu/sass/mmenu/inc/_colors.scss new file mode 100755 index 00000000..d05565e5 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/inc/_colors.scss @@ -0,0 +1,198 @@ +@mixin mm_colors( $cls: "", + $baseBg: $mm_backgroundColor, $pageShadow: $mm_pageShadow, + $color: $mm_textColor, $dimmedColor: $mm_dimmedTextColor, + $emphasizedBg: $mm_emphasizedBackgroundColor, $highlightedBg: $mm_highlightedBackgroundColor, + $borderColor: $mm_borderColor +) { + html.mm-opened#{$cls} .mm-page + { + box-shadow: $pageShadow; + } + .mm-menu#{$cls} + { + background: $baseBg; + color: $color; + + .mm-list + { + > li:after + { + border-color: $borderColor; + } + > li + { + > a + { + &.mm-subclose + { + background: $emphasizedBg; + color: $dimmedColor; + } + &.mm-subopen:after, + &.mm-subclose:before + { + border-color: $dimmedColor; + } + &.mm-subopen:before + { + border-color: $borderColor; + } + } + } + > li.mm-selected + { + > a:not(.mm-subopen), + > span + { + background: $emphasizedBg; + } + } + > li.mm-label + { + background: $highlightedBg; + } + } + + &.mm-vertical .mm-list + { + li.mm-opened + { + > a.mm-subopen, + > ul + { + background: $highlightedBg; + } + } + } + } +} + +@mixin mm_colors_buttonbars( $cls: "", + $baseBg: $mm_backgroundColor, + $color: $mm_textColor +) { + .mm-menu#{$cls} + { + .mm-buttonbar + { + border-color: $color; + background: $baseBg; + + > * + { + border-color: $color; + } + + > input:checked + label + { + background: $color; + color: $baseBg; + } + } + } +} + +@mixin mm_colors_checks( $cls: "", + $color: $mm_textColor +) { + .mm-menu#{$cls} label.mm-check:before + { + border-color: $color; + } +} + +@mixin mm_colors_counters( $cls: "", + $dimmedColor: $mm_dimmedTextColor +) { + .mm-menu#{$cls} em.mm-counter + { + color: $dimmedColor; + } +} + +@mixin mm_colors_footer( $cls: "", + $dimmedColor: $mm_dimmedTextColor, + $borderColor: $mm_borderColor +) { + .mm-menu#{$cls} + { + .mm-footer + { + border-color: $borderColor; + color: $dimmedColor; + } + } +} + +@mixin mm_colors_header( $cls: "", + $dimmedColor: $mm_dimmedTextColor, + $borderColor: $mm_borderColor +) { + .mm-menu#{$cls} + { + .mm-header + { + border-color: $borderColor; + color: $dimmedColor; + + .mm-prev:before, + .mm-next:after, + .mm-close:after + { + border-color: $dimmedColor; + } + } + } +} + +@mixin mm_colors_labels( $cls: "", + $highlightedBg: $mm_highlightedBackgroundColor +) { + .mm-menu#{$cls} + { + .mm-list li.mm-label > div > div + { + background: $highlightedBg; + } + } +} + +@mixin mm_colors_searchfield( $cls: "", + $inputBg: $mm_dimmedTextColor, $color: $mm_textColor, + $dimmedColor: $mm_dimmedTextColor +) { + .mm-menu#{$cls} + { + .mm-search input + { + background: $inputBg; + color: $color; + } + .mm-noresultsmsg + { + color: $dimmedColor; + } + } +} + +@mixin mm_colors_toggles( $cls: "", + $buttonBg: $mm_backgroundColor, + $offBg: $mm_borderColor, $onBg: $mm_toggleCheckedColor +) { + .mm-menu#{$cls} + { + label.mm-toggle + { + background: $offBg; + + &:before + { + background: $buttonBg; + } + } + input.mm-toggle:checked ~ label.mm-toggle + { + background: $onBg; + } + } +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/inc/_mixins.scss b/third_party/jquery-mmenu/sass/mmenu/inc/_mixins.scss new file mode 100755 index 00000000..94915156 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/inc/_mixins.scss @@ -0,0 +1,87 @@ +// Arrows +@mixin mm_arrow +{ + content: ''; + border: 2px solid transparent; + display: inline-block; + width: 7px; + height: 7px; + + @include mm_webkit-prefix( "transform", rotate( -45deg ) ); +} +@mixin mm_arrow-prev +{ + border-right: none; + border-bottom: none; +} +@mixin mm_arrow-next +{ + border-top: none; + border-left: none; +} + + +// Borders +@mixin mm_border( $border, $pseudo, $size, $pos1, $pos2 ) +{ + &:#{$pseudo} + { + content: ''; + border-#{$border}-width: 1px; + border-#{$border}-style: solid; + display: block; + #{$size}: 100%; + position: absolute; + #{$pos1}: 0; + #{$pos2}: 0; + } +} +@mixin mm_border-top +{ + @include mm_border( "top", "before", "width", "top", "left" ); +} +@mixin mm_border-right +{ + @include mm_border( "right", "after", "height", "right", "top" ); +} +@mixin mm_border-bottom +{ + @include mm_border( "bottom", "after", "width", "bottom", "left" ); +} +@mixin mm_border-left +{ + @include mm_border( "left", "before", "height", "left", "top" ); +} + + +// Misc +@mixin mm_vendor-prefix( $prop, $val ) +{ + -webkit-#{$prop}: $val; + -moz-#{$prop}: $val; + -ms-#{$prop}: $val; + -o-#{$prop}: $val; + #{$prop}: $val; +} +@mixin mm_webkit-prefix( $prop, $val ) +{ +// we're not yet ready to drop vendor prefixes due to IE9 and older versions of FF + @include mm_vendor_prefix( $prop, $val ); +// -webkit-#{$prop}: $val; +// #{$prop}: $val; +} +@mixin mm_ellipsis() +{ + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +@mixin mm_clearfix() +{ + &:after + { + content: ''; + display: block; + clear: both; + } +} \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/inc/_sizing.scss b/third_party/jquery-mmenu/sass/mmenu/inc/_sizing.scss new file mode 100755 index 00000000..feadaedf --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/inc/_sizing.scss @@ -0,0 +1,92 @@ +// Sizing left (default) +@mixin mm_sizing( $cls: "", + $width: $mm_width, $minWidth: $mm_minWidth, $maxWidth: $mm_maxWidth +) { + .mm-menu#{$cls} + { + width: percentage( $width ); + min-width: $minWidth; + max-width: $maxWidth; + } + html.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( percentage( $width ), 0 ) ); + } + } + @media all and (max-width: $minWidth / $width ) { + html.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( $minWidth, 0 ) ); + } + } + } + @media all and (min-width: $maxWidth / $width ) { + html.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( $maxWidth, 0 ) ); + } + } + } +} + +// Sizing right +@mixin mm_sizing_right( $cls: "", + $width: $mm_width, $minWidth: $mm_minWidth, $maxWidth: $mm_maxWidth +) { + html.mm-right.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( -( percentage( $width ) ), 0 ) ); + } + } + @media all and ( max-width: $minWidth / $width ) { + html.mm-right.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( -$minWidth, 0 ) ); + } + } + } + @media all and ( min-width: $maxWidth / $width ) { + html.mm-right.mm-opening#{$cls} + { + .mm-page, + #mm-blocker + { + @include mm-webkit-prefix( 'transform', translate( -$maxWidth, 0 ) ); + } + } + } +} + +// Sizing z-position +@mixin mm_sizing_zposition( $cls: "", + $height: $mm_height, $minHeight: $mm_minHeight, $maxHeight: $mm_maxHeight +) { + + // top + // bottom + .mm-menu.mm-front#{$cls} + { + &.mm-top, + &.mm-bottom + { + height: percentage( $height ); + min-height: $minHeight; + max-height: $maxHeight; + } + } +} diff --git a/third_party/jquery-mmenu/sass/mmenu/inc/_variables.scss b/third_party/jquery-mmenu/sass/mmenu/inc/_variables.scss new file mode 100755 index 00000000..078894f9 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/inc/_variables.scss @@ -0,0 +1,58 @@ +// Animations +$mm_transitionDuration: 0.4s !default; +$mm_transitionFunction: ease !default; + +// Sizes +$mm_width : 0.8 !default; +$mm_minWidth : 140px !default; +$mm_maxWidth : 440px !default; + +$mm_height : 0.8 !default; +$mm_minHeight : 140px !default; +$mm_maxHeight : 880px !default; + +$mm_btnSize : 40px !default; +$mm_padding : 10px !default; +$mm_fontSize : 14px !default; + +$mm_subpanelOffset : 30% !default; +$mm_subopenWidth : $mm_btnSize !default; + +// Addon sizes +$mm_buttonbarHeight : $mm_btnSize - ( $mm_padding * 2 ) !default; + +$mm_counterWidth : $mm_btnSize !default; + +$mm_toggleHeight : $mm_btnSize - $mm_padding !default; +$mm_toggleWidth : ( $mm_toggleHeight * 2 ) - $mm_padding !default; + +$mm_checkHeight : $mm_btnSize - $mm_padding !default; +$mm_checkWidth : $mm_btnSize - $mm_padding !default; + +$mm_footerHeight : $mm_btnSize; + +$mm_headerHeight : $mm_btnSize * 1.5 !default; +$mm_headerPaddingTop : $mm_headerHeight / 2 !default; + +$mm_labelHeight : ( $mm_btnSize / 2 ) + ( $mm_padding / 2 ) !default; + +$mm_searchHeight : $mm_btnSize + $mm_padding !default; +$mm_searchfieldHeight : $mm_searchHeight - ( $mm_padding * 2 ) !default; + +// Colors +$mm_pageShadow : none; //0 0 20px rgba( 0, 0, 0, 0.5 ) !default; +$mm_borderColor : rgba( 0, 0, 0, 0.15 ) !default; +$mm_backgroundColor : #222 !default; +$mm_emphasizedBackgroundColor : rgba( 0, 0, 0, 0.1 ) !default; +$mm_highlightedBackgroundColor : rgba( 255, 255, 255, 0.05 ) !default; +$mm_textColor : rgba( 255, 255, 255, 0.8 ) !default; +$mm_dimmedTextColor : rgba( 255, 255, 255, 0.5 ) !default; + +// Addon colors +$mm_toggleCheckedColor : #4bd963 !default; + + + +@import "mixins"; +@import "sizing"; +@import "colors"; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.all.scss b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.all.scss new file mode 100755 index 00000000..21980343 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.all.scss @@ -0,0 +1,15 @@ +@import "jquery.mmenu"; + +@import "addons/jquery.mmenu.buttonbars"; +@import "addons/jquery.mmenu.counters"; +@import "addons/jquery.mmenu.dragopen"; +@import "addons/jquery.mmenu.footer"; +@import "addons/jquery.mmenu.header"; +@import "addons/jquery.mmenu.labels"; +@import "addons/jquery.mmenu.searchfield"; +@import "addons/jquery.mmenu.toggles"; + +@import "extensions/jquery.mmenu.effects"; +@import "extensions/jquery.mmenu.fullscreen"; +@import "extensions/jquery.mmenu.positioning"; +@import "extensions/jquery.mmenu.themes"; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.oncanvas.scss b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.oncanvas.scss new file mode 100755 index 00000000..27a65cd3 --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.oncanvas.scss @@ -0,0 +1,298 @@ +/* + jQuery.mmenu panels CSS +*/ +@import "inc/variables"; + + +// Animations +.mm-menu.mm-horizontal > .mm-panel +{ + -webkit-transition: -webkit-transform $mm_transitionDuration $mm_transitionFunction; + transition: transform $mm_transitionDuration $mm_transitionFunction; +} + + +// Generic classes +.mm-menu .mm-hidden +{ + display: none; +} + + +// Container +.mm-wrapper +{ + overflow-x: hidden; + position: relative; +} + +// Menu +.mm-menu, +.mm-menu > .mm-panel +{ + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + z-index: 0; +} +.mm-menu +{ + background: inherit; + display: block; + overflow: hidden; + padding: 0; + + > .mm-panel + { + background: inherit; + + -webkit-overflow-scrolling: touch; + overflow: scroll; + overflow-x: hidden; + overflow-y: auto; + + box-sizing: border-box; + padding: $mm_padding * 2; + + @include mm_webkit-prefix( 'transform', translateX( 100% ) ); + + &.mm-opened + { + @include mm_webkit-prefix( 'transform', translateX( 0% ) ); + } + &.mm-subopened + { + @include mm_webkit-prefix( 'transform', translateX( -$mm_subpanelOffset ) ); + } + &.mm-highest + { + z-index: 1; + } + } + + // Lists + .mm-list + { + padding: ( $mm_btnSize / 2 ) 0; + } + > .mm-list + { + padding-bottom: 0; + + &:after + { + content: ''; + display: block; + height: $mm_btnSize; + } + } +} +.mm-panel > .mm-list +{ + margin-left: -( $mm_padding * 2 ); + margin-right: -( $mm_padding * 2 ); + + &:first-child + { + padding-top: 0; + } +} + +.mm-list, +.mm-list > li +{ + list-style: none; + display: block; + padding: 0; + margin: 0; +} +.mm-list +{ + font: inherit; + font-size: $mm_fontSize; + + a, + a:hover + { + text-decoration: none; + } + + > li + { + position: relative; + + > a, + > span + { + @include mm_ellipsis; + + color: inherit; + line-height: $mm_btnSize - ( $mm_padding * 2 ); + display: block; + padding: $mm_padding $mm_padding $mm_padding ( $mm_padding * 2 ); + margin: 0; + } + } + + > li:not(.mm-subtitle):not(.mm-label):not(.mm-search):not(.mm-noresults) + { + @include mm_border-bottom; + + &:after + { + width: auto; + margin-left: ( $mm_padding * 2 ); + position: relative; + left: auto; + } + } + + // subopen/close + a.mm-subopen + { + @include mm_border-left; + + background: rgba( 3, 2, 1, 0 ); + width: $mm_subopenWidth; + height: 100%; + padding: 0; + position: absolute; + right: 0; + top: 0; + z-index: 2; + + &.mm-fullsubopen + { + width: 100%; + + &:before + { + border-left: none; + } + } + + + a, + + span + { + padding-right: ( $mm_padding / 2 ); + margin-right: $mm_subopenWidth; + } + } + + > li.mm-selected + { + > a.mm-subopen + { + background: transparent; + } + > a.mm-fullsubopen + { + + a, + + span + { + padding-right: $mm_btnSize + ( $mm_padding / 2 ); + margin-right: 0; + } + } + } + + a.mm-subclose + { + text-indent: $mm_btnSize - ( $mm_padding * 2 ); + padding-top: ( $mm_btnSize / 2 ) + $mm_padding; + margin-top: -( $mm_btnSize / 2 ); + } + + // Labels + > li.mm-label + { + @include mm_ellipsis; + + font-size: 10px; + text-transform: uppercase; + text-indent: $mm_padding * 2; + line-height: $mm_labelHeight; + padding-right: $mm_padding / 2; + } + + // Spacers + > li.mm-spacer + { + padding-top: $mm_btnSize; + + &.mm-label + { + padding-top: $mm_labelHeight; + } + } + + // Arrows + a.mm-subopen:after, + a.mm-subclose:before + { + @include mm_arrow; + + margin-bottom: -5px; + position: absolute; + bottom: 50%; + } + a.mm-subopen:after + { + @include mm_arrow-next; + + right: 18px; + } + a.mm-subclose:before + { + @include mm_arrow-prev; + + margin-bottom: -( $mm_padding * 2 ) + 5; + left: 22px; + } +} + +// vertical submenu +.mm-menu.mm-vertical .mm-list +{ + .mm-panel + { + display: none; + padding: $mm_padding 0 $mm_padding $mm_padding; + + li:last-child:after + { + border-color: transparent; + } + } + li.mm-opened > .mm-panel + { + display: block; + } + > li + { + > a.mm-subopen + { + height: $mm_btnSize; + &:after + { + top: ( $mm_btnSize / 2 ) - 4; + bottom: auto; + } + } + &.mm-opened + { + > a.mm-subopen:after + { + @include mm_webkit-prefix( "transform", rotate( 45deg ) ); + } + } + &.mm-label > a.mm-subopen + { + height: $mm_labelHeight; + } + } +} + +@include mm_colors; \ No newline at end of file diff --git a/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.scss b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.scss new file mode 100755 index 00000000..ed6efe3b --- /dev/null +++ b/third_party/jquery-mmenu/sass/mmenu/jquery.mmenu.scss @@ -0,0 +1,6 @@ +/* + jQuery.mmenu CSS +*/ + +@import "jquery.mmenu.oncanvas"; +@import "addons/jquery.mmenu.offcanvas"; diff --git a/third_party/jquery-scrollto/LICENSE b/third_party/jquery-scrollto/LICENSE new file mode 100644 index 00000000..50293073 --- /dev/null +++ b/third_party/jquery-scrollto/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2007 Ariel Flesler + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/jquery-scrollto/jquery.scrollTo.js b/third_party/jquery-scrollto/jquery.scrollTo.js new file mode 100755 index 00000000..8b88b8ea --- /dev/null +++ b/third_party/jquery-scrollto/jquery.scrollTo.js @@ -0,0 +1,187 @@ +/*! + * jQuery.scrollTo + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * http://flesler.blogspot.com/2007/10/jqueryscrollto.html + * @projectDescription Easy element scrolling using jQuery. + * @author Ariel Flesler + * @version 1.4.14 + */ +;(function (define) { + 'use strict'; + + define(['jquery'], function ($) { + + var $scrollTo = $.scrollTo = function( target, duration, settings ) { + return $(window).scrollTo( target, duration, settings ); + }; + + $scrollTo.defaults = { + axis:'xy', + duration: 0, + limit:true + }; + + // Returns the element that needs to be animated to scroll the window. + // Kept for backwards compatibility (specially for localScroll & serialScroll) + $scrollTo.window = function( scope ) { + return $(window)._scrollable(); + }; + + // Hack, hack, hack :) + // Returns the real elements to scroll (supports window/iframes, documents and regular nodes) + $.fn._scrollable = function() { + return this.map(function() { + var elem = this, + isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1; + + if (!isWin) + return elem; + + var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem; + + return /webkit/i.test(navigator.userAgent) || doc.compatMode == 'BackCompat' ? + doc.body : + doc.documentElement; + }); + }; + + $.fn.scrollTo = function( target, duration, settings ) { + if (typeof duration == 'object') { + settings = duration; + duration = 0; + } + if (typeof settings == 'function') + settings = { onAfter:settings }; + + if (target == 'max') + target = 9e9; + + settings = $.extend( {}, $scrollTo.defaults, settings ); + // Speed is still recognized for backwards compatibility + duration = duration || settings.duration; + // Make sure the settings are given right + settings.queue = settings.queue && settings.axis.length > 1; + + if (settings.queue) + // Let's keep the overall duration + duration /= 2; + settings.offset = both( settings.offset ); + settings.over = both( settings.over ); + + return this._scrollable().each(function() { + // Null target yields nothing, just like jQuery does + if (target == null) return; + + var elem = this, + $elem = $(elem), + targ = target, toff, attr = {}, + win = $elem.is('html,body'); + + switch (typeof targ) { + // A number will pass the regex + case 'number': + case 'string': + if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) { + targ = both( targ ); + // We are done + break; + } + // Relative/Absolute selector, no break! + targ = win ? $(targ) : $(targ, this); + if (!targ.length) return; + case 'object': + // DOMElement / jQuery + if (targ.is || targ.style) + // Get the real position of the target + toff = (targ = $(targ)).offset(); + } + + var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset; + + $.each( settings.axis.split(''), function( i, axis ) { + var Pos = axis == 'x' ? 'Left' : 'Top', + pos = Pos.toLowerCase(), + key = 'scroll' + Pos, + old = elem[key], + max = $scrollTo.max(elem, axis); + + if (toff) {// jQuery / DOMElement + attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] ); + + // If it's a dom element, reduce the margin + if (settings.margin) { + attr[key] -= parseInt(targ.css('margin'+Pos)) || 0; + attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0; + } + + attr[key] += offset[pos] || 0; + + if(settings.over[pos]) + // Scroll to a fraction of its width/height + attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos]; + } else { + var val = targ[pos]; + // Handle percentage values + attr[key] = val.slice && val.slice(-1) == '%' ? + parseFloat(val) / 100 * max + : val; + } + + // Number or 'number' + if (settings.limit && /^\d+$/.test(attr[key])) + // Check the limits + attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max ); + + // Queueing axes + if (!i && settings.queue) { + // Don't waste time animating, if there's no need. + if (old != attr[key]) + // Intermediate animation + animate( settings.onAfterFirst ); + // Don't animate this axis again in the next iteration. + delete attr[key]; + } + }); + + animate( settings.onAfter ); + + function animate( callback ) { + $elem.animate( attr, duration, settings.easing, callback && function() { + callback.call(this, targ, settings); + }); + } + }).end(); + }; + + // Max scrolling position, works on quirks mode + // It only fails (not too badly) on IE, quirks mode. + $scrollTo.max = function( elem, axis ) { + var Dim = axis == 'x' ? 'Width' : 'Height', + scroll = 'scroll'+Dim; + + if (!$(elem).is('html,body')) + return elem[scroll] - $(elem)[Dim.toLowerCase()](); + + var size = 'client' + Dim, + html = elem.ownerDocument.documentElement, + body = elem.ownerDocument.body; + + return Math.max( html[scroll], body[scroll] ) - Math.min( html[size] , body[size] ); + }; + + function both( val ) { + return $.isFunction(val) || $.isPlainObject(val) ? val : { top:val, left:val }; + } + + // AMD requirement + return $scrollTo; + }) +}(typeof define === 'function' && define.amd ? define : function (deps, factory) { + if (typeof module !== 'undefined' && module.exports) { + // Node + module.exports = factory(require('jquery')); + } else { + factory(jQuery); + } +})); diff --git a/third_party/jquery-vide/LICENSE b/third_party/jquery-vide/LICENSE new file mode 100644 index 00000000..efc39113 --- /dev/null +++ b/third_party/jquery-vide/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ilya Makarov, http://vodkabears.github.io + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/jquery-vide/jquery.vide.js b/third_party/jquery-vide/jquery.vide.js new file mode 100755 index 00000000..ff0b859c --- /dev/null +++ b/third_party/jquery-vide/jquery.vide.js @@ -0,0 +1,442 @@ +/* + * Vide - v0.3.0 + * Easy as hell jQuery plugin for video backgrounds. + * http://vodkabears.github.io/vide/ + * + * Made by Ilya Makarov + * Under MIT License + */ +!(function($, window, document, navigator) { + "use strict"; + + /** + * Vide settings + * @private + */ + var pluginName = "vide", + defaults = { + volume: 1, + playbackRate: 1, + muted: true, + loop: true, + autoplay: true, + position: "50% 50%", + posterType: "detect", + resizing: true + }, + + // is iOs? + isIOS = /iPad|iPhone|iPod/i.test(navigator.userAgent), + + // is Android? + isAndroid = /Android/i.test(navigator.userAgent); + + /** + * Parse string with options + * @param {String} str + * @returns {Object|String} + * @private + */ + function parseOptions(str) { + var obj = {}, + delimiterIndex, + option, + prop, val, + arr, len, + i; + + // remove spaces around delimiters and split + arr = str.replace(/\s*:\s*/g, ":").replace(/\s*,\s*/g, ",").split(","); + + // parse string + for (i = 0, len = arr.length; i < len; i++) { + option = arr[i]; + + // Ignore urls and string without colon delimiters + if (option.search(/^(http|https|ftp):\/\//) !== -1 || + option.search(":") === -1) + { + break; + } + + delimiterIndex = option.indexOf(":"); + prop = option.substring(0, delimiterIndex); + val = option.substring(delimiterIndex + 1); + + // if val is an empty string, make it undefined + if (!val) { + val = undefined; + } + + // convert string value if it is like a boolean + if (typeof val === "string") { + val = val === "true" || (val === "false" ? false : val); + } + + // convert string value if it is like a number + if (typeof val === "string") { + val = !isNaN(val) ? +val : val; + } + + obj[prop] = val; + } + + // if nothing is parsed + if (prop == null && val == null) { + return str; + } + + return obj; + } + + /** + * Parse position option + * @param {String} str + * @returns {Object} + * @private + */ + function parsePosition(str) { + str = "" + str; + + // default value is a center + var args = str.split(/\s+/), + x = "50%", y = "50%", + len, arg, + i; + + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + + // convert values + if (arg === "left") { + x = "0%"; + } else if (arg === "right") { + x = "100%"; + } else if (arg === "top") { + y = "0%"; + } else if (arg === "bottom") { + y = "100%"; + } else if (arg === "center") { + if (i === 0) { + x = "50%"; + } else { + y = "50%"; + } + } else { + if (i === 0) { + x = arg; + } else { + y = arg; + } + } + } + + return { x: x, y: y }; + } + + /** + * Search poster + * @param {String} path + * @param {Function} callback + * @private + */ + function findPoster(path, callback) { + var onLoad = function() { + callback(this.src); + }; + + $("").load(onLoad); + $("").load(onLoad); + $("").load(onLoad); + $("").load(onLoad); + } + + /** + * Vide constructor + * @param {HTMLElement} element + * @param {Object|String} path + * @param {Object|String} options + * @constructor + */ + function Vide(element, path, options) { + this.$element = $(element); + + // parse path + if (typeof path === "string") { + path = parseOptions(path); + } + + // parse options + if (!options) { + options = {}; + } else if (typeof options === "string") { + options = parseOptions(options); + } + + // remove extension + if (typeof path === "string") { + path = path.replace(/\.\w*$/, ""); + } else if (typeof path === "object") { + for (var i in path) { + if (path.hasOwnProperty(i)) { + path[i] = path[i].replace(/\.\w*$/, ""); + } + } + } + + this.settings = $.extend({}, defaults, options); + this.path = path; + + this.init(); + } + + /** + * Initialization + * @public + */ + Vide.prototype.init = function() { + var vide = this, + position = parsePosition(vide.settings.position), + sources, + poster; + + // Set video wrapper styles + vide.$wrapper = $("
    ").css({ + "position": "absolute", + "z-index": -1, + "top": 0, + "left": 0, + "bottom": 0, + "right": 0, + "overflow": "hidden", + "-webkit-background-size": "cover", + "-moz-background-size": "cover", + "-o-background-size": "cover", + "background-size": "cover", + "background-repeat": "no-repeat", + "background-position": position.x + " " + position.y + }); + + // Get poster path + poster = vide.path; + if (typeof vide.path === "object") { + if (vide.path.poster) { + poster = vide.path.poster; + } else { + if (vide.path.mp4) { + poster = vide.path.mp4; + } else if (vide.path.webm) { + poster = vide.path.webm; + } else if (vide.path.ogv) { + poster = vide.path.ogv; + } + } + } + + // Set video poster + if (vide.settings.posterType === "detect") { + findPoster(poster, function(url) { + vide.$wrapper.css("background-image", "url(" + url + ")"); + }); + } else if (vide.settings.posterType !== "none") { + vide.$wrapper + .css("background-image", "url(" + poster + "." + vide.settings.posterType + ")"); + } + + // if parent element has a static position, make it relative + if (vide.$element.css("position") === "static") { + vide.$element.css("position", "relative"); + } + + vide.$element.prepend(vide.$wrapper); + + if (!isIOS && !isAndroid) { + sources = ""; + + if (typeof vide.path === "object") { + if (vide.path.mp4) { + sources += ""; + } + if (vide.path.webm) { + sources += ""; + } + if (vide.path.ogv) { + sources += ""; + } + + vide.$video = $(""); + } else { + vide.$video = $(""); + } + + // Disable visibility, while loading + vide.$video.css("visibility", "hidden"); + + // Set video properties + vide.$video.prop({ + autoplay: vide.settings.autoplay, + loop: vide.settings.loop, + volume: vide.settings.volume, + muted: vide.settings.muted, + playbackRate: vide.settings.playbackRate + }); + + // Append video + vide.$wrapper.append(vide.$video); + + // Video alignment + vide.$video.css({ + "margin": "auto", + "position": "absolute", + "z-index": -1, + "top": position.y, + "left": position.x, + "-webkit-transform": "translate(-" + position.x + ", -" + position.y + ")", + "-ms-transform": "translate(-" + position.x + ", -" + position.y + ")", + "transform": "translate(-" + position.x + ", -" + position.y + ")" + }); + + // resize video, when it's loaded + vide.$video.bind("loadedmetadata." + pluginName, function() { + vide.$video.css("visibility", "visible"); + vide.resize(); + vide.$wrapper.css("background-image", "none"); + }); + + // resize event is available only for 'window', + // use another code solutions to detect DOM elements resizing + vide.$element.bind("resize." + pluginName, function() { + if (vide.settings.resizing) { + vide.resize(); + } + }); + } + }; + + /** + * Get video element of the background + * @returns {HTMLVideoElement|null} + * @public + */ + Vide.prototype.getVideoObject = function() { + return this.$video ? this.$video[0] : null; + }; + + /** + * Resize video background + * @public + */ + Vide.prototype.resize = function() { + if (!this.$video) { + return; + } + + var + // get native video size + videoHeight = this.$video[0].videoHeight, + videoWidth = this.$video[0].videoWidth, + + // get wrapper size + wrapperHeight = this.$wrapper.height(), + wrapperWidth = this.$wrapper.width(); + + if (wrapperWidth / videoWidth > wrapperHeight / videoHeight) { + this.$video.css({ + "width": wrapperWidth + 2, + + // +2 pixels to prevent empty space after transformation + "height": "auto" + }); + } else { + this.$video.css({ + "width": "auto", + + // +2 pixels to prevent empty space after transformation + "height": wrapperHeight + 2 + }); + } + }; + + /** + * Destroy video background + * @public + */ + Vide.prototype.destroy = function() { + this.$element.unbind(pluginName); + + if (this.$video) { + this.$video.unbind(pluginName); + } + + delete $[pluginName].lookup[this.index]; + this.$element.removeData(pluginName); + this.$wrapper.remove(); + }; + + /** + * Special plugin object for instances. + * @type {Object} + * @public + */ + $[pluginName] = { + lookup: [] + }; + + /** + * Plugin constructor + * @param {Object|String} path + * @param {Object|String} options + * @returns {JQuery} + * @constructor + */ + $.fn[pluginName] = function(path, options) { + var instance; + + this.each(function() { + instance = $.data(this, pluginName); + + if (instance) { + + // destroy plugin instance if exists + instance.destroy(); + } + + // create plugin instance + instance = new Vide(this, path, options); + instance.index = $[pluginName].lookup.push(instance) - 1; + $.data(this, pluginName, instance); + }); + + return this; + }; + + $(document).ready(function() { + + // window resize event listener + $(window).bind("resize." + pluginName, function() { + for (var len = $[pluginName].lookup.length, i = 0, instance; i < len; i++) { + instance = $[pluginName].lookup[i]; + + if (instance && instance.settings.resizing) { + instance.resize(); + } + } + }); + + // Auto initialization. + // Add 'data-vide-bg' attribute with a path to the video without extension. + // Also you can pass options throw the 'data-vide-options' attribute. + // 'data-vide-options' must be like "muted: false, volume: 0.5". + $(document).find("[data-" + pluginName + "-bg]").each(function(i, element) { + var $element = $(element), + options = $element.data(pluginName + "-options"), + path = $element.data(pluginName + "-bg"); + + $element[pluginName](path, options); + }); + }); +})(window.jQuery, window, document, navigator); diff --git a/third_party/jquery/LICENSE b/third_party/jquery/LICENSE new file mode 100644 index 00000000..cdd31b5c --- /dev/null +++ b/third_party/jquery/LICENSE @@ -0,0 +1,21 @@ +Copyright 2014 jQuery Foundation and other contributors +http://jquery.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/jquery/jquery-1.11.2.js b/third_party/jquery/jquery-1.11.2.js new file mode 100644 index 00000000..1c3aa822 --- /dev/null +++ b/third_party/jquery/jquery-1.11.2.js @@ -0,0 +1,10346 @@ +/*! + * jQuery JavaScript Library v1.11.2 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-17T15:27Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + version = "1.11.2", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1, IE<9 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( support.ownLast ) { + for ( key in obj ) { + return hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1, IE<9 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: function() { + return +( new Date() ); + }, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.2.0-pre + * http://sizzlejs.com/ + * + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-16 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType !== 1 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + parent = doc.defaultView; + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", unloadHandler, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + docElem.appendChild( div ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + + +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +// Execute ASAP in case we need to set body.style.zoom +jQuery(function() { + // Minified: var a,b,c,d + var val, div, body, container; + + body = document.getElementsByTagName( "body" )[ 0 ]; + if ( !body || !body.style ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + div = document.createElement( "div" ); + container = document.createElement( "div" ); + container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; + + support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; + if ( val ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); +}); + + + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties + noData: { + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; +}; +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + // Minified: var a,b,c + var input = document.createElement( "input" ), + div = document.createElement( "div" ), + fragment = document.createDocumentFragment(); + + // Setup + div.innerHTML = "
    a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: IE < 9, Android < 4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
    ", "
    " ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
    " ], + tr: [ 2, "", "
    " ], + col: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
    ", "
    " ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
    " && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "