mirror of
https://github.com/google/pebble.git
synced 2025-05-29 06:23:13 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
15
tools/generate_watchface/__init__.py
Normal file
15
tools/generate_watchface/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
189
tools/generate_watchface/generate_watchface.py
Normal file
189
tools/generate_watchface/generate_watchface.py
Normal file
|
@ -0,0 +1,189 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT 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 json
|
||||
import argparse, os, sys
|
||||
import shutil
|
||||
|
||||
import watch_templates as temps
|
||||
|
||||
class WatchGenerator:
|
||||
def __init__(self):
|
||||
self._static_defs = []
|
||||
self._init_func = []
|
||||
self._update_func = []
|
||||
self.watch_name = None
|
||||
self.version = None
|
||||
self.company_name = None
|
||||
self.visible_name = None
|
||||
|
||||
def h_file_name(self):
|
||||
return 'pebble_gen_header.h'
|
||||
def c_file_name(self):
|
||||
return self.watch_name + '.c'
|
||||
def defs_file_name(self):
|
||||
return 'pebble_gen_defs.h'
|
||||
|
||||
|
||||
def generate_c_code(self):
|
||||
fs = temps.FileScope
|
||||
res_strings = []
|
||||
res_strings.append(fs.includes.format(header_name=self.h_file_name()))
|
||||
res_strings.append('\n'.join(self._static_defs))
|
||||
res_strings.append(fs.update_function.format(watch_name=self.watch_name, update_content='\n'.join(self._update_func)))
|
||||
res_strings.append(fs.init_function.format(watch_name=self.watch_name, init_content='\n'.join(self._init_func)))
|
||||
|
||||
return '\n'.join(res_strings)
|
||||
|
||||
def generate_h_code(self):
|
||||
return temps.FileScope.header.format(watch_name=self.watch_name, app_version=self.version)
|
||||
|
||||
def generate_defs_file(self):
|
||||
return temps.FileScope.defs_file.format(watch_name=self.watch_name, visible_name=self.visible_name, company_name=self.company_name)
|
||||
|
||||
def load_config(self, conf):
|
||||
self.watch_name = conf["name"]
|
||||
self.version = conf["friendlyVersion"]
|
||||
self.company_name = conf["company-name"]
|
||||
self.visible_name = conf["visible-name"]
|
||||
|
||||
for elem in conf["elements"]:
|
||||
t = elem["type"]
|
||||
if t == "static-image":
|
||||
self.parse_static_image(elem)
|
||||
elif t == "time-text":
|
||||
self.parse_time_text(elem)
|
||||
elif t == "hand":
|
||||
self.parse_hand(elem)
|
||||
else:
|
||||
raise "Unknown element type: " + t
|
||||
|
||||
def parse_static_image(self, elem):
|
||||
si = temps.StaticImage
|
||||
self._static_defs.append(si.static_defs.format(name=elem["name"]))
|
||||
self._init_func.append(si.init_lines.format(res_def_name=elem["image"]["def-name"],
|
||||
name=elem["name"]))
|
||||
|
||||
def parse_date_format(self, fmt_list):
|
||||
### parses a json object list into a c strftime format string and required buffer size
|
||||
buffer_size = 1
|
||||
fmt_string = ""
|
||||
for fmt in fmt_list:
|
||||
# TODO: implement the other operations for strftime
|
||||
t = fmt["type"]
|
||||
if t == "string":
|
||||
fmt_string += fmt.val
|
||||
buffer_size += len(fmt.val)
|
||||
elif t == "day-name-abbreviated":
|
||||
fmt_string += "%a"
|
||||
buffer_size += 4
|
||||
elif t == "day-of-month":
|
||||
fmt_string += "%d"
|
||||
buffer_size += 2
|
||||
else:
|
||||
raise "Unknown date format type: " + t
|
||||
return buffer_size, fmt_string
|
||||
|
||||
def parse_color(self, color_string):
|
||||
if color_string == "black":
|
||||
return "GColorBlack"
|
||||
elif color_string == "white":
|
||||
return "GColorWhite"
|
||||
elif color_string == "transparent":
|
||||
return "GColorTrans"
|
||||
else:
|
||||
raise "Unknown color: " + color_string
|
||||
|
||||
def parse_font_info(self, font_obj):
|
||||
f = font_obj["font"]
|
||||
if f == "gothic":
|
||||
bold = "modifiers" in font_obj and "bold" in font_obj["modifiers"]
|
||||
size = font_obj["size"]
|
||||
return "graphics_text_get_system_font({size}, GFontStyle{style})".format(size=size, style=("Bold" if bold else "Regular"))
|
||||
else:
|
||||
raise "Unknown font: " + f
|
||||
|
||||
def parse_time_text(self, elem):
|
||||
tt = temps.TimeText
|
||||
|
||||
buffer_len, date_fmt = self.parse_date_format(elem["format"])
|
||||
color = self.parse_color(elem["color"])
|
||||
bg_color = self.parse_color(elem["background-color"])
|
||||
font_func = self.parse_font_info(elem["font"])
|
||||
|
||||
self._static_defs.append(tt.static_defs.format(name=elem["name"], buffer_length=buffer_len))
|
||||
self._init_func.append(tt.init_lines.format(name=elem["name"],
|
||||
x=elem["position"][0], y=elem["position"][1],
|
||||
w=elem["size"][0], h=elem["size"][1],
|
||||
color=color, background_color=bg_color,
|
||||
font_func=font_func))
|
||||
self._update_func.append(tt.update_lines.format(name=elem["name"], date_format_string=date_fmt))
|
||||
|
||||
def parse_time_element(self, te):
|
||||
if te == "hour":
|
||||
return "t.tm_hour / 12"
|
||||
elif te == "hour24":
|
||||
return "t.tm_hour / 24"
|
||||
elif te == "minute":
|
||||
return "t.tm_min / 60"
|
||||
elif te == "second":
|
||||
return "t.tm_sec / 60"
|
||||
else:
|
||||
raise "Unknown time element: " + te
|
||||
|
||||
def parse_hand(self, elem):
|
||||
h = temps.Hand
|
||||
|
||||
ratio = self.parse_time_element(elem["time-element"])
|
||||
|
||||
self._static_defs.append(h.static_defs.format(name=elem["name"]))
|
||||
self._init_func.append(h.init_lines.format(res_def_name=elem["trans-image"]["def-name"],
|
||||
name=elem["name"],
|
||||
pivot_x=elem["pivot"][0], pivot_y=elem["pivot"][1]))
|
||||
self._update_func.append(h.update_lines.format(name=elem["name"], angle_ratio=ratio))
|
||||
|
||||
|
||||
def generate(watch_config, src_output_dir):
|
||||
with open(watch_config, 'r') as conf:
|
||||
config_data = json.loads(conf.read())
|
||||
|
||||
gen = WatchGenerator()
|
||||
|
||||
gen.load_config(config_data)
|
||||
|
||||
if not os.path.isdir(src_output_dir):
|
||||
os.makedirs(src_output_dir)
|
||||
|
||||
with open(os.path.join(src_output_dir, gen.c_file_name()), 'w') as output:
|
||||
output.write(gen.generate_c_code())
|
||||
|
||||
with open(os.path.join(src_output_dir, gen.h_file_name()), 'w') as output:
|
||||
output.write(gen.generate_h_code())
|
||||
|
||||
with open(os.path.join(src_output_dir, gen.defs_file_name()), 'w') as output:
|
||||
output.write(gen.generate_defs_file())
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('watch_config')
|
||||
parser.add_argument('src_output_dir')
|
||||
|
||||
args = parser.parse_args()
|
||||
generate(args.watch_config, args.src_output_dir)
|
||||
|
||||
shutil.copy("watch_boilerplate.template.c", os.path.join(args.src_output_dir, "watch_boilerplate.template.c"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
21
tools/generate_watchface/make_watchface.sh
Executable file
21
tools/generate_watchface/make_watchface.sh
Executable file
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
rm -r src
|
||||
|
||||
pushd "tools/generate_watchface"
|
||||
python generate_watchface.py ../../watch_generators/gen_analog.json ../../src/
|
||||
popd
|
||||
./waf distclean configure build
|
68
tools/generate_watchface/watch_boilerplate.template.c
Normal file
68
tools/generate_watchface/watch_boilerplate.template.c
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The basis for a generated watch face
|
||||
*/
|
||||
|
||||
#include "pebble_gen_defs.h"
|
||||
|
||||
#include "pebble.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "pebble_gen_header.h"
|
||||
#include "resource_ids.gen.h"
|
||||
|
||||
|
||||
PBL_APP_INFO(PBL_GEN_VISIBLE_NAME_STR, PBL_GEN_COMPANY_NAME_STR);
|
||||
|
||||
static void handle_init(AppContextRef ctx) {
|
||||
(void)ctx;
|
||||
|
||||
static Window s_window;
|
||||
window_init(&s_window, "Window Name");
|
||||
window_stack_push(&s_window);
|
||||
|
||||
static Layer s_watch_layer;
|
||||
|
||||
layer_init(&s_watch_layer, s_window.layer.frame);
|
||||
layer_add_child(&s_window.layer, &s_watch_layer);
|
||||
|
||||
PBL_GEN_INIT(&s_watch_layer);
|
||||
}
|
||||
|
||||
static void handle_render(AppContextRef ctx, PebbleRenderEvent *e) {
|
||||
(void)ctx;
|
||||
window_render(e->window, e->ctx);
|
||||
|
||||
layer_mark_dirty(&e->window->layer);
|
||||
}
|
||||
|
||||
static void handle_deinit(AppContextRef ctx) {
|
||||
(void)ctx;
|
||||
// TODO: let go of resources
|
||||
}
|
||||
|
||||
void pbl_main(void* params) {
|
||||
PebbleAppHandlers s_handlers = {
|
||||
.init_handler = &handle_init,
|
||||
.render_handler = &handle_render,
|
||||
.deinit_handler = &handle_deinit
|
||||
};
|
||||
app_event_loop(params, &s_handlers);
|
||||
}
|
60
tools/generate_watchface/watch_generators/gen_analog.json
Normal file
60
tools/generate_watchface/watch_generators/gen_analog.json
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "gen_analog",
|
||||
"friendlyVersion": "dev_0.0",
|
||||
"company-name": "Pebble",
|
||||
"visible-name": "Simple Watch Face",
|
||||
|
||||
"elements": [{
|
||||
"type": "static-image",
|
||||
"name": "background",
|
||||
"position": [0, 0],
|
||||
"image": {
|
||||
"def-name": "SIMPLE_BG"
|
||||
}
|
||||
},{
|
||||
"type": "time-text",
|
||||
"name": "day_word",
|
||||
"position": [46, 114],
|
||||
"size": [27, 20],
|
||||
"format": [{
|
||||
"type": "day-name-abbreviated"
|
||||
}],
|
||||
"font": {
|
||||
"font": "gothic",
|
||||
"size": 18
|
||||
},
|
||||
"color": "white",
|
||||
"background-color": "black"
|
||||
},{
|
||||
"type": "time-text",
|
||||
"name": "day_num",
|
||||
"position": [73, 114],
|
||||
"size": [18, 20],
|
||||
"format": [{
|
||||
"type": "day-of-month"
|
||||
}],
|
||||
"font": {
|
||||
"font": "gothic",
|
||||
"size": 18,
|
||||
"modifiers": ["bold"]
|
||||
},
|
||||
"color": "white",
|
||||
"background-color": "black"
|
||||
},{
|
||||
"type": "hand",
|
||||
"name": "minute_hand",
|
||||
"time-element": "minute",
|
||||
"pivot": [ 8, 80 ],
|
||||
"trans-image": {
|
||||
"def-name": "SIMPLE_MINUTE"
|
||||
}
|
||||
}, {
|
||||
"type": "hand",
|
||||
"name": "hour_hand",
|
||||
"time-element": "hour",
|
||||
"pivot": [ 6, 60 ],
|
||||
"trans-image": {
|
||||
"def-name": "SIMPLE_HOUR"
|
||||
}
|
||||
}]
|
||||
}
|
113
tools/generate_watchface/watch_templates.py
Normal file
113
tools/generate_watchface/watch_templates.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
|
||||
# globally used functions to put other templates in
|
||||
class FileScope:
|
||||
header = """
|
||||
#pragma once
|
||||
#include "pebble.h"
|
||||
|
||||
void {watch_name}_init(Layer *window);
|
||||
"""
|
||||
|
||||
includes = """
|
||||
#include "{header_name}"
|
||||
#include "resource_ids.gen.h"
|
||||
#include <time.h>
|
||||
|
||||
"""
|
||||
|
||||
init_function = """
|
||||
void {watch_name}_init(Layer *window) {{
|
||||
const GPoint CENTER = {{window->frame.size.w / 2, window->frame.size.h / 2}};
|
||||
|
||||
{init_content}
|
||||
|
||||
window->update_proc = &{watch_name}_update;
|
||||
}}
|
||||
"""
|
||||
update_function = """
|
||||
static void {watch_name}_update(Layer *me) {{
|
||||
const GPoint CENTER = {{me->frame.size.w / 2, me->frame.size.h / 2}};
|
||||
(void)me;
|
||||
PblTm t;
|
||||
get_time(&t);
|
||||
|
||||
{update_content}
|
||||
|
||||
}}
|
||||
"""
|
||||
|
||||
# TODO: make the app number an actual function call
|
||||
defs_file = """
|
||||
#pragma once
|
||||
|
||||
#define PBL_GEN_LOCAL_INCLUDE #include "{watch_name}.h"
|
||||
|
||||
#define PBL_GEN_COMPANY_NAME_STR "{company_name}"
|
||||
#define PBL_GEN_VISIBLE_NAME_STR "{visible_name}"
|
||||
#define PBL_GEN_INIT(win_layer) {watch_name}_init(win_layer)
|
||||
|
||||
"""
|
||||
|
||||
# non-moving image (like a background)
|
||||
class StaticImage:
|
||||
static_defs = """
|
||||
static BmpContainer s_{name};
|
||||
"""
|
||||
init_lines = """
|
||||
bmp_init_container({res_def_name}, &s_{name});
|
||||
layer_add_child(window, &s_{name}.layer.layer);
|
||||
"""
|
||||
|
||||
# formatted textual date strings
|
||||
class TimeText:
|
||||
static_defs = """
|
||||
static TextLayer s_{name};
|
||||
static char s_{name}_buffer[{buffer_length}] = "";
|
||||
"""
|
||||
# TODO: getting fonts is ghetto-ugly in code right now
|
||||
init_lines = """
|
||||
text_layer_init(&s_{name}, GRect({x}, {y}, {w}, {h}));
|
||||
text_layer_set_text(&s_{name}, s_{name}_buffer);
|
||||
text_layer_set_text_color(&s_{name}, {color});
|
||||
text_layer_set_background_color(&s_{name}, {background_color});
|
||||
GFont {name}_font = {font_func};
|
||||
text_layer_set_font(&s_{name}, {name}_font);
|
||||
layer_add_child(window, &s_{name}.layer);
|
||||
"""
|
||||
update_lines = """
|
||||
string_format_time(s_{name}_buffer, sizeof(s_{name}_buffer), "{date_format_string}", &t);
|
||||
text_layer_set_text(&s_{name}, s_{name}_buffer);
|
||||
"""
|
||||
|
||||
# common analog hands
|
||||
class Hand:
|
||||
static_defs = """
|
||||
static RotBmpPairContainer s_{name};
|
||||
"""
|
||||
init_lines = """
|
||||
rotbmp_pair_init_container({res_def_name}_WHITE, {res_def_name}_BLACK, &s_{name});
|
||||
rotbmp_pair_layer_set_src_ic(&s_{name}.layer, GPoint({pivot_x}, {pivot_y}));
|
||||
GRect {name}_frame = layer_get_frame(&s_{name}.layer.layer);
|
||||
{name}_frame.origin.x = CENTER.x - ({name}_frame.size.w / 2);
|
||||
{name}_frame.origin.y = CENTER.y - ({name}_frame.size.h / 2);
|
||||
layer_set_frame(&s_{name}.layer.layer, {name}_frame);
|
||||
layer_add_child(window, &s_{name}.layer.layer);
|
||||
"""
|
||||
update_lines = """
|
||||
rotbmp_pair_layer_set_angle(&s_{name}.layer, TRIG_MAX_ANGLE * {angle_ratio});
|
||||
"""
|
Loading…
Add table
Add a link
Reference in a new issue