From b73ec17ee3fa5309ab5017b8c193fac4975ac740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Che=C5=82kowski?= Date: Wed, 15 Jul 2026 14:56:46 +0200 Subject: [PATCH] feat: add scaling methods to the Theme --- docs/theme.md | 25 ++- package-lock.json | 196 +++++++++--------- package.json | 10 +- src/components/modal/Modal.component.brs | 1 + src/components/theme/Theme.brs | 80 ++++++- src/components/theme/Theme.facade.brs | 32 +++ src/components/theme/Theme.xml | 5 + .../theme/_mocks/Theme.facade.mock.brs | 15 ++ src/components/theme/_tests/Theme.test.brs | 100 +++++++++ 9 files changed, 347 insertions(+), 117 deletions(-) diff --git a/docs/theme.md b/docs/theme.md index 56743e6..6f4dd0e 100644 --- a/docs/theme.md +++ b/docs/theme.md @@ -33,8 +33,25 @@ Now you can use the facade: ```brightscript theme = ThemeFacade() -theme.getFont("regular") ' It corresponds to the font name defined in the getMyTheme function. -theme.getFontUri("regular")' It is the same as getFont but returns uri -theme.rgba("0x00000000", 0.8) ' 1 represents "FF" and 0 - "00". All other values are between them. -theme.colors.white ' Other data that is passed to theme node +theme.getFont("regular", 28) ' It corresponds to the font name defined in the getMyTheme function. +theme.getScaledFont("regular", 28) ' Scales font size using current resolution and FHD baseline +theme.getFontUri("regular") ' It is the same as getFont but returns uri +theme.scaleX(320) ' Scales value against resolution width / 1920 +theme.scaleY(180) ' Scales value against resolution height / 1080 +theme.scaleUniform(48) ' Uses smaller of X/Y scale +size = theme.scaleSize([320, 180]) ' Returns [scaledWidth, scaledHeight] +theme.rgba("0x00000000", 0.8) ' 1 represents "FF" and 0 - "00". All other values are between them. +theme.colors.white ' Other data that is passed to theme node ``` + +Built-in helpers use `theme.resolution.width` and `theme.resolution.height` if present. Missing resolution falls back to the FHD baseline `1920x1080`, so existing apps keep current behaviour until they opt in. + +Available built-in helpers: +- `getFont(fontName as String, sizeInPixels as Integer) as Object` +- `getScaledFont(fontName as String, sizeInPixels as Float, mode = "uniform" as String) as Object` +- `getFontUri(fontName as String) as String` +- `scaleX(value as Float) as Integer` +- `scaleY(value as Float) as Integer` +- `scaleUniform(value as Float) as Integer` +- `scaleSize(size as Object) as Object` +- `rgba(color as String, opacity as Float) as String` diff --git a/package-lock.json b/package-lock.json index 15878f9..9265f56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,14 @@ "@dazn/kopytko-utils": "^2.8.1" }, "devDependencies": { - "@dazn/kopytko-packager": "^1.4.2", + "@dazn/kopytko-packager": "^1.4.3", "@dazn/kopytko-unit-testing-framework": "^2.6.5", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^12.0.8", - "kopytko-formatter": "^1.1.2", - "kopytko-linter": "^1.3.2", - "semantic-release": "^25.0.5" + "@semantic-release/github": "^12.0.9", + "kopytko-formatter": "^1.1.13", + "kopytko-linter": "^1.6.7", + "semantic-release": "^25.0.7" } }, "node_modules/@actions/core": { @@ -123,9 +123,9 @@ } }, "node_modules/@dazn/kopytko-packager": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@dazn/kopytko-packager/-/kopytko-packager-1.4.2.tgz", - "integrity": "sha512-aI9Niipfk2BnCc1L91wRct981m8sASgGO7jpuEX2uw1cegGptY6hnG1QqVJ7KQoHuf0609MrdzDTiY4ut98x2Q==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@dazn/kopytko-packager/-/kopytko-packager-1.4.3.tgz", + "integrity": "sha512-5dZzUxx/xjKQKccVeDszreqOleMMMWKEiFpdXjitwBEXPd0CNkG2vQdxvo8ACauk+RjtfiX7EeWAnEjfzJoaYA==", "dev": true, "license": "MIT", "dependencies": { @@ -296,9 +296,9 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.10.tgz", - "integrity": "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==", + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.11.tgz", + "integrity": "sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -475,9 +475,9 @@ } }, "node_modules/@semantic-release/github": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.8.tgz", - "integrity": "sha512-tej5AAgK5X9wHRoDmYhecMXEHEkFeGOY1XsEblKxu8pIQwahzf1STYyr7iPU6Lpbg6C5I3N2w/ocXrBo+L7jhw==", + "version": "12.0.9", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.9.tgz", + "integrity": "sha512-ODIqb0V3QqndipryEEiaBxUQCFjvv7Oese5Dt4omMGa60YRNEW0Sx3K+zri0uac2Y6S9nOlMehciWIzvvRCTGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -964,9 +964,9 @@ "license": "MIT" }, "node_modules/adm-zip": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", - "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", + "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", "dev": true, "license": "MIT", "engines": { @@ -1125,9 +1125,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -1337,9 +1337,9 @@ } }, "node_modules/cli-highlight/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "dependencies": { @@ -2235,9 +2235,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "dependencies": { @@ -2411,9 +2411,9 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2742,9 +2742,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -2779,9 +2779,9 @@ "license": "MIT" }, "node_modules/json-with-bigint": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", - "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.10.tgz", + "integrity": "sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==", "dev": true, "license": "MIT" }, @@ -2799,9 +2799,9 @@ } }, "node_modules/kopytko-brightscript-parser": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/kopytko-brightscript-parser/-/kopytko-brightscript-parser-1.0.2.tgz", - "integrity": "sha512-3kc5DlJ1djpVDDuGri7VMXqSCUWb69UsSPbW4PjDuL+OAlK60BRovNTqXOEEuyeWlRUk4zWu0SGFDFYdWgAPcw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/kopytko-brightscript-parser/-/kopytko-brightscript-parser-1.2.0.tgz", + "integrity": "sha512-pQnsDiioKu32Y2p4zl5TlpXtkSheXZL+UZ34Asx62GTCiWRDNzIeRhma02x37tFWelvs3xmD2k6Xcw5e3+vvSQ==", "dev": true, "license": "MIT", "engines": { @@ -2809,13 +2809,13 @@ } }, "node_modules/kopytko-formatter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/kopytko-formatter/-/kopytko-formatter-1.1.2.tgz", - "integrity": "sha512-Bj4htrebO47IqURwHHYHLocS2IhqHyOyCZxL3kSIDGh1eVoZ6pEHZ03cjrhIQlmKQwlZes7PzyCdsGueT6jshg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/kopytko-formatter/-/kopytko-formatter-1.1.13.tgz", + "integrity": "sha512-cv01buERAW4Kk1BZNqs1Myjt/iP2bYdK8NI10BsLrijDpEfQZK6cFxpx0WAEzv1eCWUMV0vM1MZIopn8ONy+Qw==", "dev": true, "license": "MIT", "dependencies": { - "kopytko-brightscript-parser": "^1.0.2" + "kopytko-brightscript-parser": "^1.2.0" }, "bin": { "kopytko-format": "dist/bin/kopytko-format.js" @@ -2825,13 +2825,13 @@ } }, "node_modules/kopytko-linter": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/kopytko-linter/-/kopytko-linter-1.3.2.tgz", - "integrity": "sha512-vacXhw+VI2uhtDs9DXptktHR8Qthw2eD5uTpXutcNmB1hxxHHsZ/QruIJQiHn38kl4ZH4jgMq7PKQtCSoOwotg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/kopytko-linter/-/kopytko-linter-1.6.7.tgz", + "integrity": "sha512-kJDQxpNLPG0nh1igQeWUjM6m0s2Ok4AqQJLPIvmvL9w0oo/IOzYe3WBGhIEs+1Rx4YoGu4TeZisiNoYX7f1whg==", "dev": true, "license": "MIT", "dependencies": { - "kopytko-brightscript-parser": "^1.0.2" + "kopytko-brightscript-parser": "^1.2.0" }, "bin": { "kopytko-lint": "dist/bin/kopytko-lint.js" @@ -3275,9 +3275,9 @@ } }, "node_modules/npm": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.17.0.tgz", - "integrity": "sha512-PurxiZexEHDTE4SSaLI3ZrnbAGiZfeyUcQcxcP5D+hfytNAze/D1IzDuInTn9XVLIbAQUnQuSPXJx02LHjLvQw==", + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.18.0.tgz", + "integrity": "sha512-T67M4L5wNm0cZ7EBLErcEkY1SmzEW/WJ+SADBzsFUY1UdAPfFHXFQtZ6SEXiK0+vzXysCvAsepbMaBTwnrAD+w==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -3356,8 +3356,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.8.0", - "@npmcli/config": "^10.11.0", + "@npmcli/arborist": "^9.9.0", + "@npmcli/config": "^10.12.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -3381,11 +3381,11 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.10", - "libnpmexec": "^10.3.0", - "libnpmfund": "^7.0.24", + "libnpmdiff": "^8.1.11", + "libnpmexec": "^10.3.1", + "libnpmfund": "^7.0.25", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.10", + "libnpmpack": "^9.1.11", "libnpmpublish": "^11.2.0", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", @@ -3401,7 +3401,7 @@ "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", - "npm-profile": "^12.0.1", + "npm-profile": "^12.0.2", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", @@ -3410,11 +3410,11 @@ "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.8.4", + "semver": "^7.8.5", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", - "tar": "^7.5.16", + "tar": "^7.5.19", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", @@ -3486,7 +3486,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.8.0", + "version": "9.9.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3534,7 +3534,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.11.0", + "version": "10.12.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3879,7 +3879,7 @@ } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "5.0.6", + "version": "5.0.7", "dev": true, "inBundle": true, "license": "MIT", @@ -4253,12 +4253,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.10", + "version": "8.1.11", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.8.0", + "@npmcli/arborist": "^9.9.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -4272,13 +4272,13 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.3.0", + "version": "10.3.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.8.0", + "@npmcli/arborist": "^9.9.0", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", @@ -4295,12 +4295,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.24", + "version": "7.0.25", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.8.0" + "@npmcli/arborist": "^9.9.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -4320,12 +4320,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.10", + "version": "9.1.11", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.8.0", + "@npmcli/arborist": "^9.9.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -4694,13 +4694,13 @@ } }, "node_modules/npm/node_modules/npm-profile": { - "version": "12.0.1", + "version": "12.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0" + "proc-log": "^6.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -4905,7 +4905,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.8.4", + "version": "7.8.5", "dev": true, "inBundle": true, "license": "ISC", @@ -5030,7 +5030,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.16", + "version": "7.5.19", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -5126,7 +5126,7 @@ } }, "node_modules/npm/node_modules/undici": { - "version": "6.26.0", + "version": "6.27.0", "dev": true, "inBundle": true, "license": "MIT", @@ -5346,9 +5346,9 @@ } }, "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.5.tgz", + "integrity": "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==", "dev": true, "license": "MIT", "engines": { @@ -5795,9 +5795,9 @@ } }, "node_modules/semantic-release": { - "version": "25.0.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.5.tgz", - "integrity": "sha512-mn61SUJwtM8ThrWn2WmgLVpwVJeG/hPSupua1psdMoufmwRIPyvRLkRkL0JDXkP67OntlLWUYnBnfVc8EDO3/g==", + "version": "25.0.7", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.7.tgz", + "integrity": "sha512-fFiUD7LNFI0TOGkc49WDHUX4GYKrOMDKct5ReSB1EJCZiPsPdbIPIJGys1xb2ILpK1v9JMsRkjJQgTRpbr7DgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6043,9 +6043,9 @@ } }, "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -6355,9 +6355,9 @@ } }, "node_modules/string-kit": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/string-kit/-/string-kit-0.19.3.tgz", - "integrity": "sha512-94p913R6+Ea6656A39bqJeHgt64HM9RSq36oc0F8N8Mz76g5+cLaqEwkouqguEDrJ4rhVQpS+tKz214FdtMoqA==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/string-kit/-/string-kit-0.19.5.tgz", + "integrity": "sha512-MqNWtQQw6xMFbOSa26HfyS5uyDu1tR4OEPz44VDP6JWa/S0E0N11uw8CXrPt5hme/tBXRp5AmoIvFl5B9lt7PA==", "dev": true, "license": "MIT", "engines": { @@ -6615,9 +6615,9 @@ } }, "node_modules/terminal-kit": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/terminal-kit/-/terminal-kit-3.1.2.tgz", - "integrity": "sha512-ro2FyU4A+NwA74DLTYTnoCFYuFpgV1aM07IS6MPrJeajoI2hwF44EdUqjoTmKEl6srYDWtbVkc/b1C16iUnxFQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/terminal-kit/-/terminal-kit-3.1.3.tgz", + "integrity": "sha512-URPwQqXe/T5dZoD4qBHUO7eS+Vtf0PjliCftJU2EPaF5uVw/QG1zqgLy5kqwTrn1ix9e9HtMgMKAnzgaAnr3yA==", "dev": true, "license": "MIT", "dependencies": { @@ -6626,9 +6626,9 @@ "lazyness": "^1.2.0", "ndarray": "^1.0.19", "nextgen-events": "^1.5.3", - "seventh": "^0.9.2", - "string-kit": "^0.19.0", - "tree-kit": "^0.8.7" + "seventh": "^0.9.4", + "string-kit": "^0.19.5", + "tree-kit": "^0.8.10" }, "engines": { "node": ">=16.13.0" @@ -6753,9 +6753,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -6812,9 +6812,9 @@ } }, "node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { diff --git a/package.json b/package.json index 71e2348..8b53d97 100644 --- a/package.json +++ b/package.json @@ -31,14 +31,14 @@ "@dazn/kopytko-utils": "^2.8.1" }, "devDependencies": { - "@dazn/kopytko-packager": "^1.4.2", + "@dazn/kopytko-packager": "^1.4.3", "@dazn/kopytko-unit-testing-framework": "^2.6.5", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^12.0.8", - "kopytko-formatter": "^1.1.2", - "kopytko-linter": "^1.3.2", - "semantic-release": "^25.0.5" + "@semantic-release/github": "^12.0.9", + "kopytko-formatter": "^1.1.13", + "kopytko-linter": "^1.6.7", + "semantic-release": "^25.0.7" }, "license": "MIT", "kopytkoModuleDir": "src/", diff --git a/src/components/modal/Modal.component.brs b/src/components/modal/Modal.component.brs index 0507e8b..fb3c96e 100644 --- a/src/components/modal/Modal.component.brs +++ b/src/components/modal/Modal.component.brs @@ -1,5 +1,6 @@ ' @import /components/eventBus/EventBus.facade.brs ' @import /components/modal/ModalEvents.const.brs + sub constructor() m._eventBus = EventBusFacade() m._modalEvents = ModalEvents() diff --git a/src/components/theme/Theme.brs b/src/components/theme/Theme.brs index ea0b276..3ffde9e 100644 --- a/src/components/theme/Theme.brs +++ b/src/components/theme/Theme.brs @@ -1,20 +1,32 @@ +' @import /components/getProperty.brs from @dazn/kopytko-utils ' @import /components/padStart.brs from @dazn/kopytko-utils +sub init() + m._FALLBACK_BASE_HEIGHT = 1080 + m._FALLBACK_BASE_WIDTH = 1920 + + m._scaleX = 1 + m._scaleY = 1 +end sub + sub setAppTheme(appTheme as Object) - if (NOT m.global.hasField("theme")) - m.top.addFields(appTheme) - m.global.addFields({ - theme: m.top, - }) - end if + if (m.global.hasField("theme")) then return + + baseHeight = getProperty(appTheme, ["baseResolution", "height"], m._FALLBACK_BASE_HEIGHT) + baseWidth = getProperty(appTheme, ["baseResolution", "width"], m._FALLBACK_BASE_WIDTH) + m._scaleX = getProperty(appTheme, ["resolution", "width"], baseWidth) / baseWidth + m._scaleY = getProperty(appTheme, ["resolution", "height"], baseHeight) / baseHeight + + m.top.addFields(appTheme) + m.global.addFields({ theme: m.top }) end sub function getFont(options as Object) as Object - if (m.top.fonts = Invalid) - return Invalid - end if + fonts = getProperty(m.top, "fonts") + + if (fonts = Invalid) then return Invalid - font = m.top.fonts[options.fontName] + font = fonts[options.fontName] if (font = Invalid) return Invalid @@ -26,9 +38,57 @@ function getFont(options as Object) as Object return font end function +function getScaledFont(options as Object) as Object + return getFont({ + fontName: options.fontName, + sizeInPixels: _scaleValue(getProperty(options, "sizeInPixels", 0), getProperty(options, "mode", "uniform")), + }) +end function + +function scaleX(options as Object) as Integer + return _scaleValue(getProperty(options, "value", 0), "x") +end function + +function scaleY(options as Object) as Integer + return _scaleValue(getProperty(options, "value", 0), "y") +end function + +function scaleUniform(options as Object) as Integer + return _scaleValue(getProperty(options, "value", 0), "uniform") +end function + +function scaleSize(options as Object) as Object + size = getProperty(options, "size", []) + + if (size.count() < 2) then return [] + + return [ + scaleX({ value: size[0] }), + scaleY({ value: size[1] }), + ] +end function + function rgba(options as Object) as String baseColor = Left(options.color, 8) alphaValue = UCase(padStart(StrI(CInt(options.opacity * 255), 16), 2, "0")) return baseColor + alphaValue end function + +function _scaleValue(value as Float, mode = "uniform" as String) as Integer + return CInt(value * _getScale(mode)) +end function + +function _getScale(mode = "uniform" as String) as Float + mode = LCase(mode) + + if (mode = "x") + return m._scaleX + else if (mode = "y") + return m._scaleY + else if (m._scaleX < m._scaleY) + return m._scaleX + end if + + return m._scaleY +end function diff --git a/src/components/theme/Theme.facade.brs b/src/components/theme/Theme.facade.brs index 42a34b9..03d5715 100644 --- a/src/components/theme/Theme.facade.brs +++ b/src/components/theme/Theme.facade.brs @@ -21,6 +21,14 @@ function ThemeFacade() as Object return m._theme.callFunc("getFont", { fontName: fontName, sizeInPixels: sizeInPixels }) end function + ' @param {String} fontName + ' @param {Float} sizeInPixels + ' @param {String} [mode="uniform"] + ' @returns {Node} - returns scaled Font component + prototype.getScaledFont = function (fontName as String, sizeInPixels as Float, mode = "uniform" as String) as Object + return m._theme.callFunc("getScaledFont", { fontName: fontName, mode: mode, sizeInPixels: sizeInPixels }) + end function + ' Useful when you need to get font uri like for SimpleLabel component ' @param {String} fontName ' @returns {String} @@ -28,6 +36,30 @@ function ThemeFacade() as Object return m._theme.callFunc("getFont", { fontName: fontName }).uri end function + ' @param {Float} value + ' @returns {Integer} + prototype.scaleX = function (value as Float) as Integer + return m._theme.callFunc("scaleX", { value: value }) + end function + + ' @param {Float} value + ' @returns {Integer} + prototype.scaleY = function (value as Float) as Integer + return m._theme.callFunc("scaleY", { value: value }) + end function + + ' @param {Float} value + ' @returns {Integer} + prototype.scaleUniform = function (value as Float) as Integer + return m._theme.callFunc("scaleUniform", { value: value }) + end function + + ' @param {Object} size + ' @returns {Object} + prototype.scaleSize = function (size as Object) as Object + return m._theme.callFunc("scaleSize", { size: size }) + end function + ' It sets alpha channel on the RGB color by manipulating last 2 chars ' @param {String} color - hexadecimal color ' @param {Float} opacity diff --git a/src/components/theme/Theme.xml b/src/components/theme/Theme.xml index adc5193..69e2170 100644 --- a/src/components/theme/Theme.xml +++ b/src/components/theme/Theme.xml @@ -3,7 +3,12 @@ + + + + + diff --git a/src/components/theme/_mocks/Theme.facade.mock.brs b/src/components/theme/_mocks/Theme.facade.mock.brs index b74b959..f5fa25b 100644 --- a/src/components/theme/_mocks/Theme.facade.mock.brs +++ b/src/components/theme/_mocks/Theme.facade.mock.brs @@ -11,12 +11,27 @@ function ThemeFacade() as Object getFont: function (fontName as String, sizeInPixels as Integer) as Object return m.getFontMock("getFont", { fontName: fontName, sizeInPixels: sizeInPixels }, "Object") end function, + getScaledFont: function (fontName as String, sizeInPixels as Float, mode = "uniform" as String) as Object + return m.getScaledFontMock("getScaledFont", { fontName: fontName, mode: mode, sizeInPixels: sizeInPixels }, "Object") + end function, getFontUri: function (fontName as String) as String return m.getFontUriMock("getFontUri", { fontName: fontName }, "String") end function, rgba: function (color as String, opacity as Float) as String return m.rgbaMock("rgba", { color: color, opacity: opacity }, "String") end function, + scaleX: function (value as Float) as Integer + return m.scaleXMock("scaleX", { value: value }, "Integer") + end function, + scaleY: function (value as Float) as Integer + return m.scaleYMock("scaleY", { value: value }, "Integer") + end function, + scaleUniform: function (value as Float) as Integer + return m.scaleUniformMock("scaleUniform", { value: value }, "Integer") + end function, + scaleSize: function (size as Object) as Object + return m.scaleSizeMock("scaleSize", { size: size }, "Object") + end function, }, properties: { backgroundColor: { diff --git a/src/components/theme/_tests/Theme.test.brs b/src/components/theme/_tests/Theme.test.brs index a7c786e..dca0b77 100644 --- a/src/components/theme/_tests/Theme.test.brs +++ b/src/components/theme/_tests/Theme.test.brs @@ -6,6 +6,9 @@ function TestSuite__Theme() as Object ts.setBeforeEach(sub (_ts as Object) m.global.delete("theme") + if (m.top.hasField("resolution")) + m.top.removeField("resolution") + end if setAppTheme({ fonts: { regular: __createFont("example.ttf") }, }) @@ -57,6 +60,88 @@ function TestSuite__Theme() as Object return ts.assertEqual(expectedOptions, resultOptions) end function) + ts.addTest("should scale x values using resolution width", function (ts as Object) as String + ' Given + __setResolution(1280, 720) + + ' When + result = scaleX({ value: 1920 }) + + ' Then + return ts.assertEqual(1280, result) + end function) + + ts.addTest("should scale y values using resolution height", function (ts as Object) as String + ' Given + __setResolution(1280, 720) + + ' When + result = scaleY({ value: 1080 }) + + ' Then + return ts.assertEqual(720, result) + end function) + + ts.addTest("should scale uniform values using smaller axis", function (ts as Object) as String + ' Given + __setResolution(1280, 720) + + ' When + result = scaleUniform({ value: 48 }) + + ' Then + return ts.assertEqual(32, result) + end function) + + ts.addTest("should return original values for fhd scale", function (ts as Object) as String + ' Given + __setResolution(1920, 1080) + + ' When + result = scaleSize({ size: [960, 540] }) + + ' Then + return ts.assertEqual([960, 540], result) + end function) + + ts.addTest("should scale size arrays per axis", function (ts as Object) as String + ' Given + __setResolution(1280, 720) + + ' When + result = scaleSize({ size: [960, 540] }) + + ' Then + return ts.assertEqual([640, 360], result) + end function) + + ts.addTest("should fallback to fhd when resolution is missing", function (ts as Object) as String + ' When + result = scaleUniform({ value: 48 }) + + ' Then + return ts.assertEqual(48, result) + end function) + + ts.addTest("should create scaled font using uniform mode by default", function (ts as Object) as String + ' Given + __setResolution(1280, 720) + expectedOptions = { + uri: "pkg:/fonts/example.ttf", + size: 32, + } + + ' When + font = getScaledFont({ fontName: "regular", sizeInPixels: 48 }) + resultOptions = { + uri: font.uri, + size: font.size, + } + + ' Then + return ts.assertEqual(expectedOptions, resultOptions) + end function) + ts.addTest("should set opacity to FF when given 1 as opacity parameter", function (ts as Object) as String ' Given color = "0xF2000000" @@ -99,6 +184,21 @@ function TestSuite__Theme() as Object return ts end function +sub __setResolution(width as Integer, height as Integer) + m.global.delete("theme") + if (m.top.hasField("fonts")) + m.top.removeField("fonts") + end if + if (m.top.hasField("resolution")) + m.top.removeField("resolution") + end if + + setAppTheme({ + fonts: { regular: __createFont("example.ttf") }, + resolution: { height: height, width: width }, + }) +end sub + function __createFont(fontFileName as String) as Object font = CreateObject("roSGNode", "Font") font.uri = "pkg:/fonts/" + fontFileName