Compare commits

...

60 Commits

Author SHA1 Message Date
Patrick Goldinger
cdc8635793 Release v0.3.3 2021-01-17 22:56:24 +01:00
Patrick Goldinger
0ed3e7bd22 Update translations from Crowdin 2021-01-17 22:32:03 +01:00
Patrick Goldinger
18ac2dc0d8 Update CONTRIBUTING.md content adding info 2021-01-17 21:48:10 +01:00
Patrick Goldinger
c04ca29421 Merge pull request #198 from klausweiss/feature/pl-layout
Add extended popups for Polish
2021-01-17 19:54:34 +01:00
Patrick Goldinger
b6466daebb Merge branch 'master' into feature/pl-layout 2021-01-17 19:51:00 +01:00
Patrick Goldinger
d9dedc447f Merge pull request #184 from bertin0/romanian_layout
Add romanian layout, based on QWERTY (closes issue #121)
2021-01-17 19:21:55 +01:00
Patrick Goldinger
5dbd98ae9e Merge branch 'master' into romanian_layout 2021-01-17 19:18:49 +01:00
Patrick Goldinger
f8db145a63 Merge pull request #171 from tsiflimagas/greek_layout
Add Greek layout
2021-01-17 19:16:29 +01:00
Patrick Goldinger
563a24b7d1 Merge branch 'master' into greek_layout 2021-01-17 19:11:15 +01:00
Patrick Goldinger
5dbea21fab Merge pull request #167 from williamtheaker/wt.popup_character
Adds colon character to long press. Fixes #166
2021-01-17 19:01:08 +01:00
Patrick Goldinger
ce937c3f58 Merge pull request #137 from williamtheaker/wt.russian
Add Russian layout; Document adding new languages
2021-01-17 18:57:05 +01:00
Patrick Goldinger
439fdade51 Merge branch 'master' into wt.russian 2021-01-17 18:50:26 +01:00
Patrick Goldinger
a77531e483 Fix comma typo in hr.json 2021-01-17 18:47:35 +01:00
Patrick Goldinger
343646f0f8 Merge pull request #134 from HeDidNothingWrong/patch-2
Update config.json to include hr keyboard extension
2021-01-17 18:46:26 +01:00
Patrick Goldinger
91c2337633 Merge pull request #133 from HeDidNothingWrong/patch-1
Create hr.json
2021-01-17 18:45:28 +01:00
Patrick Goldinger
000c0f1e30 Merge pull request #162 from florisboard/feat-theme-rework
Theme rework (Milestone v0.4.0 / E) & Asset manager base (Milestone v0.4.0 / C)
2021-01-17 18:30:02 +01:00
Patrick Goldinger
2401b1c776 Add doc strings to some Theme classes 2021-01-17 18:26:50 +01:00
Patrick Goldinger
555e329447 Remove obsolete resources and pref declarations 2021-01-17 17:15:31 +01:00
Patrick Goldinger
a2b3033d04 Add input validation / Fix UI and logic bugs 2021-01-17 17:00:47 +01:00
Patrick Goldinger
1020fff6cd Implement create new theme fab option 2021-01-17 12:23:55 +01:00
Mikołaj Biel
6fa4fadf04 Add Polish layout 2021-01-17 10:21:03 +01:00
Patrick Goldinger
f386428acd Add license note for expandable fab library 2021-01-16 21:48:35 +01:00
Patrick Goldinger
f45540eab1 Add adaptive theme coloring 2021-01-16 21:44:33 +01:00
Patrick Goldinger
0d509f8cfb Add Theme Editor UI 2021-01-15 20:41:03 +01:00
Albert Geantă
5fd26affc9 Add romanian layout, based on QWERTY 2021-01-14 15:02:09 +02:00
tsiflimagas
198ae1fc7c Add Greek layout 2021-01-14 10:18:08 +02:00
Patrick Goldinger
1e9ce7ba54 Add basic Edit Theme UI implementation / Fix bugs 2021-01-14 01:30:27 +01:00
Willie Theaker
702785b7fc Fix incorrect character 2021-01-13 12:43:24 -08:00
Patrick Goldinger
8c37c6188b Add borderless theme presets (day & night) 2021-01-13 01:20:59 +01:00
Patrick Goldinger
69ad3e2352 Add basic Theme Manager UI 2021-01-13 01:20:30 +01:00
Willie Theaker
9161d1574d Oops misread issue 2021-01-11 14:56:14 -08:00
Willie Theaker
7ff7309e76 Adds colon character to long press. Fixes #166 2021-01-11 14:50:49 -08:00
Willie Theaker
6df803c239 Add long press characters 2021-01-11 13:15:45 -08:00
Patrick Goldinger
3385fe7cbd Add "Follow time" theme mode implementation 2021-01-11 19:57:50 +01:00
Patrick Goldinger
320b9e0751 Fix emoji keyboard view being default layout 2021-01-11 19:03:04 +01:00
Patrick Goldinger
9178207653 Improve KeyView theme attribute logic and performance
The KeyView now better caches and refreshes the theme of the key
according to its current state. Also, the Theme.getAttr() method
has been improved (both in terms of functionality and in performance).
2021-01-11 01:43:06 +01:00
Patrick Goldinger
90b0812ae4 Add basic Theme UI in Settings / Fix bugs
Does not include theme modification (yet)
2021-01-10 20:55:38 +01:00
Patrick Goldinger
fb03a82e45 Fix icon fill color not adapting to current Theme 2021-01-10 20:53:32 +01:00
Patrick Goldinger
f5a7220ba7 Adapt views to work with new theme logic 2021-01-10 13:47:52 +01:00
Patrick Goldinger
e413f3918e Add theme manager and new theme declaration 2021-01-10 13:45:51 +01:00
Patrick Goldinger
d25bdd8938 Add basic asset manager 2021-01-10 13:43:42 +01:00
Willie Theaker
1f84d08fa9 Add Russian layout; Document adding new langauges 2021-01-08 12:23:03 -08:00
HeDidNothingWrong
da9d68dd3b Update config.json
Update config for croatian extended popup
2021-01-08 18:49:21 +01:00
HeDidNothingWrong
1bc36ceec7 Update hr.json 2021-01-08 18:42:33 +01:00
HeDidNothingWrong
c57b60d00c Create hr.json
Croatian additional symbols (lowercase) - also applicable for Serbia, Slovenia, Bosnia. Casually combined with qwertz
2021-01-08 18:08:16 +01:00
Patrick Goldinger
e304fbd120 Update F-Droid info in README.md 2021-01-08 09:13:08 +01:00
Patrick Goldinger
7eb7c21e13 Merge pull request #122 from florisboard/feat-popup-layer
Rework popup UI implementation
2021-01-05 17:07:02 +01:00
Patrick Goldinger
710e7ca85e Fix overdraw issues
Overdraw caused high CPU usage (~25-50% of CPU when open but no
touch events). Now the CPU is only used to process input events and
execute any follow up events. If FlorisBoard is nw left open without
using it, CPU usage goes down to 0%.
2021-01-05 03:14:46 +01:00
Patrick Goldinger
207845d46f Rework popup UI implementation 2021-01-04 18:18:08 +01:00
Patrick Goldinger
707d54b6f4 Merge pull request #118 from yashx/border
Added Option to Hide keys border
2021-01-01 13:03:33 +01:00
Patrick Goldinger
c01f167d49 Merge pull request #117 from yashx/switchKeyboard
Added gesture option to quick switch to previous keyboard
2020-12-31 14:44:07 +01:00
Patrick Goldinger
176ca00f66 Merge pull request #119 from florisboard/feat-improve-layout-definition
Improve layout definition structure
2020-12-31 14:35:19 +01:00
Patrick Goldinger
081cfdb0ee Add new libraries to README and include license texts 2020-12-31 13:31:50 +01:00
Patrick Goldinger
d9f94aecac Add documentation / Improve Asset interface 2020-12-31 13:31:05 +01:00
yashx
6691706aed Added Option to Hide keys border #87 2020-12-31 17:46:45 +05:30
yashx
1b77138798 Added gesture option to quick switch to previous keyboard 2020-12-31 00:36:43 +05:30
Patrick Goldinger
07ebd04052 Adapt new extended popup structure to json files 2020-12-30 19:49:03 +01:00
Patrick Goldinger
40c2bfd819 Adopt improved keyboard definition of existing layouts 2020-12-30 03:22:27 +01:00
Patrick Goldinger
855ad47674 Add default extended popup mapping 2020-12-30 03:21:35 +01:00
Patrick Goldinger
e032e4acb8 Complete rework of the key data structure + popups
This rework is a very important one, as it solves many conflicts which
arised while implementing special keyboard layouts like dvorak, etc.

With the new data structure, popup keys carry around way less baggage,
but at the same time the structure around the keys have improved a lot and provide more useful information for the popup manager.
This includes that smart hint/accent prioritization is now working
theoretically, now it just needs to be defined correctly in the
json files.

Also the layout and extended popup json files got a authors field
which indicates who the author is. At the current time not very
important, but this is a small step in modularization into assets,
which are the very base of extension support (but that's still a
looong way).

Also some parts of the LayoutManager got a code cleanup, which makes
especially the merging of popups and hints better readable.
2020-12-30 03:20:57 +01:00
234 changed files with 9542 additions and 3764 deletions

View File

@@ -32,12 +32,29 @@ free to ask for help at any time!
## Adding a new keyboard layout / dictionary for locale
As FlorisBoard is currently in alpha stage, things might change
drastically. This also includes the config scheme of keyboard layouts.
To prevent incompatible configs because some features and structures may
change, please do not add this kind of content yet. As FlorisBoard's
state progresses and its core stabilizes, you will be able to add
keyboard layouts.
You can now oficially add layouts to FlorisBoard as described below.
FlorisBoard's core has stabilized enough that adding new content is
safe, although there will be some changes in the future.
Currently you need to modify `app/src/main/assets/ime/config.json` to
add the filename of the language/layout to the `characterLayouts`
section and the `defaultSubtypes` section, making sure to include
the language's IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
for `id` to avoid possible crahses caused by duplicate ids.
Add the keyboard layout at `app/src/main/assets/ime/text/characters/<preferredLayout_name_here>.json`,
with `code` referring to the characters codepoint and `label` being the
respective unicode character.
Any accents or diacritics that should be exposed via long press can be
added at `assets/ime/text/characters/extended_popups/<languageTag_name_here>.json`.
For each key, you can add 1 main and several relevant accents. The main
accent should be used for accents which are important for the language
you add. The main field is used for determining if a hint or an accent
should take priority, so please make sure to leave main empty and just
use relevant for accents which are not-so important.
## Bug reporting

View File

@@ -10,9 +10,9 @@ fully respecting your privacy. Currently in alpha/early-beta state.
## Public Alpha Test Programme
Wanna try it out on your device? Use one of the following options:
_A. IzzySoft's repo for F-Droid_:
_A. Get it on F-Droid_:
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge">](https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge">](https://f-droid.org/packages/dev.patrickgold.florisboard)
_B. Google Play Public Alpha Test_:
@@ -31,17 +31,15 @@ tester, follow these steps:
4. Finished! You will receive future versions of FlorisBoard via Google
Play.
With the v0.4.0 release FlorisBoard will enter the public beta in GPlay, allowing to directly search
for and download FlorisBoard without prior joining the alpha group.
_C. Use the APK provided in the release section of this repo_
### Giving feedback
If you want to give feedback to FlorisBoard, there are several ways to
do so, as listed [here](CONTRIBUTING.md#giving-general-feedback).
### Note on F-Droid release
FlorisBoard is currently available through Google Play and IzzySoft's
repo for F-Droid, but is in the inclusion process for the main F-Droid
repo.
---
<img align="right" height="256"
@@ -65,9 +63,10 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
### Layouts
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish,
Norwegian, Swedish/Finnish, Icelandic, Danish, Hungarian); more
coming in future versions
* [x] Non-latin character layouts (Arabic, Persian)
Norwegian, Swedish/Finnish, Icelandic, Danish, Hungarian,
Croatian, Polish, Romanian); more coming in future versions
* [x] Non-latin character layouts (Arabic, Persian, Greek, Russian
(JCUKEN))
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
@@ -80,7 +79,7 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
* [x] Setup wizard
* [x] Preferences screen
* [x] Customize look and behaviour of keyboard
* [x] Theme presets (currently only day/night theme)
* [x] Theme presets (currently only day/night theme + borderless)
* [x] Theme customization
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
@@ -123,8 +122,8 @@ close as possible.
words over time. Data collected here is stored locally and never leaves
the user's device.
- Module C: Extension packs
- Ability to load dictionaries (and later potentionally other cool
- Module C: Extension packs (base implementation with #162)
- Ability to load dictionaries (and later potentially other cool
features too) only if needed to keep the core APK size small
- Currently unclear how exactly this will work, but this is definitely
a must-have feature
@@ -133,6 +132,14 @@ close as possible.
- Swiping over the characters will automatically convert this to a word
- Possibly also add improvements based on the Flow keyboard
- Module E: Theme rework (Implemented with #162)
- Themes are now based on the Asset schema
- Dynamic theme creation
- Different theme modes (`Always day`, `Always dark`, `Follow system`
and `Follow time`)
- Define a separate theme both for day and night theme
- Adapt to app theme if possible
### [v0.5.0](https://github.com/florisboard/florisboard/milestone/5)
There's no exact roadmap yet but it is planned that the media part of
FlorisBoard (emojis, emoticons, kaomoji) gets a rework. Also as an extension
@@ -167,6 +174,12 @@ to get more information on this topic.
[square](https://github.com/square)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
* [Timber](https://github.com/JakeWharton/timber) by
[JakeWharton](https://github.com/JakeWharton)
* [kotlin-result](https://github.com/michaelbull/kotlin-result) by
[Michael Bull](https://github.com/michaelbull)
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
[Nambi](https://github.com/nambicompany)
## License
```

View File

@@ -6,12 +6,21 @@ android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
defaultConfig {
applicationId "dev.patrickgold.florisboard"
minSdkVersion 23
targetSdkVersion 29
versionCode 21
versionName "0.3.2"
versionCode 22
versionName "0.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -31,6 +40,12 @@ android {
resValue "string", "app_name", "FlorisBoard"
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
@@ -45,13 +60,17 @@ dependencies {
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.mockito:mockito-core:1.10.19'
testImplementation 'org.mockito:mockito-inline:2.13.0'
testImplementation 'org.robolectric:robolectric:4.4'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android:flexbox:2.0.1'
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
implementation 'com.squareup.moshi:moshi-adapters:1.9.2'
implementation 'com.google.android.material:material:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation "com.michael-bull.kotlin-result:kotlin-result:1.1.9"
implementation 'com.nambimobile.widgets:expandable-fab:1.0.2'
}

View File

@@ -67,6 +67,20 @@
</intent-filter>
</activity-alias>
<!-- Theme Selector Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeManagerActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/settings__title"
android:theme="@style/SettingsTheme"/>
<!-- Theme Editor Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeEditorActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/settings__theme_editor__title"
android:theme="@style/SettingsTheme"/>
<!-- About Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.AboutActivity"

View File

@@ -19,7 +19,9 @@
"esperanto_with_hx": "Esperanto with 'ĥ'",
"colemak": "Colemak",
"dvorak": "Dvorak",
"canadian_french": "Canadian French (QWERTY)"
"jcuken_russian": "Russian (JCUKEN)",
"canadian_french": "Canadian French (QWERTY)",
"greek": "Ελληνικά"
},
"defaultSubtypes": [
{
@@ -156,6 +158,31 @@
"id": 1101,
"languageTag": "eo",
"preferredLayout": "esperanto"
},
{
"id": 1201,
"languageTag": "hr",
"preferredLayout": "qwertz"
},
{
"id": 1301,
"languageTag": "ru",
"preferredLayout": "jcuken_russian"
},
{
"id": 1401,
"languageTag": "el",
"preferredLayout": "greek"
},
{
"id": 1501,
"languageTag": "ro",
"preferredLayout": "qwerty"
},
{
"id": 1601,
"languageTag": "pl",
"preferredLayout": "qwerty"
}
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "arabic",
"authors": [ "HeiWiper" ],
"direction": "rtl",
"modifier": "arabic",
"arrangement": [
@@ -33,7 +34,7 @@
[
{ "code": 1584, "label": "ذ" },
{ "code": 1569, "label": "ء" },
{ "code": 65157, "label": "ﺅ" },
{ "code": 65157, "label": "ﺅ" },
{ "code": 1585, "label": "ر" },
{ "code": 1609, "label": "ى" },
{ "code": 1577, "label": "ة" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "azerty",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 113, "label": "q" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,20 +27,23 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 109, "label": "m" }
], [
],
[
{ "code": 119, "label": "w" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },
{ "code": 110, "label": "n" },
{ "code": 39, "label": "'", "popup": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
] }
{ "code": 39, "label": "'", "popup": {
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "canadian_french",
"authors": [ "The-Quantum-Alpha" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 232, "label": "è" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é" },
{ "code": 224, "label": "à" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "colemak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -13,10 +14,13 @@
{ "code": 108, "label": "l" },
{ "code": 117, "label": "u" },
{ "code": 121, "label": "y" },
{ "code": 59, "label": ";", "popup": [
{ "code": 58, "label": ":" }
] }
], [
{ "code": 59, "label": ";", "popup": {
"relevant": [
{ "code": 58, "label": ":" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 114, "label": "r" },
{ "code": 115, "label": "s" },
@@ -27,7 +31,8 @@
{ "code": 101, "label": "e" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "danish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 230, "label": "æ" },
{ "code": 248, "label": "ø" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,23 +1,36 @@
{
"type": "characters",
"name": "dvorak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"modifier": "dvorak",
"arrangement": [
[
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 39, "label": "'", "variation": "normal", "popup": [
{ "code": 33, "label": "!" },
{ "code": 34, "label": "\"" }
] },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": 44, "label": ",", "popup": [
{ "code": 60, "label": "<" },
{ "code": 63, "label": "?" }
] },
{ "code": 46, "label": ".", "popup": [
{ "code": 62, "label": ">" }
] },
{ "code": 64, "label": "@", "groupId": 101, "variation": "email_address" },
{ "code": 39, "label": "'", "groupId": 101, "variation": "normal", "popup": {
"relevant": [
{ "code": 33, "label": "!" },
{ "code": 34, "label": "\"" }
]
} },
{ "code": 39, "label": "'", "groupId": 101, "variation": "password", "popup": {
"relevant": [
{ "code": 33, "label": "!" },
{ "code": 34, "label": "\"" }
]
} },
{ "code": 47, "label": "/", "groupId": 101, "variation": "uri" },
{ "code": 44, "label": ",", "popup": {
"relevant": [
{ "code": 60, "label": "<" },
{ "code": 63, "label": "?" }
]
} },
{ "code": 46, "label": ".", "popup": {
"relevant": [
{ "code": 62, "label": ">" }
]
} },
{ "code": 112, "label": "p" },
{ "code": 121, "label": "y" },
{ "code": 102, "label": "f" },
@@ -25,7 +38,8 @@
{ "code": 99, "label": "c" },
{ "code": 114, "label": "r" },
{ "code": 108, "label": "l" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 111, "label": "o" },
{ "code": 101, "label": "e" },
@@ -36,7 +50,8 @@
{ "code": 116, "label": "t" },
{ "code": 110, "label": "n" },
{ "code": 115, "label": "s" }
], [
],
[
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 120, "label": "x" },

View File

@@ -1,20 +1,28 @@
{
"type": "characters",
"name": "esperanto",
"authors": [ "jeremiah-miller", "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 349, "label": "ŝ" },
{ "code": 285, "label": "ĝ" },
{ "code": 349, "label": "ŝ", "popup": {
"main": { "code": 113, "label": "q" }
} },
{ "code": 285, "label": "ĝ", "popup": {
"main": { "code": 119, "label": "w" }
} },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 365, "label": "ŭ" },
{ "code": 365, "label": "ŭ", "popup": {
"main": { "code": 121, "label": "y" }
} },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,9 +33,12 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 309, "label": "ĵ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 265, "label": "ĉ" },
{ "code": 265, "label": "ĉ", "popup": {
"main": { "code": 120, "label": "x" }
} },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },

View File

@@ -1,20 +1,28 @@
{
"type": "characters",
"name": "esperanto_with_hx",
"authors": [ "jeremiah-miller", "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 349, "label": "ŝ" },
{ "code": 285, "label": "ĝ" },
{ "code": 349, "label": "ŝ", "popup": {
"main": { "code": 113, "label": "q" }
} },
{ "code": 285, "label": "ĝ", "popup": {
"main": { "code": 119, "label": "w" }
} },
{ "code": 101, "label": "e" },
{ "code": 114, "label": "r" },
{ "code": 116, "label": "t" },
{ "code": 365, "label": "ŭ" },
{ "code": 365, "label": "ŭ", "popup": {
"main": { "code": 121, "label": "y" }
} },
{ "code": 117, "label": "u" },
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,9 +33,12 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 309, "label": "ĵ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 265, "label": "ĉ" },
{ "code": 265, "label": "ĉ", "popup": {
"main": { "code": 120, "label": "x" }
} },
{ "code": 99, "label": "c" },
{ "code": 118, "label": "v" },
{ "code": 98, "label": "b" },

View File

@@ -0,0 +1,22 @@
{
"type": "characters/extended_popups",
"name": "$default",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"~enter": {
"main": { "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
"relevant": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
]
},
"~left": {
"main": { "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
"relevant": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
]
}
}
}
}

View File

@@ -1,97 +1,148 @@
{
"ض": [
{ "code": 1633, "label": "١" }
],
"ص": [
{ "code": 1634, "label": "٢" }
],
": [
{ "code": 1635, "label": "٣" }
],
"ق": [
{ "code": 1704, "label": "ڨ"},
{ "code": 1636, "label": "٤" }
],
"ف": [
{ "code": 1701, "label": "ڥ" },
{ "code": 1700, "label": "ڤ" },
{ "code": 1698, "label": "ڢ" },
{ "code": 1637, "label": "٥" }
],
"غ": [
{ "code": 1638, "label": "٦" }
],
"ع": [
{ "code": 1639, "label": "٧" }
],
"ه": [
{ "code": 1726, "label": "ھ" },
{ "code": 1640, "label": "٨" }
],
"خ": [
{ "code": 1641, "label": "٩" }
],
"ح": [
{ "code": 1632, "label": "٠" }
],
": [
{ "code": 1670, "label": "چ" }
],
"ش": [
{ "code": 1692, "label": "ڜ" }
],
"ي": [
{ "code": 1574, "label": "ئ" },
{ "code": 1609, "label": "ى" }
],
": [
{ "code": 1662, "label": "پ" }
],
"ل": [
{ "code": 65275, "label": "لا" },
{ "code": 65273, "label": "لإ" },
{ "code": 65271, "label": "لأ" },
{ "code": 65269, "label": "لآ" }
],
"ا": [
{ "code": 1570, "label": "آ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" },
{ "code": 1649, "label": "ٱ" }
],
": [
{ "code": 1705, "label": "ک"},
{ "code": 1711, "label": "گ" }
],
"ى": [
{ "code": 1574, "label": "ئ" }
],
"ز": [
{ "code": 1688, "label": "ژ" }
],
".~normal": [
{ "code": 1611, "label": "ً" },
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1620, "label": "ٔ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1618, "label": "ْ" }
],
".~uri": [
{ "code": -255, "label": ".ir"},
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "ar",
"authors": [ "HeiWiper" ],
"mapping": {
"all": {
"ض": {
"relevant": [
{ "code": 1633, "label": "١" }
]
},
"ص": {
"relevant": [
{ "code": 1634, "label": "٢" }
]
},
"ث": {
"relevant": [
{ "code": 1635, "label": "٣" }
]
},
"ق": {
"relevant": [
{ "code": 1704, "label": "ڨ" },
{ "code": 1636, "label": "٤" }
]
},
"ف": {
"relevant": [
{ "code": 1701, "label": "ڥ" },
{ "code": 1700, "label": "ڤ" },
{ "code": 1698, "label": "ڢ" },
{ "code": 1637, "label": "٥" }
]
},
"غ": {
"relevant": [
{ "code": 1638, "label": "٦" }
]
},
"ع": {
"relevant": [
{ "code": 1639, "label": "٧" }
]
},
"ه": {
"relevant": [
{ "code": 1726, "label": "ھ" },
{ "code": 1640, "label": "٨" }
]
},
"خ": {
"relevant": [
{ "code": 1641, "label": "٩" }
]
},
"ح": {
"relevant": [
{ "code": 1632, "label": "٠" }
]
},
"ج": {
"relevant": [
{ "code": 1670, "label": "چ" }
]
},
"ش": {
"relevant": [
{ "code": 1692, "label": "ڜ" }
]
},
"ي": {
"relevant": [
{ "code": 1574, "label": "ئ" },
{ "code": 1609, "label": "ى" }
]
},
"ب": {
"relevant": [
{ "code": 1662, "label": "پ" }
]
},
"ل": {
"relevant": [
{ "code": 65275, "label": "لا" },
{ "code": 65273, "label": "لإ" },
{ "code": 65271, "label": "لأ" },
{ "code": 65269, "label": "لآ" }
]
},
"ا": {
"relevant": [
{ "code": 1570, "label": "آ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" },
{ "code": 1649, "label": "ٱ" }
]
},
"ك": {
"relevant": [
{ "code": 1705, "label": "ک"},
{ "code": 1711, "label": "گ" }
]
},
"ى": {
"relevant": [
{ "code": 1574, "label": "ئ" }
]
},
"ز": {
"relevant": [
{ "code": 1688, "label": "ژ" }
]
},
"~right": {
"main": { "code": 1611, "label": "ً" },
"relevant": [
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1620, "label": "ٔ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1618, "label": "ْ" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,96 +1,133 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
],
"d": [
{ "code": 240, "label": "ð" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"l": [
{ "code": 322, "label": "ł" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "da",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" }
]
},
"u": {
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
"ø": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".eu" },
{ "code": -255, "label": ".dk" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,80 +1,109 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 230, "label": " },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 226, "label": "â" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "de",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"main": { "code": 228, "label": "ä" },
"relevant": [
{ "code": 230, "label": "æ" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 226, "label": "â" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"main": { "code": 246, "label": "ö" },
"relevant": [
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" }
]
},
"s": {
"main": { "code": 223, "label": "ß" },
"relevant": [
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"main": { "code": 252, "label": "ü" },
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ch" },
{ "code": -255, "label": ".de" },
{ "code": -255, "label": ".at" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,80 @@
{
"type": "characters/extended_popups",
"name": "el",
"authors": [ "tsiflimagas" ],
"mapping": {
"all": {
"α": {
"relevant": [
{ "code": 940, "label": "ά" }
]
},
"ε": {
"relevant": [
{ "code": 941, "label": "έ" }
]
},
"η": {
"relevant": [
{ "code": 942, "label": "ή" }
]
},
"ι": {
"relevant": [
{ "code": 912, "label": "ΐ" },
{ "code": 970, "label": "ϊ" },
{ "code": 943, "label": "ί" }
]
},
"ο": {
"relevant": [
{ "code": 972, "label": "ό" }
]
},
"υ": {
"relevant": [
{ "code": 944, "label": "ΰ" },
{ "code": 971, "label": "ϋ" },
{ "code": 973, "label": "ύ" }
]
},
"ω": {
"relevant": [
{ "code": 974, "label": "ώ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".gr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,90 +1,107 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 230, "label": " },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" }
],
"s": [
{ "code": 223, "label": "ß" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 249, "label": "ù" }
],
"ŝ": [
{ "code": 113, "label": "q" }
],
"ĝ": [
{ "code": 119, "label": "w" }
],
"ĉ": [
{ "code": 120, "label": "x" }
],
"ŭ": [
{ "code": 121, "label": "y" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "en",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 230, "label": "æ" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 224, "label": "à" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 275, "label": "ē" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" },
{ "code": 237, "label": "í" },
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 245, "label": "õ" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 243, "label": "ó" },
{ "code": 244, "label": "ô" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 249, "label": "ù" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,58 +1,90 @@
{
"c": [
{ "code": 265, "label": "ĉ" }
],
"g": [
{ "code": 285, "label": "ĝ" }
],
"h": [
{ "code": 293, "label": "ĥ" }
],
"j": [
{ "code": 309, "label": "ĵ" }
],
"s": [
{ "code": 349, "label": "ŝ" }
],
"u": [
{ "code": 365, "label": "ŭ" }
],
"q": [
{ "code": 349, "label": "ŝ" }
],
"w": [
{ "code": 285, "label": "ĝ" }
],
"x": [
{ "code": 265, "label": "ĉ" }
],
"y": [
{ "code": 365, "label": "ŭ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" },
{ "code": 61, "label": "=" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "eo",
"authors": [ "jeremiah-miller" ],
"mapping":{
"all": {
"c": {
"relevant": [
{ "code": 265, "label": "ĉ" }
]
},
"g": {
"relevant": [
{ "code": 285, "label": "ĝ" }
]
},
"h": {
"relevant": [
{ "code": 293, "label": "ĥ" }
]
},
"j": {
"relevant": [
{ "code": 309, "label": "ĵ" }
]
},
"s": {
"relevant": [
{ "code": 349, "label": "ŝ" }
]
},
"u": {
"relevant": [
{ "code": 365, "label": "ŭ" }
]
},
"q": {
"relevant": [
{ "code": 349, "label": "ŝ" }
]
},
"w": {
"relevant": [
{ "code": 285, "label": "ĝ" }
]
},
"x": {
"relevant": [
{ "code": 265, "label": "ĉ" }
]
},
"y": {
"relevant": [
{ "code": 365, "label": "ŭ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,86 +1,115 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 229, "label": " },
{ "code": 261, "label": "ą" },
{ "code": 230, "label": "æ" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 224, "label": "à" },
{ "code": 228, "label": "ä" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 235, "label": "ë" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 238, "label": "î" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" }
],
"s": [
{ "code": 223, "label": "ß" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 58, "label": ":" },
{ "code": 38, "label": "&" },
{ "code": 64, "label": "@" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 161, "label": "¡" },
{ "code": 39, "label": "'" },
{ "code": 191, "label": "¿" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "es",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"main": { "code": 225, "label": "á" },
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 261, "label": "ą" },
{ "code": 230, "label": "æ" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 224, "label": "à" },
{ "code": 228, "label": "ä" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" }
]
},
"c": {
"relevant": [
{ "code": 269, "label": "č" },
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 235, "label": "ë" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 299, "label": "ī" },
{ "code": 238, "label": "î" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"main": { "code": 243, "label": "ó" },
"relevant": [
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 245, "label": "õ" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".com.es" },
{ "code": -255, "label": ".es" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,80 +1,125 @@
{
"ض": [
{ "code": 1777, "label": "۱" }
],
"ص": [
{ "code": 1778, "label": "۲" }
],
": [
{ "code": 1779, "label": "۳" }
],
"ق": [
{ "code": 1780, "label": "۴" }
],
"ف": [
{ "code": 1781, "label": "۵" }
],
"غ": [
{ "code": 1782, "label": "۶" }
],
"ع": [
{ "code": 1783, "label": "۷" }
],
"ه": [
{ "code": 1784, "label": "۸" }
],
"خ": [
{ "code": 1785, "label": "۹" }
],
"ح": [
{ "code": 1776, "label": "۰" }
],
"ی": [
{ "code": 1574, "label": "ئ" },
{ "code": 1610, "label": "ي" }
],
"ا": [
{ "code": 1570, "label": "آ" },
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" }
],
": [
{ "code": 1577, "label": "ة" }
],
"ک": [
{ "code": 1706, "label": "ڪ"},
{ "code": 1603, "label": "ك" }
],
"ز": [
{ "code": 1688, "label": "ژ" }
],
": [
{ "code": 1572, "label": "ؤ" }
],
".~normal": [
{ "code": 1611, "label": "ً" },
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1618, "label": "ْ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1620, "label": "ٔ" }
],
".~uri": [
{ "code": -255, "label": ".ir"},
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fa",
"authors": [ "PHELAT" ],
"mapping": {
"all": {
"ض": {
"relevant": [
{ "code": 1777, "label": "۱" }
]
},
"ص": {
"relevant": [
{ "code": 1778, "label": "۲" }
]
},
"ث": {
"relevant": [
{ "code": 1779, "label": "۳" }
]
},
"ق": {
"relevant": [
{ "code": 1780, "label": "۴" }
]
},
"ف": {
"relevant": [
{ "code": 1781, "label": "۵" }
]
},
"غ": {
"relevant": [
{ "code": 1782, "label": "۶" }
]
},
"ع": {
"relevant": [
{ "code": 1783, "label": "۷" }
]
},
"ه": {
"relevant": [
{ "code": 1784, "label": "۸" }
]
},
"خ": {
"relevant": [
{ "code": 1785, "label": "۹" }
]
},
"ح": {
"relevant": [
{ "code": 1776, "label": "۰" }
]
},
"ی": {
"relevant": [
{ "code": 1574, "label": "ئ" },
{ "code": 1610, "label": "ي" }
]
},
"ا": {
"relevant": [
{ "code": 1570, "label": "آ" },
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" }
]
},
": {
"relevant": [
{ "code": 1577, "label": "ة" }
]
},
"ک": {
"relevant": [
{ "code": 1706, "label": "ڪ"},
{ "code": 1603, "label": "ك" }
]
},
"ز": {
"relevant": [
{ "code": 1688, "label": "ژ" }
]
},
"و": {
"relevant": [
{ "code": 1572, "label": "ؤ" }
]
},
"~right": {
"main": { "code": 1611, "label": "ً" },
"relevant": [
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1618, "label": "ْ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1620, "label": "ٔ" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,87 +1,118 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 224, "label": "à" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 248, "label": "ø" }
],
"s": [
{ "code": 353, "label": "š" },
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 249, "label": "ù" }
],
"z": [
{ "code": 382, "label": "ž" },
{ "code": 380, "label": "ż" },
{ "code": 378, "label": "ź" }
],
"ä": [
{ "code": 230, "label": "æ" }
],
"ö": [
{ "code": 248, "label": "ø" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fi",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 224, "label": "à" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"o": {
"relevant": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 248, "label": "ø" }
]
},
"s": {
"relevant": [
{ "code": 353, "label": "š" },
{ "code": 223, "label": "ß" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 249, "label": "ù" }
]
},
"z": {
"relevant": [
{ "code": 382, "label": "ž" },
{ "code": 380, "label": "ż" },
{ "code": 378, "label": "ź" }
]
},
"ä": {
"relevant": [
{ "code": 230, "label": "æ" }
]
},
": {
"relevant": [
{ "code": 248, "label": "ø" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,90 +1,121 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": " },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 226, "label": "â" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" },
{ "code": 237, "label": "í" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 244, "label": "ô" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 248, "label": "ø" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 339, "label": "œ" }
],
"s": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
],
"u": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"y": [
{ "code": 255, "label": "ÿ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "fr",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 226, "label": "â" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" },
{ "code": 228, "label": "ä" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 238, "label": "î" },
{ "code": 299, "label": "ī" },
{ "code": 237, "label": "í" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 239, "label": "ï" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 244, "label": "ô" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 248, "label": "ø" },
{ "code": 243, "label": "ó" },
{ "code": 242, "label": "ò" },
{ "code": 246, "label": "ö" },
{ "code": 339, "label": "œ" }
]
},
"s": {
"relevant": [
{ "code": 223, "label": "ß" },
{ "code": 353, "label": "š" },
{ "code": 347, "label": "ś" }
]
},
"u": {
"relevant": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"y": {
"relevant": [
{ "code": 255, "label": "ÿ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,61 @@
{
"type": "characters/extended_popups",
"name": "hr",
"authors": [ "hedidnothingwrong" ],
"mapping": {
"all": {
"c": {
"relevant": [
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"d": {
"relevant": [
{ "code": 273, "label": "đ" }
]
},
"s": {
"relevant": [
{ "code": 353, "label": "š" }
]
},
"z": {
"relevant": [
{ "code": 382, "label": "ž" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".eu" },
{ "code": -255, "label": ".hr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,53 +1,80 @@
{
"a": [
{ "code": 225, "label": "á" }
],
"e": [
{ "code": 233, "label": "é" }
],
"i": [
{ "code": 237, "label": "í" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" },
{ "code": 337, "label": "ő" }
],
"ö": [
{ "code": 337, "label": "ő" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 369, "label": "ű" }
],
"ü": [
{ "code": 369, "label": "ű" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".hu" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "hu",
"authors": [ "zoli111" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 225, "label": "á" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" },
{ "code": 246, "label": "ö" },
{ "code": 337, "label": "ő" }
]
},
"ö": {
"relevant": [
{ "code": 337, "label": "ő" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 369, "label": "ű" }
]
},
"ü": {
"relevant": [
{ "code": 369, "label": "ű" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".hu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,81 +1,110 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" },
{ "code": 229, "label": "å" }
],
"d": [
{ "code": 240, "label": "ð" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"t": [
{ "code": 254, "label": "þ" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "is",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 225, "label": "á" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" },
{ "code": 229, "label": "å" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 234, "label": "ê" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 236, "label": "ì" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 239, "label": "ï" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 339, "label": "œ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"t": {
"relevant": [
{ "code": 254, "label": "þ" }
]
},
"u": {
"relevant": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 252, "label": "ü" },
{ "code": 249, "label": "ù" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,77 +1,103 @@
{
"a": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": " },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" }
],
"e": [
{ "code": 232, "label": "è" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 236, "label": "ì" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 237, "label": "í" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 242, "label": "ò" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" }
],
"u": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "it",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 224, "label": "à" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 257, "label": "ā" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 228, "label": "ä" },
{ "code": 230, "label": "æ" }
]
},
"e": {
"relevant": [
{ "code": 232, "label": "è" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 237, "label": "í" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 242, "label": "ò" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" },
{ "code": 245, "label": "õ" },
{ "code": 246, "label": "ö" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" }
]
},
"u": {
"relevant": [
{ "code": 249, "label": "ù" },
{ "code": 363, "label": "ū" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".it" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,72 +1,99 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "nb",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
"ø": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,78 +1,109 @@
{
"a": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": " },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
],
"c": [
{ "code": 231, "label": "ç" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
],
"i": [
{ "code": 236, "label": "ì" }
],
"o": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
],
"y": [
{ "code": 7923, "label": "" }
],
"æ": [
{ "code": 228, "label": "ä" }
],
"ø": [
{ "code": 246, "label": "ö" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "nn",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 229, "label": "å" },
{ "code": 225, "label": "á" },
{ "code": 226, "label": "â" },
{ "code": 227, "label": "ã" },
{ "code": 257, "label": "ā" },
{ "code": 230, "label": "æ" },
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 275, "label": "ē" },
{ "code": 281, "label": "ę" },
{ "code": 279, "label": "ė" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "code": 236, "label": "ì" }
]
},
"o": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 333, "label": "ō" },
{ "code": 339, "label": "œ" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 246, "label": "ö" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 251, "label": "û" },
{ "code": 250, "label": "ú" }
]
},
"y": {
"relevant": [
{ "code": 7923, "label": "" }
]
},
"æ": {
"relevant": [
{ "code": 228, "label": "ä" }
]
},
": {
"relevant": [
{ "code": 246, "label": "ö" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,88 @@
{
"type": "characters/extended_popups",
"name": "pl",
"authors": [ "Mikołaj Biel" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 261, "label": "ą" },
{ "code": 224, "label": "à" }
]
},
"c": {
"relevant": [
{ "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "code": 281, "label": "ę" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 243, "label": "ó" }
]
},
"s": {
"relevant": [
{ "code": 347, "label": "ś" }
]
},
"x": {
"relevant": [
{ "code": 378, "label": "ź" }
]
},
"z": {
"relevant": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".pl" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,81 +1,109 @@
{
"a": [
{ "code": 225, "label": "á" },
{ "code": 228, "label": " },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 170, "label": "ª" },
{ "code": 227, "label": "ã" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" },
{ "code": 269, "label": "č" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 235, "label": "ë" },
{ "code": 279, "label": "ė" },
{ "code": 275, "label": "ē" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" },
{ "code": 281, "label": "ę" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 238, "label": "î" }
],
"n": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
],
"o": [
{ "code": 243, "label": "ó" },
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 245, "label": "õ" }
],
"u": [
{ "code": 250, "label": "ú" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "pt",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 170, "label": "ª" },
{ "code": 225, "label": "á" },
{ "code": 227, "label": "ã" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 263, "label": "ć" },
{ "code": 269, "label": "č" }
]
},
"e": {
"relevant": [
{ "code": 235, "label": "ë" },
{ "code": 279, "label": "ė" },
{ "code": 275, "label": "ē" },
{ "code": 233, "label": "é" },
{ "code": 232, "label": "è" },
{ "code": 234, "label": "ê" },
{ "code": 281, "label": "ę" }
]
},
"i": {
"relevant": [
{ "code": 299, "label": "ī" },
{ "code": 239, "label": "ï" },
{ "code": 303, "label": "į" },
{ "code": 236, "label": "ì" },
{ "code": 237, "label": "í" },
{ "code": 238, "label": "î" }
]
},
"n": {
"relevant": [
{ "code": 241, "label": "ñ" },
{ "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "code": 186, "label": "º" },
{ "code": 333, "label": "ō" },
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" },
{ "code": 246, "label": "ö" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 245, "label": "õ" }
]
},
"u": {
"relevant": [
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 252, "label": "ü" },
{ "code": 251, "label": "û" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".pt" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,55 @@
{
"type": "characters/extended_popups",
"name": "ro",
"authors": [ "bertin0" ],
"mapping": {
"all": {
"a": {
"main": { "code": 259, "label": "ă" },
"relevant": [
{ "code": 226, "label": "â" }
]
},
"i": {
"main": { "code": 238, "label": "î" }
},
"s": {
"main": {"code": 537, "label": "ș"}
},
"t": {
"main": {"code": 539, "label": "ț"}
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ro" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" },
{ "code": -255, "label": ".edu" }
]
}
}
}
}

View File

@@ -0,0 +1,45 @@
{
"type": "characters/extended_popups",
"name": "ru",
"authors": [ "williamtheaker" ],
"mapping": {
"all": {
"е": {
"relevant": [
{ "code": 1105, "label": "ё" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ru" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,115 +1,160 @@
{
"a": [
{ "code": 228, "label": "ä" },
{ "code": 224, "label": " },
{ "code": 226, "label": "â" },
{ "code": 261, "label": "ą" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" }
],
"c": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
],
"d": [
{ "code": 240, "label": "ð" },
{ "code": 271, "label": "ď" }
],
"e": [
{ "code": 233, "label": "é" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 281, "label": "ę" }
],
"i": [
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" },
{ "code": 299, "label": "ī" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 236, "label": "ì" }
],
"l": [
{ "code": 322, "label": "ł" }
],
"n": [
{ "code": 324, "label": "ń" },
{ "code": 328, "label": "ň" },
{ "code": 241, "label": "ñ" }
],
"o": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" }
],
"r": [
{ "code": 345, "label": "ř" }
],
"s": [
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" },
{ "code": 351, "label": "ş" },
{ "code": 223, "label": "ß" }
],
"t": [
{ "code": 357, "label": "ť" },
{ "code": 254, "label": "þ" }
],
"u": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 251, "label": "û" }
],
"y": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
],
"z": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" },
{ "code": 382, "label": "ž" }
],
"ä": [
{ "code": 230, "label": "æ" }
],
"ö": [
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" }
],
".~normal": [
{ "code": 44, "label": "," },
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
],
".~uri": [
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
"type": "characters/extended_popups",
"name": "sv",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "code": 228, "label": "ä" },
{ "code": 224, "label": "à" },
{ "code": 226, "label": "â" },
{ "code": 261, "label": "ą" },
{ "code": 227, "label": "ã" },
{ "code": 229, "label": "å" },
{ "code": 230, "label": "æ" },
{ "code": 225, "label": "á" }
]
},
"c": {
"relevant": [
{ "code": 231, "label": "ç" },
{ "code": 269, "label": "č" },
{ "code": 263, "label": "ć" }
]
},
"d": {
"relevant": [
{ "code": 240, "label": "ð" },
{ "code": 271, "label": "ď" }
]
},
"e": {
"relevant": [
{ "code": 233, "label": "é" },
{ "code": 234, "label": "ê" },
{ "code": 232, "label": "è" },
{ "code": 235, "label": "ë" },
{ "code": 281, "label": "ę" }
]
},
"i": {
"relevant": [
{ "code": 237, "label": "í" },
{ "code": 239, "label": "ï" },
{ "code": 299, "label": "ī" },
{ "code": 303, "label": "į" },
{ "code": 238, "label": "î" },
{ "code": 236, "label": "ì" }
]
},
"l": {
"relevant": [
{ "code": 322, "label": "ł" }
]
},
"n": {
"relevant": [
{ "code": 324, "label": "ń" },
{ "code": 328, "label": "ň" },
{ "code": 241, "label": "ñ" }
]
},
"o": {
"relevant": [
{ "code": 246, "label": "ö" },
{ "code": 333, "label": "ō" },
{ "code": 245, "label": "õ" },
{ "code": 242, "label": "ò" },
{ "code": 244, "label": "ô" },
{ "code": 243, "label": "ó" },
{ "code": 339, "label": "œ" },
{ "code": 248, "label": "ø" }
]
},
"r": {
"relevant": [
{ "code": 345, "label": "ř" }
]
},
"s": {
"relevant": [
{ "code": 347, "label": "ś" },
{ "code": 353, "label": "š" },
{ "code": 351, "label": "ş" },
{ "code": 223, "label": "ß" }
]
},
"t": {
"relevant": [
{ "code": 357, "label": "ť" },
{ "code": 254, "label": "þ" }
]
},
"u": {
"relevant": [
{ "code": 252, "label": "ü" },
{ "code": 363, "label": "ū" },
{ "code": 249, "label": "ù" },
{ "code": 250, "label": "ú" },
{ "code": 251, "label": "û" }
]
},
"y": {
"relevant": [
{ "code": 253, "label": "ý" },
{ "code": 255, "label": "ÿ" }
]
},
"z": {
"relevant": [
{ "code": 378, "label": "ź" },
{ "code": 380, "label": "ż" },
{ "code": 382, "label": "ž" }
]
},
"ä": {
"relevant": [
{ "code": 230, "label": "æ" }
]
},
"ö": {
"relevant": [
{ "code": 248, "label": "ø" },
{ "code": 339, "label": "œ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -0,0 +1,40 @@
{
"type": "characters",
"name": "greek",
"authors": [ "tsiflimagas" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 894, "label": ";" },
{ "code": 962, "label": "ς" },
{ "code": 949, "label": "ε" },
{ "code": 961, "label": "ρ" },
{ "code": 964, "label": "τ" },
{ "code": 965, "label": "υ" },
{ "code": 952, "label": "θ" },
{ "code": 953, "label": "ι" },
{ "code": 959, "label": "ο" },
{ "code": 960, "label": "π" }
],
[
{ "code": 945, "label": "α" },
{ "code": 963, "label": "σ" },
{ "code": 948, "label": "δ" },
{ "code": 966, "label": "φ" },
{ "code": 947, "label": "γ" },
{ "code": 951, "label": "η" },
{ "code": 958, "label": "ξ" },
{ "code": 954, "label": "κ" },
{ "code": 955, "label": "λ" }
],
[
{ "code": 950, "label": "ζ" },
{ "code": 967, "label": "χ" },
{ "code": 968, "label": "ψ" },
{ "code": 969, "label": "ω" },
{ "code": 946, "label": "β" },
{ "code": 957, "label": "ν" },
{ "code": 956, "label": "μ" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "hungarian",
"authors": [ "zoli111" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 246, "label": "ö" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é" },
{ "code": 225, "label": "á" }
], [
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "icelandic",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 240, "label": "ð" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 230, "label": "æ" },
{ "code": 246, "label": "ö" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -0,0 +1,46 @@
{
"type": "characters",
"name": "jcuken_russian",
"authors": [ "williamtheaker" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 1081, "label": "й" },
{ "code": 1094, "label": "ц" },
{ "code": 1091, "label": "у" },
{ "code": 1082, "label": "к" },
{ "code": 1077, "label": "е" },
{ "code": 1085, "label": "н" },
{ "code": 1075, "label": "г" },
{ "code": 1096, "label": "ш" },
{ "code": 1097, "label": "щ" },
{ "code": 1079, "label": "з" },
{ "code": 1093, "label": "х" },
{ "code": 1098, "label": "ъ" }
],
[
{ "code": 1092 , "label": "ф" },
{ "code": 1099 , "label": "ы" },
{ "code": 1074 , "label": "в" },
{ "code": 1072 , "label": "а" },
{ "code": 1087 , "label": "п" },
{ "code": 1088 , "label": "р" },
{ "code": 1086 , "label": "о" },
{ "code": 1083 , "label": "л" },
{ "code": 1076 , "label": "д" },
{ "code": 1078 , "label": "ж" },
{ "code": 1101 , "label": "э" }
],
[
{ "code": 1103 , "label": "я" },
{ "code": 1095 , "label": "ч" },
{ "code": 1089 , "label": "с" },
{ "code": 1084 , "label": "м" },
{ "code": 1080 , "label": "и" },
{ "code": 1090 , "label": "т" },
{ "code": 1100 , "label": "ь" },
{ "code": 1073 , "label": "б" },
{ "code": 1102 , "label": "ю" }
]
]
}

View File

@@ -1,32 +1,27 @@
{
"type": "characters/mod",
"name": "arabic",
"authors": [ "HeiWiper" ],
"direction": "rtl",
"arrangement": [
[
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 1548, "label": "،", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,32 +1,28 @@
{
"type": "characters/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -1, "label": "shift", "type": "modifier" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 44, "label": ",", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 44, "label": ",", "groupId": 1, "variation": "normal" },
{ "code": 44, "label": ",", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,30 +1,25 @@
{
"type": "characters/mod",
"name": "dvorak",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -1, "label": "shift", "type": "modifier" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 113, "label": "q" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 122, "label": "z", "variation": "email_address" },
{ "code": 122, "label": "z", "variation": "normal" },
{ "code": 122, "label": "z", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 113, "label": "q", "groupId": 1 },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "email_address" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "normal" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "password" },
{ "code": 122, "label": "z", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,34 +1,31 @@
{
"type": "characters/mod",
"name": "persian",
"authors": [ "PHELAT" ],
"direction": "rtl",
"arrangement": [
[
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 1548, "label": "،", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 8204, "label": "half_space", "variation": "normal" },
{ "code": 8204, "label": "half_space", "variation": "password" },
{ "code": 1600, "label": "kashida", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
{ "code": 1600, "label": "kashida", "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "norwegian",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 248, "label": "ø" },
{ "code": 230, "label": "æ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "persian",
"authors": [ "PHELAT" ],
"direction": "rtl",
"modifier": "persian",
"arrangement": [

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "qwerty",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -24,7 +26,8 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "qwertz",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -24,7 +26,8 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" }
], [
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "spanish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,7 +15,8 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -25,7 +27,8 @@
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 241, "label": "ñ" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swedish_finnish",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +16,8 @@
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 229, "label": "å" }
], [
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,7 +29,8 @@
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö" },
{ "code": 228, "label": "ä" }
], [
],
[
{ "code": 122, "label": "z" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_french",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 232, "label": "è", "popup": [
{ "code": 252, "label": "ü" }
] }
], [
{ "code": 232, "label": "è", "popup": {
"relevant": [
{ "code": 252, "label": "ü" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 233, "label": "é", "popup": [
{ "code": 246, "label": "ö" }
] },
{ "code": 224, "label": "à", "popup": [
{ "code": 228, "label": "ä" }
] }
], [
{ "code": 233, "label": "é", "popup": {
"relevant": [
{ "code": 246, "label": "ö" }
]
} },
{ "code": 224, "label": "à", "popup": {
"relevant": [
{ "code": 228, "label": "ä" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_german",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 252, "label": "ü", "popup": [
{ "code": 232, "label": "è" }
] }
], [
{ "code": 252, "label": "ü", "popup": {
"relevant": [
{ "code": 232, "label": "è" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö", "popup": [
{ "code": 233, "label": "é" }
] },
{ "code": 228, "label": "ä", "popup": [
{ "code": 224, "label": "à" }
] }
], [
{ "code": 246, "label": "ö", "popup": {
"relevant": [
{ "code": 233, "label": "é" }
]
} },
{ "code": 228, "label": "ä", "popup": {
"relevant": [
{ "code": 224, "label": "à" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "characters",
"name": "swiss_italian",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -14,10 +15,13 @@
{ "code": 105, "label": "i" },
{ "code": 111, "label": "o" },
{ "code": 112, "label": "p" },
{ "code": 252, "label": "ü", "popup": [
{ "code": 232, "label": "è" }
] }
], [
{ "code": 252, "label": "ü", "popup": {
"relevant": [
{ "code": 232, "label": "è" }
]
} }
],
[
{ "code": 97, "label": "a" },
{ "code": 115, "label": "s" },
{ "code": 100, "label": "d" },
@@ -27,13 +31,18 @@
{ "code": 106, "label": "j" },
{ "code": 107, "label": "k" },
{ "code": 108, "label": "l" },
{ "code": 246, "label": "ö", "popup": [
{ "code": 233, "label": "é" }
] },
{ "code": 228, "label": "ä", "popup": [
{ "code": 224, "label": "à" }
] }
], [
{ "code": 246, "label": "ö", "popup": {
"relevant": [
{ "code": 233, "label": "é" }
]
} },
{ "code": 228, "label": "ä", "popup": {
"relevant": [
{ "code": 224, "label": "à" }
]
} }
],
[
{ "code": 121, "label": "y" },
{ "code": 120, "label": "x" },
{ "code": 99, "label": "c" },

View File

@@ -1,6 +1,7 @@
{
"type": "extension",
"name": "clipboard_cursor_row",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[

View File

@@ -1,41 +1,74 @@
{
"type": "extension",
"name": "number_row",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "popup": [
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "" },
{ "code": 188, "label": "¼" },
{ "code": 8539, "label": "⅛" }
] },
{ "code": 50, "label": "2", "popup": [
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "" }
] },
{ "code": 51, "label": "3", "popup": [
{ "code": 8540, "label": "⅜" },
{ "code": 179, "label": "³" },
{ "code": 190, "label": "¾" }
] },
{ "code": 52, "label": "4", "popup": [
{ "code": 8308, "label": "" }
] },
{ "code": 53, "label": "5", "popup": [
{ "code": 8541, "label": "⅝" }
] },
{ "code": 54, "label": "6", "popup": [] },
{ "code": 55, "label": "7", "popup": [
{ "code": 8542, "label": "" }
] },
{ "code": 56, "label": "8", "popup": [] },
{ "code": 57, "label": "9", "popup": [] },
{ "code": 48, "label": "0", "popup": [
{ "code": 8709, "label": "" },
{ "code": 8319, "label": "" }
] }
{ "code": 49, "label": "1", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 50, "label": "2", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 51, "label": "3", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8535, "label": "" },
{ "code": 190, "label": "¾" },
{ "code": 8540, "label": "⅜" }
]
} },
{ "code": 52, "label": "4", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "" },
"relevant": [
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 53, "label": "5", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 54, "label": "6", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" }
} },
{ "code": 55, "label": "7", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8542, "label": "⅞" }
]
} },
{ "code": 56, "label": "8", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" }
} },
{ "code": 57, "label": "9", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" }
} },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" }
]
} }
]
]
}

View File

@@ -1,43 +1,53 @@
{
"type": "numeric_advanced",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 43, "label": "+", "popup": [
{ "code": 45, "label": "-" },
{ "code": 42, "label": "*" },
{ "code": 47, "label": "/" }
] },
{ "code": 43, "label": "+", "popup": {
"relevant": [
{ "code": 45, "label": "-" },
{ "code": 42, "label": "*" },
{ "code": 47, "label": "/" }
]
} },
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 37, "label": "%", "popup": [] }
], [
{ "code": 40, "label": "(", "popup": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
] },
{ "code": 37, "label": "%" }
],
[
{ "code": 40, "label": "(", "popup": {
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 41, "label": ")", "popup": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
] },
{ "code": 32, "label": "space" }
],
[
{ "code": 41, "label": ")", "popup": {
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 44, "label": "," },
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 48, "label": "0", "type": "numeric" },
{ "code": 61, "label": "=", "popup": [] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 61, "label": "=" },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,29 +1,33 @@
{
"type": "numeric",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 45, "label": "-" }
],
[
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 32, "label": "space" }
],
[
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
{ "code": 44, "label": ",", "popup": [] },
{ "code": 48, "label": "0", "type": "numeric", "popup": [
{ "code": 43, "label": "+" }
] },
{ "code": 46, "label": ".", "popup": [] },
],
[
{ "code": 44, "label": "," },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 43, "label": "+" }
} },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,29 +1,33 @@
{
"type": "phone",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 45, "label": "-" }
],
[
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 32, "label": "space" }
],
[
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -207, "label": "view_phone2", "type": "system_gui" },
{ "code": 48, "label": "0", "type": "numeric", "popup": [
{ "code": 43, "label": "+" }
] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 43, "label": "+" }
} },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,27 +1,31 @@
{
"type": "phone2",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 40, "label": "(", "popup": [] },
{ "code": 47, "label": "/", "popup": [] },
{ "code": 41, "label": ")", "popup": [] },
{ "code": 45, "label": "-", "popup": [] }
], [
{ "code": 78, "label": "N", "popup": [] },
{ "code": 40, "label": "(" },
{ "code": 47, "label": "/" },
{ "code": 41, "label": ")" },
{ "code": 45, "label": "-" }
],
[
{ "code": 78, "label": "N" },
{ "code": 44, "label": "pause", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 32, "label": " ", "popup": [] }
], [
{ "code": 42, "label": "*", "popup": [] },
{ "code": 44, "label": "," },
{ "code": 32, "label": "space" }
],
[
{ "code": 42, "label": "*" },
{ "code": 59, "label": "wait", "type": "system_gui" },
{ "code": 35, "label": "#", "popup": [] },
{ "code": 35, "label": "#" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -206, "label": "view_phone", "type": "system_gui" },
{ "code": 43, "label": "+", "popup": [] },
{ "code": 46, "label": ".", "popup": [] },
{ "code": 43, "label": "+" },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]

View File

@@ -1,21 +1,23 @@
{
"type": "symbols/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": ",", "popup": [] },
{ "code": 44, "label": "," },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": " ", "popup": [] },
{ "code": 46, "label": ".", "popup": [
{ "code": 8230, "label": "…" }
] },
{ "code": 10, "label": "enter", "type": "enter_editing" }
{ "code": 32, "label": "space" },
{ "code": 46, "label": ".", "popup": {
"main": { "code": 8230, "label": "…" }
} },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,74 +1,96 @@
{
"type": "symbols",
"name": "western_default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 64, "label": "@", "popup": [] },
{ "code": 35, "label": "#", "popup": [
{ "code": 8470, "label": "№" }
] },
{ "code": 36, "label": "$", "popup": [
{ "code": 8369, "label": "" },
{ "code": 8364, "label": "€" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
] },
{ "code": 37, "label": "%", "popup": [
{ "code": 8240, "label": "‰" },
{ "code": 8453, "label": "" }
] },
{ "code": 38, "label": "&", "popup": [] },
{ "code": 45, "label": "-", "popup": [
{ "code": 8212, "label": "—" },
{ "code": 95, "label": "_" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
] },
{ "code": 43, "label": "+", "popup": [
{ "code": 177, "label": "±" }
] },
{ "code": 40, "label": "(", "popup": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
] },
{ "code": 41, "label": ")", "popup": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
] },
{ "code": 47, "label": "/", "popup": [] }
], [
{ "code": 42, "label": "*", "popup": [
{ "code": 9733, "label": "★" },
{ "code": 8224, "label": "" },
{ "code": 8225, "label": "‡" }
] },
{ "code": 34, "label": "\"", "popup": [
{ "code": 8222, "label": "„" },
{ "code": 8220, "label": "“" },
{ "code": 8221, "label": "" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
] },
{ "code": 39, "label": "'", "popup": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8217, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
] },
{ "code": 58, "label": ":", "popup": [] },
{ "code": 59, "label": ";", "popup": [] },
{ "code": 33, "label": "!", "popup": [
{ "code": 161, "label": "¡" }
] },
{ "code": 63, "label": "?", "popup": [
{ "code": 191, "label": "¿" },
{ "code": 8253, "label": "‽" }
] }
{ "code": 64, "label": "@" },
{ "code": 35, "label": "#", "popup": {
"main": { "code": 8470, "label": "№" }
} },
{ "code": 36, "label": "$", "popup": {
"main": { "code": 8364, "label": "" },
"relevant": [
{ "code": 8369, "label": "" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
]
} },
{ "code": 37, "label": "%", "popup": {
"main": { "code": 8240, "label": "‰" },
"relevant": [
{ "code": 8453, "label": "℅" }
]
} },
{ "code": 38, "label": "&" },
{ "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 8212, "label": "" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
} },
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "code": 47, "label": "/" }
],
[
{ "code": 42, "label": "*", "popup": {
"main": { "code": 8224, "label": "†" },
"relevant": [
{ "code": 9733, "label": "" },
{ "code": 8225, "label": "" }
]
} },
{ "code": 34, "label": "\"", "popup": {
"main": { "code": 8221, "label": "”" },
"relevant": [
{ "code": 8222, "label": "" },
{ "code": 8220, "label": "" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
]
} },
{ "code": 39, "label": "'", "popup": {
"main": { "code": 8217, "label": "" },
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} },
{ "code": 58, "label": ":", "popup": {
"main": { "code": 8942, "label": "⋮" }
} },
{ "code": 59, "label": ";" },
{ "code": 33, "label": "!", "popup": {
"main": { "code": 161, "label": "¡" }
} },
{ "code": 63, "label": "?", "popup": {
"main": { "code": 191, "label": "¿" },
"relevant": [
{ "code": 8253, "label": "‽" }
]
} }
]
]
}

View File

@@ -1,19 +1,21 @@
{
"type": "symbols2/mod",
"name": "default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 0 },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
], [
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 60, "label": "<", "popup": [] },
{ "code": 60, "label": "<" },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": " ", "popup": [] },
{ "code": 62, "label": ">", "popup": [] },
{ "code": 10, "label": "enter", "type": "enter_editing" }
{ "code": 32, "label": "space" },
{ "code": 62, "label": ">" },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -1,66 +1,79 @@
{
"type": "symbols2",
"name": "western_default",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 126, "label": "~", "popup": [] },
{ "code": 96, "label": "`", "popup": [] },
{ "code": 124, "label": "|", "popup": [] },
{ "code": 8226, "label": "•", "popup": [
{ "code": 9827, "label": "" },
{ "code": 9824, "label": "♠" },
{ "code": 9834, "label": "" },
{ "code": 9829, "label": "" },
{ "code": 9830, "label": "" }
] },
{ "code": 8730, "label": "√", "popup": [] },
{ "code": 960, "label": "π", "popup": [
{ "code": 937, "label": "Ω" },
{ "code": 928, "label": "Π" },
{ "code": 956, "label": "μ" }
] },
{ "code": 247, "label": "÷", "popup": [] },
{ "code": 215, "label": "×", "popup": [] },
{ "code": 182, "label": "¶", "popup": [
{ "code": 167, "label": "§" }
] },
{ "code": 8710, "label": "∆", "popup": [] }
], [
{ "code": 163, "label": "£", "popup": [] },
{ "code": 162, "label": "¢", "popup": [] },
{ "code": 8364, "label": "€", "popup": [] },
{ "code": 165, "label": "¥", "popup": [] },
{ "code": 94, "label": "^", "popup": [
{ "code": 8592, "label": "" },
{ "code": 8593, "label": "" },
{ "code": 8595, "label": "" },
{ "code": 8594, "label": "" }
] },
{ "code": 176, "label": "°", "popup": [
{ "code": 8242, "label": "" },
{ "code": 8243, "label": "" }
] },
{ "code": 61, "label": "=", "popup": [
{ "code": 8734, "label": "∞" },
{ "code": 8800, "label": "≠" },
{ "code": 8776, "label": "" }
] },
{ "code": 123, "label": "{", "popup": [
{ "code": 40, "label": "(" }
] },
{ "code": 125, "label": "}", "popup": [
{ "code": 41, "label": ")" }
] },
{ "code": 92, "label": "\\", "popup": [] }
], [
{ "code": 95, "label": "_", "popup": [] },
{ "code": 169, "label": "©", "popup": [] },
{ "code": 174, "label": "®", "popup": [] },
{ "code": 8482, "label": "", "popup": [] },
{ "code": 10003, "label": "", "popup": [] },
{ "code": 91, "label": "[", "popup": [] },
{ "code": 93, "label": "]", "popup": [] }
{ "code": 126, "label": "~" },
{ "code": 96, "label": "`" },
{ "code": 124, "label": "|" },
{ "code": 8226, "label": "•", "popup": {
"main": { "code": 9834, "label": "" },
"relevant": [
{ "code": 9827, "label": "" },
{ "code": 9824, "label": "" },
{ "code": 9829, "label": "" },
{ "code": 9830, "label": "♦" }
]
} },
{ "code": 8730, "label": "" },
{ "code": 960, "label": "π", "popup": {
"main": { "code": 928, "label": "Π" },
"relevant": [
{ "code": 937, "label": "Ω" },
{ "code": 956, "label": "μ" }
]
} },
{ "code": 247, "label": "÷" },
{ "code": 215, "label": "×" },
{ "code": 182, "label": "¶", "popup": {
"main": { "code": 167, "label": "§" }
} },
{ "code": 8710, "label": "∆" }
],
[
{ "code": 163, "label": "£" },
{ "code": 162, "label": "¢" },
{ "code": 8364, "label": "" },
{ "code": 165, "label": "¥" },
{ "code": 94, "label": "^", "popup": {
"main": { "code": 8593, "label": "↑" },
"relevant": [
{ "code": 8592, "label": "" },
{ "code": 8595, "label": "↓" },
{ "code": 8594, "label": "→" }
]
} },
{ "code": 176, "label": "°", "popup": {
"main": { "code": 8242, "label": "" },
"relevant": [
{ "code": 8243, "label": "" }
]
} },
{ "code": 61, "label": "=", "popup": {
"main": { "code": 8800, "label": "≠" },
"relevant": [
{ "code": 8734, "label": "∞" },
{ "code": 8776, "label": "≈" }
]
} },
{ "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
{ "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
{ "code": 92, "label": "\\" }
],
[
{ "code": 95, "label": "_" },
{ "code": 169, "label": "©" },
{ "code": 174, "label": "®" },
{ "code": 8482, "label": "™" },
{ "code": 10003, "label": "✓" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" }
]
]
}

View File

@@ -1,64 +1,64 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_day",
"displayName": "Floris Day",
"author": "patrickgold",
"label": "Floris Day",
"authors": [ "patrickgold" ],
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"bgColor": "#E0E0E0"
"background": "#E0E0E0"
},
"key": {
"bgColor": "#FFFFFF",
"bgColorPressed": "#F5F5F5",
"fgColor": "@window/textColor"
"background": "#FFFFFF",
"backgroundPressed": "#F5F5F5",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "true"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#EEEEEE",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#757575"
"foreground": "@window/textColor",
"foregroundAlt": "#757575"
},
"oneHanded": {
"bgColor": "#E8F5E9"
"background": "#E8F5E9",
"foreground": "#424242"
},
"oneHandedButton": {
"fgColor": "#424242"
"popup": {
"background": "#EEEEEE",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"bgColor": "#A000FF",
"fgColor": "#FFFFFF"
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#8A8A8A"
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#8A8A8A"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
"background": "@key/background",
"foreground": "@key/foreground"
}
}
}

View File

@@ -0,0 +1,68 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_day_borderless",
"label": "Floris Day Borderless",
"authors": [ "patrickgold" ],
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"background": "#E0E0E0"
},
"key": {
"background": "transparent",
"backgroundPressed": "#7FF5F5F5",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "false"
},
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"key:space": {
"background": "#7FF5F5F5",
"backgroundPressed": "#FFF5F5F5"
},
"media": {
"foreground": "@window/textColor",
"foregroundAlt": "#757575"
},
"oneHanded": {
"background": "#E8F5E9",
"foreground": "#424242"
},
"popup": {
"background": "#EEEEEE",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#8A8A8A"
},
"smartbarButton": {
"background": "#FFFFFF",
"foreground": "@window/textColor"
}
}
}

View File

@@ -1,64 +1,64 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_night",
"displayName": "Floris Night",
"author": "patrickgold",
"label": "Floris Night",
"authors": [ "patrickgold" ],
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"bgColor": "#212121"
"background": "#212121"
},
"key": {
"bgColor": "#424242",
"bgColorPressed": "#616161",
"fgColor": "@window/textColor"
"background": "#424242",
"backgroundPressed": "#616161",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "true"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#757575",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#BDBDBD"
"foreground": "@window/textColor",
"foregroundAlt": "#BDBDBD"
},
"oneHanded": {
"bgColor": "#1B5E20"
"background": "#1B5E20",
"foreground": "#EEEEEE"
},
"oneHandedButton": {
"fgColor": "#EEEEEE"
"popup": {
"background": "#757575",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"bgColor": "#A000FF",
"fgColor": "#FFFFFF"
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#73FFFFFF"
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#73FFFFFF"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
"background": "@key/background",
"foreground": "@key/foreground"
}
}
}

View File

@@ -0,0 +1,68 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "floris_night_borderless",
"label": "Floris Night Borderless",
"authors": [ "patrickgold" ],
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"background": "#212121"
},
"key": {
"background": "transparent",
"backgroundPressed": "#7F616161",
"foreground": "@window/textColor",
"foregroundPressed": "@window/textColor",
"showBorder": "false"
},
"key:enter": {
"background": "@window/colorPrimary",
"backgroundPressed": "@window/colorPrimaryDark",
"foreground": "#FFFFFF",
"foregroundPressed": "#FFFFFF"
},
"key:shift:capslock": {
"foreground": "@window/colorAccent",
"foregroundPressed": "@window/colorAccent"
},
"key:space": {
"background": "#2F616161",
"backgroundPressed": "#7F616161"
},
"media": {
"foreground": "@window/textColor",
"foregroundAlt": "#BDBDBD"
},
"oneHanded": {
"background": "#1B5E20",
"foreground": "#EEEEEE"
},
"popup": {
"background": "#757575",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "transparent",
"foreground": "@window/textColor",
"foregroundAlt": "#73FFFFFF"
},
"smartbarButton": {
"background": "#424242",
"foreground": "@window/textColor"
}
}
}

View File

@@ -61,6 +61,30 @@ limitations under the License.
<hr>
<h3>ExpandableFab</h3>
<span>Copyright (c) 2020 Kelvin Abumere and The Nambi Company</span>
<pre>
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.
</pre>
<hr>
<h3>FlexboxLayout</h3>
<span>Copyright 2018 Google LLC</span>
<pre>
@@ -479,6 +503,24 @@ limitations under the License.
<hr>
<h3>kotlin-result</h3>
<span>Copyright (c) 2017-2020 Michael Bull (https://www.michael-bull.com)</span>
<pre>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>
<hr>
<h3>Material Icons</h3>
<span>Copyright 2018 Google LLC</span>
<pre>
@@ -696,6 +738,24 @@ You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</pre>
<hr>
<h3>Timber</h3>
<span>Copyright 2013 Jake Wharton</span>
<pre>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@@ -19,13 +19,20 @@ package dev.patrickgold.florisboard.ime.core
import android.app.Application
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.crashutility.CrashUtility
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import timber.log.Timber
class FlorisApplication : Application() {
override fun onCreate() {
super.onCreate()
CrashUtility.install(this)
if (BuildConfig.DEBUG)
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
CrashUtility.install(this)
val prefHelper = PrefHelper.getDefaultInstance(this)
val assetManager = AssetManager.init(this)
ThemeManager.init(this, assetManager, prefHelper)
prefHelper.initDefaultPreferences()
}
}

View File

@@ -27,22 +27,24 @@ import android.inputmethodservice.InputMethodService
import android.media.AudioManager
import android.os.*
import android.provider.Settings
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.ImageButton
import android.view.inputmethod.InputMethodManager
import com.squareup.moshi.Json
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.media.MediaInputManager
import dev.patrickgold.florisboard.ime.popup.PopupLayerView
import dev.patrickgold.florisboard.ime.text.TextInputManager
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.settings.SettingsMainActivity
import dev.patrickgold.florisboard.util.*
import timber.log.Timber
@@ -58,7 +60,8 @@ private var florisboardInstance: FlorisBoard? = null
* Core class responsible to link together both the text and media input managers as well as
* managing the one-handed UI.
*/
class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedListener {
class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedListener,
ThemeManager.OnThemeUpdatedListener {
lateinit var prefs: PrefHelper
private set
@@ -66,11 +69,15 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
get() = inputWindowView?.context ?: this
var inputView: InputView? = null
private set
var popupLayerView: PopupLayerView? = null
private set
private var inputWindowView: InputWindowView? = null
private var eventListeners: MutableList<WeakReference<EventListener?>?> = mutableListOf()
private var audioManager: AudioManager? = null
private var imeManager:InputMethodManager? = null
var clipboardManager: ClipboardManager? = null
private val themeManager: ThemeManager = ThemeManager.default()
private var vibrator: Vibrator? = null
private val osHandler = Handler()
@@ -167,6 +174,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
}
Timber.i("onCreate()")
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager?.addPrimaryClipChangedListener(this)
@@ -177,11 +185,11 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
subtypeManager = SubtypeManager(this, prefs)
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
currentThemeIsNight = prefs.internal.themeCurrentIsNight
currentThemeIsNight = themeManager.activeTheme.isNightTheme
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
isNumberRowVisible = prefs.keyboard.numberRow
setTheme(currentThemeResId)
updateTheme()
themeManager.registerOnThemeUpdatedListener(this)
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
@@ -196,6 +204,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
baseContext.setTheme(currentThemeResId)
inputWindowView = layoutInflater.inflate(R.layout.florisboard, null) as InputWindowView
popupLayerView = inputWindowView?.findViewById(R.id.popup_layer)
eventListeners.toList().forEach { it?.get()?.onCreateInputView() }
@@ -207,9 +216,10 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
this.inputView = inputView
initializeOneHandedEnvironment()
updateTheme()
updateSoftInputWindowLayoutParameters()
updateOneHandedPanelVisibility()
themeManager.notifyCallbackReceivers()
setActiveInput(R.id.text_input)
eventListeners.toList().forEach { it?.get()?.onRegisterInputView(inputView) }
}
@@ -217,6 +227,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
override fun onDestroy() {
Timber.i("onDestroy()")
themeManager.unregisterOnThemeUpdatedListener(this)
clipboardManager?.removePrimaryClipChangedListener(this)
osHandler.removeCallbacksAndMessages(null)
florisboardInstance = null
@@ -239,6 +250,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
super.onStartInputView(info, restarting)
activeEditorInstance = EditorInstance.from(info, this)
themeManager.updateRemoteColorValues(activeEditorInstance.packageName)
eventListeners.toList().forEach {
it?.get()?.onStartInputView(activeEditorInstance, restarting)
}
@@ -271,7 +283,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
textInputManager.layoutManager.clearLayoutCache(KeyboardMode.CHARACTERS)
isNumberRowVisible = newIsNumberRowVisible
}
updateTheme()
themeManager.update()
updateOneHandedPanelVisibility()
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
@@ -316,15 +328,11 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
eventListeners.toList().forEach { it?.get()?.onUpdateSelection() }
}
/**
* Updates the theme of the IME Window, status and navigation bar, as well as the InputView and
* some of its components.
*/
private fun updateTheme() {
override fun onThemeUpdated(theme: Theme) {
// Rebuild the UI if the theme has changed from day to night or vice versa to prevent
// theme glitches with scrollbars and hints of buttons in the media UI. If the UI must be
// rebuild, quit this method, as it will be called again by the newly created UI.
val newThemeIsNightMode = prefs.internal.themeCurrentIsNight
val newThemeIsNightMode = theme.isNightTheme
if (currentThemeIsNight != newThemeIsNightMode) {
currentThemeResId = getDayNightBaseThemeId(newThemeIsNightMode)
currentThemeIsNight = newThemeIsNightMode
@@ -337,9 +345,9 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
var flags = w.decorView.systemUiVisibility
// Update navigation bar theme
w.navigationBarColor = prefs.theme.navBarColor
w.navigationBarColor = theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_COLOR).toSolidColor().color
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
flags = if (prefs.theme.navBarIsLight) {
flags = if (theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_LIGHT).toOnOff().state) {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
@@ -358,10 +366,10 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
w.decorView.systemUiVisibility = flags
// Update InputView theme
inputView?.setBackgroundColor(prefs.theme.keyboardBgColor)
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(prefs.theme.oneHandedBgColor)
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(prefs.theme.oneHandedBgColor)
ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor).also {
inputView?.setBackgroundColor(theme.getAttr(Theme.Attr.KEYBOARD_BACKGROUND).toSolidColor().color)
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
ColorStateList.valueOf(theme.getAttr(Theme.Attr.ONE_HANDED_FOREGROUND).toSolidColor().color).also {
inputView?.oneHandedCtrlMoveStart?.imageTintList = it
inputView?.oneHandedCtrlMoveEnd?.imageTintList = it
inputView?.oneHandedCtrlCloseStart?.imageTintList = it
@@ -463,6 +471,7 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
SwipeAction.HIDE_KEYBOARD -> requestHideSelf(0)
SwipeAction.SWITCH_TO_PREV_SUBTYPE -> switchToPrevSubtype()
SwipeAction.SWITCH_TO_NEXT_SUBTYPE -> switchToNextSubtype()
SwipeAction.SWITCH_TO_PREV_KEYBOARD -> switchToPrevKeyboard()
else -> textInputManager.executeSwipeAction(swipeAction)
}
}
@@ -486,6 +495,21 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
return subtypeManager.subtypes.size > 1
}
fun switchToPrevKeyboard(){
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switchToPreviousInputMethod()
} else {
window.window?.let { window ->
imeManager?.switchToLastInputMethod(window.attributes.token)
}
}
} catch (e: Exception) {
Timber.e(e,"Unable to switch to the previous IME")
imeManager?.showInputMethodPicker()
}
}
fun switchToPrevSubtype() {
activeSubtype = subtypeManager.switchToPrevSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
@@ -504,12 +528,10 @@ class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedL
fun setActiveInput(type: Int) {
when (type) {
R.id.text_input -> {
inputView?.mainViewFlipper?.displayedChild =
inputView?.mainViewFlipper?.indexOfChild(textInputManager.textViewGroup) ?: 0
inputView?.mainViewFlipper?.displayedChild = 0
}
R.id.media_input -> {
inputView?.mainViewFlipper?.displayedChild =
inputView?.mainViewFlipper?.indexOfChild(mediaInputManager.mediaViewGroup) ?: 0
inputView?.mainViewFlipper?.displayedChild = 1
}
}
}

View File

@@ -25,6 +25,8 @@ import dev.patrickgold.florisboard.ime.text.gestures.DistanceThreshold
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.util.TimeUtil
import dev.patrickgold.florisboard.util.VersionName
import kotlin.collections.HashMap
@@ -139,6 +141,8 @@ class PrefHelper(
PreferenceManager.setDefaultValues(context, R.xml.prefs_keyboard, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_theme, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_typing, true)
//theme.dayThemeRef = "assets:ime/theme/floris_day.json"
//theme.nightThemeRef = "assets:ime/theme/floris_night.json"
//setPref(Keyboard.SUBTYPES, "")
//setPref(Internal.IS_IME_SET_UP, false)
}
@@ -213,6 +217,7 @@ class PrefHelper(
const val SWIPE_RIGHT = "gestures__swipe_right"
const val SPACE_BAR_SWIPE_LEFT = "gestures__space_bar_swipe_left"
const val SPACE_BAR_SWIPE_RIGHT = "gestures__space_bar_swipe_right"
const val SPACE_BAR_SWIPE_UP = "gestures__space_bar_swipe_up"
const val DELETE_KEY_SWIPE_LEFT = "gestures__delete_key_swipe_left"
const val SWIPE_VELOCITY_THRESHOLD = "gestures__swipe_velocity_threshold"
const val SWIPE_DISTANCE_THRESHOLD = "gestures__swipe_distance_threshold"
@@ -230,6 +235,9 @@ class PrefHelper(
var swipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_RIGHT, v)
var spaceBarSwipeUp: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_UP, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_UP, v)
var spaceBarSwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_LEFT, v)
@@ -271,9 +279,6 @@ class PrefHelper(
class Internal(private val prefHelper: PrefHelper) {
companion object {
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val THEME_CURRENT_BASED_ON = "internal__theme_current_based_on"
const val THEME_CURRENT_IS_MODIFIED = "internal__theme_current_is_modified"
const val THEME_CURRENT_IS_NIGHT = "internal__theme_current_is_night"
const val VERSION_ON_INSTALL = "internal__version_on_install"
const val VERSION_LAST_USE = "internal__version_last_use"
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
@@ -282,15 +287,6 @@ class PrefHelper(
var isImeSetUp: Boolean
get() = prefHelper.getPref(IS_IME_SET_UP, false)
set(v) = prefHelper.setPref(IS_IME_SET_UP, v)
var themeCurrentBasedOn: String
get() = prefHelper.getPref(THEME_CURRENT_BASED_ON, "undefined")
set(v) = prefHelper.setPref(THEME_CURRENT_BASED_ON, v)
var themeCurrentIsModified: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_MODIFIED, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_MODIFIED, v)
var themeCurrentIsNight: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_NIGHT, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_NIGHT, v)
var versionOnInstall: String
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_ON_INSTALL, v)
@@ -429,127 +425,35 @@ class PrefHelper(
*/
class Theme(private val prefHelper: PrefHelper) {
companion object {
const val COLOR_PRIMARY = "theme__colorPrimary"
const val COLOR_PRIMARY_DARK = "theme__colorPrimaryDark"
const val COLOR_ACCENT = "theme__colorAccent"
const val NAV_BAR_COLOR = "theme__navBarColor"
const val NAV_BAR_IS_LIGHT = "theme__navBarIsLight"
const val KEYBOARD_BG_COLOR = "theme__keyboard_bgColor"
const val KEY_BG_COLOR = "theme__key_bgColor"
const val KEY_BG_COLOR_PRESSED = "theme__key_bgColorPressed"
const val KEY_FG_COLOR = "theme__key_fgColor"
const val KEY_ENTER_BG_COLOR = "theme__keyEnter_bgColor"
const val KEY_ENTER_BG_COLOR_PRESSED = "theme__keyEnter_bgColorPressed"
const val KEY_ENTER_FG_COLOR = "theme__keyEnter_fgColor"
const val KEY_SHIFT_BG_COLOR = "theme__keyShift_bgColor"
const val KEY_SHIFT_BG_COLOR_PRESSED = "theme__keyShift_bgColorPressed"
const val KEY_SHIFT_FG_COLOR = "theme__keyShift_fgColor"
const val KEY_SHIFT_FG_COLOR_CAPSLOCK = "theme__keyShift_fgColorCapsLock"
const val KEY_POPUP_BG_COLOR = "theme__keyPopup_bgColor"
const val KEY_POPUP_BG_COLOR_ACTIVE = "theme__keyPopup_bgColorActive"
const val KEY_POPUP_FG_COLOR = "theme__keyPopup_fgColor"
const val MEDIA_FG_COLOR = "theme__media_fgColor"
const val MEDIA_FG_COLOR_ALT = "theme__media_fgColorAlt"
const val ONE_HANDED_BG_COLOR = "theme__oneHanded_bgColor"
const val ONE_HANDED_BUTTON_FG_COLOR = "theme__oneHandedButton_fgColor"
const val PRIVATE_MODE_BG_COLOR = "theme__privateMode_bgColor"
const val PRIVATE_MODE_FG_COLOR = "theme__privateMode_fgColor"
const val SMARTBAR_BG_COLOR = "theme__smartbar_bgColor"
const val SMARTBAR_FG_COLOR = "theme__smartbar_fgColor"
const val SMARTBAR_FG_COLOR_ALT = "theme__smartbar_fgColorAlt"
const val SMARTBAR_BUTTON_BG_COLOR = "theme__smartbarButton_bgColor"
const val SMARTBAR_BUTTON_FG_COLOR = "theme__smartbarButton_fgColor"
const val MODE = "theme__mode"
const val DAY_THEME_REF = "theme__day_theme_ref"
const val DAY_THEME_ADAPT_TO_APP = "theme__day_theme_adapt_to_app"
const val NIGHT_THEME_REF = "theme__night_theme_ref"
const val NIGHT_THEME_ADAPT_TO_APP = "theme__night_theme_adapt_to_app"
const val SUNRISE_TIME = "theme__sunrise_time"
const val SUNSET_TIME = "theme__sunset_time"
}
var colorPrimary: Int
get() = prefHelper.getPref(COLOR_PRIMARY, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY, v)
var colorPrimaryDark: Int
get() = prefHelper.getPref(COLOR_PRIMARY_DARK, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY_DARK, v)
var colorAccent: Int
get() = prefHelper.getPref(COLOR_ACCENT, 0)
set(v) = prefHelper.setPref(COLOR_ACCENT, v)
var navBarColor: Int
get() = prefHelper.getPref(NAV_BAR_COLOR, 0)
set(v) = prefHelper.setPref(NAV_BAR_COLOR, v)
var navBarIsLight: Boolean
get() = prefHelper.getPref(NAV_BAR_IS_LIGHT, false)
set(v) = prefHelper.setPref(NAV_BAR_IS_LIGHT, v)
var keyboardBgColor: Int
get() = prefHelper.getPref(KEYBOARD_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEYBOARD_BG_COLOR, v)
var keyBgColor: Int
get() = prefHelper.getPref(KEY_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR, v)
var keyBgColorPressed: Int
get() = prefHelper.getPref(KEY_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR_PRESSED, v)
var keyFgColor: Int
get() = prefHelper.getPref(KEY_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_FG_COLOR, v)
var keyEnterBgColor: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR, v)
var keyEnterBgColorPressed: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR_PRESSED, v)
var keyEnterFgColor: Int
get() = prefHelper.getPref(KEY_ENTER_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_FG_COLOR, v)
var keyShiftBgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR, v)
var keyShiftBgColorPressed: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR_PRESSED, v)
var keyShiftFgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR, v)
var keyShiftFgColorCapsLock: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, v)
var keyPopupBgColor: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR, v)
var keyPopupBgColorActive: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR_ACTIVE, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR_ACTIVE, v)
var keyPopupFgColor: Int
get() = prefHelper.getPref(KEY_POPUP_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_FG_COLOR, v)
var mediaFgColor: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR, v)
var mediaFgColorAlt: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR_ALT, v)
var oneHandedBgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BG_COLOR, v)
var oneHandedButtonFgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BUTTON_FG_COLOR, v)
var privateModeBgColor: Int
get() = prefHelper.getPref(PRIVATE_MODE_BG_COLOR, 0)
set(v) = prefHelper.setPref(PRIVATE_MODE_BG_COLOR, v)
var privateModeFgColor: Int
get() = prefHelper.getPref(PRIVATE_MODE_FG_COLOR, 0)
set(v) = prefHelper.setPref(PRIVATE_MODE_FG_COLOR, v)
var smartbarBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BG_COLOR, v)
var smartbarFgColor: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR, v)
var smartbarFgColorAlt: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR_ALT, v)
var smartbarButtonBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_BG_COLOR, v)
var smartbarButtonFgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_FG_COLOR, v)
var mode: ThemeMode
get() = ThemeMode.fromString(prefHelper.getPref(MODE, ThemeMode.FOLLOW_SYSTEM.toString()))
set(v) = prefHelper.setPref(MODE, v)
var dayThemeRef: String
get() = prefHelper.getPref(DAY_THEME_REF, "assets:ime/theme/floris_day.json")
set(v) = prefHelper.setPref(DAY_THEME_REF, v)
var dayThemeAdaptToApp: Boolean
get() = prefHelper.getPref(DAY_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(DAY_THEME_ADAPT_TO_APP, v)
var nightThemeRef: String
get() = prefHelper.getPref(NIGHT_THEME_REF, "assets:ime/theme/floris_night.json")
set(v) = prefHelper.setPref(NIGHT_THEME_REF, v)
var nightThemeAdaptToApp: Boolean
get() = prefHelper.getPref(NIGHT_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(NIGHT_THEME_ADAPT_TO_APP, v)
var sunriseTime: Int
get() = prefHelper.getPref(SUNRISE_TIME, TimeUtil.encode(6, 0))
set(v) = prefHelper.setPref(SUNRISE_TIME, v)
var sunsetTime: Int
get() = prefHelper.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
set(v) = prefHelper.setPref(SUNSET_TIME, v)
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import android.content.Context
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Result
/**
* Interface for an Asset to use within FlorisBoard. An asset is everything from a dictionary to a
* keyboard layout to a extended popup mapping, etc. Assets are very important for the splitting
* FlorisBoard's resources into assets.
*
* NOTE: At the current state, this is only a simple implementation idea and only PopupMappingAsset
* partly uses it. This package and it's classes are expected to grow and gain more importance over
* time.
*/
interface Asset {
/**
* The name of the Asset, must be unique throughout all Assets. Is used to internally identify
* and sort the Asset. This name is non-translatable and thus is a static string.
*/
val name: String
/**
* The display name of the Asset. This is the label which will be shown to the user in the
* Settings UI. Currently also a static string.
* TODO: make this string localize-able
*/
val label: String
/**
* A list of authors who actively worked on the content of this Asset. Any content of string is
* valid, but the best practice is to use the GitHub username.
*/
val authors: List<String>
/**
* "Static" functions which every Asset should provide.
*/
interface Companion<T> {
/**
* Creates an empty Asset of type [T].
*/
fun empty(): T
/**
* Loads an Asset of type [T] from the specified path.
*/
fun fromFile(context: Context, path: String): Result<T, Throwable> = Err(NotImplementedError())
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import android.content.Context
import com.github.michaelbull.result.*
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dev.patrickgold.florisboard.ime.popup.PopupExtension
import dev.patrickgold.florisboard.ime.text.key.KeyTypeAdapter
import dev.patrickgold.florisboard.ime.text.key.KeyVariationAdapter
import dev.patrickgold.florisboard.ime.text.layout.LayoutTypeAdapter
import dev.patrickgold.florisboard.ime.theme.Theme
import timber.log.Timber
import java.io.File
class AssetManager private constructor(private val applicationContext: Context) {
private val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
/*.add(PolymorphicJsonAdapterFactory.of(Asset::class.java, "\$type")
.withSubtype(PopupExtension::class.java, PopupExtension::class.qualifiedName)
.withSubtype(Theme::class.java, Theme::class.qualifiedName)
)*/
.add(LayoutTypeAdapter())
.add(KeyTypeAdapter())
.add(KeyVariationAdapter())
.build()
companion object {
private var defaultInstance: AssetManager? = null
fun init(applicationContext: Context): AssetManager {
val instance = AssetManager(applicationContext)
defaultInstance = instance
return instance
}
fun default(): AssetManager {
val instance = defaultInstance
if (instance != null) {
return instance
} else {
throw UninitializedPropertyAccessException(
"${this::class.simpleName} has not been initialized previously. Make sure to call init(applicationContext) before using default()."
)
}
}
}
fun deleteAsset(ref: AssetRef): Result<Nothing?, Throwable> {
return when (ref.source) {
AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
if (file.isFile) {
val success = file.delete()
if (success) {
Ok(null)
} else {
Err(Exception("Could not delete file."))
}
} else {
Err(Exception("Provided reference is not a file."))
}
}
else -> Err(Exception("Can not delete an asset in source '${ref.source}'"))
}
}
fun hasAsset(ref: AssetRef): Boolean {
return when (ref.source) {
AssetSource.Assets -> {
try {
val file = File(ref.path)
val list = applicationContext.assets.list(file.parent?.toString() ?: "")
list?.contains(file.name) == true
} catch (e: Exception) {
false
}
}
AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
file.exists() && file.isFile
}
else -> false
}
}
fun <T: Asset> listAssets(ref: AssetRef, assetClass: Class<T>): Result<Map<AssetRef, T>, Throwable> {
val retMap = mutableMapOf<AssetRef, T>()
return when (ref.source) {
AssetSource.Assets -> {
try {
val list = applicationContext.assets.list(ref.path)
if (list != null) {
for (file in list) {
val fileRef = ref.copy(path = ref.path + "/" + file)
val assetResult = loadAsset(fileRef, assetClass)
assetResult.onSuccess { asset ->
retMap[fileRef.copy()] = asset
}.onFailure { error ->
Timber.e(error.toString())
}
}
}
Ok(retMap.toMap())
} catch (e: Exception) {
Err(e)
}
}
AssetSource.Internal -> {
val dir = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
if (dir.isDirectory) {
dir.listFiles()?.let {
it.forEach { file ->
if (file.isFile) {
val fileRef = ref.copy(path = ref.path + "/" + file.name)
val assetResult = loadAsset(fileRef, assetClass)
assetResult.onSuccess { asset ->
retMap[fileRef.copy()] = asset
}.onFailure { error ->
Timber.e(error.toString())
}
}
}
}
}
Ok(retMap.toMap())
}
else -> Ok(retMap.toMap())
}
}
fun <T: Asset> loadAsset(ref: AssetRef, assetClass: Class<T>): Result<T, Throwable> {
val rawJsonData = when (ref.source) {
is AssetSource.Assets -> {
try {
applicationContext.assets.open(ref.path).bufferedReader().use { it.readText() }
} catch (e: Exception) {
return Err(e)
}
}
is AssetSource.Internal -> {
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
val contents = readFile(file)
if (contents.isBlank()) {
"{}"
} else {
contents
}
}
else -> "{}"
}
return try {
val adapter = moshi.adapter(assetClass)
val asset = adapter.fromJson(rawJsonData)
if (asset != null) {
Ok(asset)
} else {
Err(NullPointerException("Asset failed to load!"))
}
} catch (e: Exception) {
Err(e)
}
}
fun <T: Asset> writeAsset(ref: AssetRef, assetClass: Class<T>, asset: T): Result<Boolean, Throwable> {
return when (ref.source) {
AssetSource.Internal -> {
val adapter = moshi.adapter(assetClass)
val rawJson = adapter.toJson(asset)
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
writeToFile(file, rawJson)
Ok(true)
}
else -> Err(Exception("Can not write an asset in source '${ref.source}'"))
}
}
/**
* Reads a given [file] and returns its content.
*
* @param file The file object.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun readFile(file: File): String {
val retText = StringBuilder()
if (file.exists()) {
val newLine = System.lineSeparator()
file.forEachLine {
retText.append(it)
retText.append(newLine)
}
}
return retText.toString()
}
/**
* Writes given [text] to given [file]. If the file already exists, its current content
* will be overwritten.
*
* @param file The file object.
* @param text The text to write to the file.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun writeToFile(file: File, text: String) {
try {
file.parent?.let {
val dir = File(it)
if (!dir.exists()) {
dir.mkdirs()
}
}
file.writeText(text)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getOrElse
/**
* Data class which is a reference to an asset file. It indicates in which storage medium the asset
* is as well as the relative path to it.
*
* @property source The source in which the asset is (APK assets, internal storage, external)
* @property path The relative path to the asset within [source]. Must not begin and end with a
* forward slash.
*/
data class AssetRef(
val source: AssetSource,
val path: String
) {
companion object {
private const val DELIMITER: String = ":"
fun fromString(str: String): Result<AssetRef, String> {
val items = str.split(DELIMITER)
if (items.size != 2) {
return Err("Unexpected length of given asset ref. Make sure that the asset ref string contains exactly 2 items separated by '$DELIMITER'!")
}
val retSource = AssetSource.fromString(items[0]).getOrElse {
return Err(it)
}
return Ok(AssetRef(retSource, items[1]))
}
}
override fun toString(): String {
val retString: StringBuilder = StringBuilder().apply {
append(source.toString())
append(DELIMITER)
append(path)
}
return retString.toString()
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.extension
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.util.*
/**
* Sealed class which specifies where an asset comes from. There are 3 different types, all of which
* require a different approach on how to access the actual asset.
*/
sealed class AssetSource {
/**
* The asset comes pre-built with the application, thus all paths must be relative to the asset
* directory of FlorisBoard.
*/
object Assets : AssetSource()
/**
* The asset is saved in the internal storage of FlorisBoard, all relative paths must therefore
* be treated as such.
*/
object Internal : AssetSource()
/**
* Asset source is an external extension, which requires the package name and possibly other
* data. Currently NYI.
* TODO: Implement external extensions
*/
data class External(val packageName: String) : AssetSource() {
override fun toString(): String {
return super.toString()
}
}
companion object {
private val externalRegex: Regex = """^external\\(([a-z]+\\.)*[a-z]+\\)\$""".toRegex()
fun fromString(str: String): Result<AssetSource, String> {
return when (val string = str.toLowerCase(Locale.ENGLISH)) {
"assets" -> Ok(Assets)
"internal" -> Ok(Internal)
else -> {
if (string.matches(externalRegex)) {
val packageName = string.substring(9, string.length - 1)
Ok(External(packageName))
} else {
Err("'$str' is not a valid AssetSource.")
}
}
}
}
}
override fun toString(): String {
return when (this) {
is Assets -> "assets"
is Internal -> "internal"
is External -> "external($packageName)"
}
}
}

View File

@@ -18,12 +18,10 @@ package dev.patrickgold.florisboard.ime.media
import android.annotation.SuppressLint
import android.os.Handler
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.*
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.EditorInstance
import dev.patrickgold.florisboard.ime.core.FlorisBoard
@@ -148,10 +146,10 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
event ?: return false
val data = when (view.id) {
R.id.media_input_switch_to_text_input_button -> {
KeyData(KeyCode.SWITCH_TO_TEXT_CONTEXT)
KeyData(code = KeyCode.SWITCH_TO_TEXT_CONTEXT)
}
R.id.media_input_backspace_button -> {
KeyData(KeyCode.DELETE, type = KeyType.ENTER_EDITING)
KeyData(code = KeyCode.DELETE, type = KeyType.ENTER_EDITING)
}
else -> null
}

View File

@@ -25,12 +25,14 @@ import android.widget.LinearLayout
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlin.math.roundToInt
class MediaInputView : LinearLayout, FlorisBoard.EventListener {
class MediaInputView : LinearLayout, FlorisBoard.EventListener,
ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
var tabLayout: TabLayout? = null
private set
@@ -47,18 +49,26 @@ class MediaInputView : LinearLayout, FlorisBoard.EventListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
tabLayout = findViewById(R.id.media_input_tabs)
switchToTextInputButton = findViewById(R.id.media_input_switch_to_text_input_button)
backspaceButton = findViewById(R.id.media_input_backspace_button)
onApplyThemeAttributes()
}
override fun onApplyThemeAttributes() {
tabLayout?.setTabTextColors(prefs.theme.mediaFgColor, prefs.theme.mediaFgColor)
tabLayout?.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout?.setSelectedTabIndicatorColor(prefs.theme.colorPrimary)
switchToTextInputButton?.setTextColor(prefs.theme.mediaFgColor)
backspaceButton?.imageTintList = ColorStateList.valueOf(prefs.theme.mediaFgColor)
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
val colorPrimary = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY).toSolidColor().color
tabLayout?.setTabTextColors(fgColor, fgColor)
tabLayout?.tabIconTint = ColorStateList.valueOf(fgColor)
tabLayout?.setSelectedTabIndicatorColor(colorPrimary)
switchToTextInputButton?.setTextColor(fgColor)
backspaceButton?.imageTintList = ColorStateList.valueOf(fgColor)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

View File

@@ -30,6 +30,9 @@ import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
/**
* View class for managing the rendering and the events of a single emoji keyboard key.
@@ -44,7 +47,7 @@ class EmojiKeyView(
private val emojiKeyboardView: EmojiKeyboardView,
val data: EmojiKeyData
) : androidx.appcompat.widget.AppCompatTextView(emojiKeyboardView.context),
FlorisBoard.EventListener {
FlorisBoard.EventListener, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
@@ -92,8 +95,8 @@ class EmojiKeyView(
(parent.parent as ScrollView)
.requestDisallowInterceptTouchEvent(true)
emojiKeyboardView.isScrollBlocked = true
emojiKeyboardView.popupManager.show(this)
emojiKeyboardView.popupManager.extend(this)
emojiKeyboardView.popupManager.show(this, KeyHintMode.DISABLED)
emojiKeyboardView.popupManager.extend(this, KeyHintMode.DISABLED)
florisboard?.keyPressVibrate()
florisboard?.keyPressSound()
}, delayMillis.toLong())
@@ -146,10 +149,10 @@ class EmojiKeyView(
)
}
override fun onApplyThemeAttributes() {
override fun onThemeUpdated(theme: Theme) {
triangleDrawable?.colorFilter =
BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
prefs.theme.mediaFgColorAlt, BlendModeCompat.SRC_ATOP
theme.getAttr(Theme.Attr.MEDIA_FOREGROUND_ALT).toSolidColor().color, BlendModeCompat.SRC_ATOP
)
}

View File

@@ -31,8 +31,9 @@ import com.google.android.flexbox.JustifyContent
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
import dev.patrickgold.florisboard.ime.popup.PopupManager
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlinx.coroutines.*
import java.util.*
@@ -43,9 +44,10 @@ import java.util.*
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private var activeCategory: EmojiCategory = EmojiCategory.SMILEYS_EMOTION
private var emojiViewFlipper: ViewFlipper
@@ -57,7 +59,7 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
private val uiLayouts = EnumMap<EmojiCategory, ScrollView>(EmojiCategory::class.java)
var isScrollBlocked: Boolean = false
var popupManager = KeyPopupManager<EmojiKeyboardView, EmojiKeyView>(this)
var popupManager = PopupManager<EmojiKeyboardView, EmojiKeyView>(this, florisboard?.popupLayerView)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@@ -104,12 +106,18 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
mainScope.launch {
layouts.await()
buildLayout()
setActiveCategory(EmojiCategory.SMILEYS_EMOTION)
themeManager.requestThemeUpdate(this@EmojiKeyboardView)
}
onApplyThemeAttributes()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
/**
@@ -216,8 +224,10 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
isScrollBlocked = true
}
override fun onApplyThemeAttributes() {
tabLayout.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout.setSelectedTabIndicatorColor(prefs.theme.colorAccent)
override fun onThemeUpdated(theme: Theme) {
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
val colorAccent = theme.getAttr(Theme.Attr.WINDOW_COLOR_ACCENT).toSolidColor().color
tabLayout.tabIconTint = ColorStateList.valueOf(fgColor)
tabLayout.setSelectedTabIndicatorColor(colorAccent)
}
}

View File

@@ -68,7 +68,7 @@ class EmoticonKeyView : androidx.appcompat.widget.AppCompatTextView {
MotionEvent.ACTION_DOWN -> {
setBackgroundColor(getColorFromAttr(context, R.attr.semiTransparentColor))
florisboard.keyPressVibrate()
florisboard.keyPressSound(KeyData(0))
florisboard.keyPressSound(KeyData())
}
MotionEvent.ACTION_UP -> {
setBackgroundColor(Color.TRANSPARENT)

View File

@@ -1,77 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
@SuppressLint("ViewConstructor")
class KeyPopupExtendedSingleView(
context: Context, val adjustedIndex: Int, var isActive: Boolean = false
) : androidx.appcompat.widget.AppCompatTextView(
context, null, 0
) {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var iconDrawable: Drawable? = null
init {
background = getDrawable(context, R.drawable.shape_rect_rounded)
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, when {
isActive -> prefs.theme.keyPopupBgColorActive
else -> Color.TRANSPARENT
})
setTextColor(prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
canvas ?: return
val drawable = iconDrawable
val drawablePadding = (0.2f * measuredHeight).toInt()
if (drawable != null) {
var marginV = 0
var marginH = 0
if (measuredWidth > measuredHeight) {
marginH = (measuredWidth - measuredHeight) / 2
} else {
marginV = (measuredHeight - measuredWidth) / 2
}
drawable.setBounds(
marginH + drawablePadding,
marginV + drawablePadding,
measuredWidth - marginH - drawablePadding,
measuredHeight - marginV - drawablePadding)
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
prefs.theme.keyPopupFgColor,
BlendModeCompat.SRC_ATOP
)
drawable.draw(canvas)
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.KeyPopupViewBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupView : LinearLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private lateinit var binding: KeyPopupViewBinding
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
binding = KeyPopupViewBinding.bind(this)
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
binding.symbol.setTextColor(prefs.theme.keyPopupFgColor)
setImageTintColor2(binding.threedots, prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
}
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.util.ViewLayoutUtils
import kotlin.math.min
class PopupExtendedView : View, ThemeManager.OnThemeUpdatedListener {
private val themeManager: ThemeManager = ThemeManager.default()
private val activeBackgroundDrawable: PaintDrawable = PaintDrawable()
private var backgroundDrawable: PaintDrawable = PaintDrawable()
private val labelPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
private val tldPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
val properties: Properties = Properties(
width = resources.getDimension(R.dimen.key_width).toInt(),
height = resources.getDimension(R.dimen.key_height).toInt(),
xOffset = 0,
yOffset = 0,
gravity = Gravity.START,
elements = mutableListOf(),
activeElementIndex = -1,
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
)
val isShowing: Boolean
get() = visibility == VISIBLE
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init {
visibility = GONE
background = backgroundDrawable
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
activeBackgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND_ACTIVE).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
backgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
tldPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
if (isShowing) {
invalidate()
}
}
private fun applyProperties(anchor: View) {
val anchorCoords = IntArray(2)
anchor.getLocationInWindow(anchorCoords)
val anchorX = anchorCoords[0]
val anchorY = anchorCoords[1] + anchor.measuredHeight
when (val lp = layoutParams) {
is FrameLayout.LayoutParams -> lp.apply {
width = properties.width
height = properties.height
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
else -> {
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
}
}
labelPaint.textSize = properties.labelTextSize
tldPaint.textSize = properties.labelTextSize * 0.6f
if (isShowing) {
requestLayout()
invalidate()
}
}
fun show(anchor: View) {
applyProperties(anchor)
visibility = VISIBLE
requestLayout()
invalidate()
}
fun hide() {
visibility = GONE
requestLayout()
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
if (properties.elements.isEmpty() || properties.elements.first().isEmpty()) {
return
}
val baseSize = properties.elements.first().size
val elementWidth = measuredWidth / baseSize
val elementHeight = measuredHeight / properties.elements.size
var currentElementIndex = 0
for ((r, row) in properties.elements.reversed().withIndex()) {
val leftOffset = when (properties.gravity) {
Gravity.END -> baseSize - row.size
else -> 0
}
for ((e, element) in row.withIndex()) {
val left = (e + leftOffset) * elementWidth
val top = r * elementHeight
if (properties.activeElementIndex == currentElementIndex) {
activeBackgroundDrawable.setBounds(
left, top, left + elementWidth, top + elementHeight
)
activeBackgroundDrawable.draw(canvas)
}
when (element) {
is Element.Label -> {
val label = element.label
if (label.isNotEmpty()) {
val centerX = left + elementWidth / 2.0f
val centerY = top + elementHeight / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
canvas.drawText(label, centerX, centerY, labelPaint)
}
}
is Element.Tld -> {
val tld = element.tld
if (tld.isNotEmpty()) {
val centerX = left + elementWidth / 2.0f
val centerY = top + elementHeight / 2.0f + (tldPaint.textSize - tldPaint.descent()) / 2
canvas.drawText(tld, centerX, centerY, tldPaint)
}
}
is Element.Icon -> {
val drawable = element.icon
drawable.setTint(labelPaint.color)
val drawableSize = (min(elementWidth, elementHeight) * 0.6f).toInt()
val drawablePaddingLeft = ((elementWidth - drawableSize) / 2.0f).toInt()
val drawablePaddingTop = ((elementHeight - drawableSize) / 2.0f).toInt()
drawable.setBounds(
left + drawablePaddingLeft,
top + drawablePaddingTop,
left + drawablePaddingLeft + drawableSize,
top + drawablePaddingTop + drawableSize
)
drawable.draw(canvas)
}
else -> {}
}
currentElementIndex++
}
}
}
data class Properties(
var width: Int,
var height: Int,
var xOffset: Int,
var yOffset: Int,
var gravity: Int,
var elements: MutableList<MutableList<Element>>,
var activeElementIndex: Int,
var labelTextSize: Float
) {
fun getElementOrNull(index: Int = activeElementIndex): Element? {
if (index < 0) {
return null
}
var cachedIndex = index
elements.reversed().forEach { row ->
if (cachedIndex >= row.size) {
cachedIndex -= row.size
} else {
return row[cachedIndex]
}
}
return null
}
}
sealed class Element(val adjustedIndex: Int) {
class Label(val label: String, adjustedIndex: Int) : Element(adjustedIndex)
class Tld(val tld: String, adjustedIndex: Int) : Element(adjustedIndex)
class Icon(val icon: Drawable, adjustedIndex: Int) : Element(adjustedIndex)
object Undefined : Element(-1)
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dev.patrickgold.florisboard.ime.extension.Asset
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyTypeAdapter
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
import dev.patrickgold.florisboard.ime.text.key.KeyVariationAdapter
/**
* An object which maps each base key to its extended popups. This can be done for each
* key variation. [KeyVariation.ALL] is always the fallback for each key.
*/
typealias PopupMapping = Map<KeyVariation, Map<String, PopupSet<KeyData>>>
/**
* Class which contains an extended popup mapping to use for adding popups subtype based on the
* keyboard layout.
*
* @property mapping The mapping of the base keys to their popups. See [PopupMapping] for more info.
*/
class PopupExtension(
override val name: String,
override val label: String = name,
override val authors: List<String>,
val mapping: PopupMapping
) : Asset {
companion object : Asset.Companion<PopupExtension> {
override fun empty() = PopupExtension("", "", listOf(), mapOf())
override fun fromFile(context: Context, path: String): Result<PopupExtension, Throwable> {
return try {
val raw = context.assets.open(path).bufferedReader().use { it.readText() }
val asset = fromJsonString(raw)
if (asset != null) {
Ok(asset)
} else {
Err(NullPointerException())
}
} catch (e: Exception) {
Err(e)
}
}
fun fromJsonString(json: String): PopupExtension? {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(KeyTypeAdapter())
.add(KeyVariationAdapter())
.build()
val layoutAdapter = moshi.adapter(PopupExtension::class.java)
return layoutAdapter.fromJson(json)
}
}
}

View File

@@ -16,22 +16,32 @@
package dev.patrickgold.florisboard.ime.popup
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import com.google.android.flexbox.FlexboxLayout
import android.view.MotionEvent
import android.widget.FrameLayout
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupExtendedView : FlexboxLayout {
class PopupLayerView : FrameLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
super.onDraw(canvas)
init {
background = null
isClickable = false
isFocusable = false
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return false
}
}

View File

@@ -17,24 +17,17 @@
package dev.patrickgold.florisboard.ime.popup
import android.content.res.Configuration
import android.util.TypedValue
import android.view.*
import android.widget.*
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.view.get
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.KeyPopupExtendedViewBinding
import dev.patrickgold.florisboard.databinding.KeyPopupViewBinding
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyView
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyboardView
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.util.ViewLayoutUtils
/**
* Manages the creation and dismissal of key popups as well as the checks if the pointer moved
@@ -42,11 +35,13 @@ import dev.patrickgold.florisboard.util.ViewLayoutUtils
*
* @property keyboardView Reference to the keyboard view to which this manager class belongs to.
*/
class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD) {
class PopupManager<T_KBD: View, T_KV: View>(
private val keyboardView: T_KBD,
private val popupLayerView: PopupLayerView?
) {
private var anchorLeft: Boolean = false
private var anchorRight: Boolean = false
private var anchorOffset: Int = 0
private var activeExtIndex: Int? = null
private val exceptionsForKeyCodes = listOf(
KeyCode.ENTER,
KeyCode.LANGUAGE_SWITCH,
@@ -57,107 +52,81 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
private var keyPopupHeight: Int
var keyPopupTextSize: Float = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
private var keyPopupDiffX: Int = 0
private val popupView: KeyPopupViewBinding
private val popupViewExt: KeyPopupExtendedViewBinding
private val popupView: PopupView
private val popupViewExt: PopupExtendedView
private var row0count: Int = 0
private var row1count: Int = 0
private var window: PopupWindow
private var windowExt: PopupWindow
/** Is true if the preview popup is visible to the user, else false */
val isShowingPopup: Boolean
get() = popupView.root.visibility == View.VISIBLE
get() = popupView.isShowing
/** Is true if the extended popup is visible to the user, else false */
val isShowingExtendedPopup: Boolean
get() = windowExt.isShowing
get() = popupViewExt.isShowing
init {
val inflater = LayoutInflater.from(keyboardView.context)
keyPopupWidth = keyboardView.resources.getDimension(R.dimen.key_width).toInt()
keyPopupHeight = keyboardView.resources.getDimension(R.dimen.key_height).toInt()
popupView = KeyPopupViewBinding.inflate(inflater, null, false)
popupView.root.visibility = View.INVISIBLE
popupViewExt = KeyPopupExtendedViewBinding.inflate(inflater, null, false)
window = createPopupWindow(popupView.root)
windowExt = createPopupWindow(popupViewExt.root)
popupView = PopupView(keyboardView.context)
popupViewExt = PopupExtendedView(keyboardView.context)
popupLayerView?.addView(popupView)
popupLayerView?.addView(popupViewExt)
}
/**
* Helper function to create a [KeyPopupExtendedSingleView] and preconfigure it.
* Helper function to create a element for the extended popup and preconfigure it.
*
* @param keyView Reference to the keyView currently controlling the popup.
* @param k The index of the key in the key data popup array.
* @param isInitActive If it should initially be marked as active.
* @param isWrapBefore If the [FlexboxLayout] should wrap before this view.
* @return A preconfigured [KeyPopupExtendedSingleView].
* @param adjustedIndex The index of the key in the key data popup array.
* @return A preconfigured extended popup element.
*/
private fun createTextView(
private fun createElement(
keyView: T_KV,
k: Int,
isInitActive: Boolean = false,
isWrapBefore: Boolean = false
): KeyPopupExtendedSingleView? {
val textView = KeyPopupExtendedSingleView(keyView.context, k, isInitActive)
val lp = FlexboxLayout.LayoutParams(keyPopupWidth, (keyPopupHeight * 0.4f).toInt())
lp.isWrapBefore = isWrapBefore
textView.layoutParams = lp
textView.gravity = Gravity.CENTER
val textSize = keyPopupTextSize
if (keyView is KeyView) {
when (keyView.dataPopupWithHint[k].code) {
KeyCode.SETTINGS -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_settings
)
}
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
textView.text = keyView.resources.getString(R.string.key__view_characters)
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_sentiment_satisfied
)
}
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
textView.iconDrawable = getDrawable(
keyView.context, R.drawable.ic_smartphone
)
}
else -> {
textView.setTextSize(
TypedValue.COMPLEX_UNIT_PX, when (keyView.dataPopupWithHint[k].code) {
KeyCode.URI_COMPONENT_TLD,
KeyCode.SWITCH_TO_TEXT_CONTEXT -> textSize * 0.6f
else -> textSize
}
)
textView.text = keyView.getComputedLetter(keyView.dataPopupWithHint[k])
adjustedIndex: Int
): PopupExtendedView.Element {
return when (keyView) {
is KeyView -> {
when (keyView.data.popup[adjustedIndex].code) {
KeyCode.SETTINGS -> {
getDrawable(keyView.context, R.drawable.ic_settings)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
PopupExtendedView.Element.Label(
keyView.resources.getString(R.string.key__view_characters), adjustedIndex
)
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
getDrawable(keyView.context, R.drawable.ic_sentiment_satisfied)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
KeyCode.URI_COMPONENT_TLD -> {
PopupExtendedView.Element.Tld(
keyView.data.popup[adjustedIndex].label, adjustedIndex
)
}
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
getDrawable(keyView.context, R.drawable.ic_smartphone)?.let {
PopupExtendedView.Element.Icon(it, adjustedIndex)
} ?: PopupExtendedView.Element.Undefined
}
else -> {
PopupExtendedView.Element.Label(
keyView.getComputedLetter(keyView.data.popup[adjustedIndex]), adjustedIndex
)
}
}
}
} else if (keyView is EmojiKeyView) {
textView.text = keyView.data.popup[k].getCodePointsAsString()
}
return textView
}
/**
* Helper function for a convenient way of creating a [PopupWindow].
*
* @param view The view to set as content view of the [PopupWindow].
* @return A new [PopupWindow] already preconfigured and ready-to-go.
*/
private fun createPopupWindow(view: View): PopupWindow {
return PopupWindow(view.context).apply {
animationStyle = 0
contentView = view
elevation = ViewLayoutUtils.convertDpToPixel(2.0f, view.context)
enterTransition = null
exitTransition = null
isAttachedInDecor = false
isClippingEnabled = false
isFocusable = false
isTouchable = false
setBackgroundDrawable(null)
is EmojiKeyView -> {
PopupExtendedView.Element.Label(
keyView.data.popup[adjustedIndex].getCodePointsAsString(), adjustedIndex
)
}
else -> {
PopupExtendedView.Element.Undefined
}
}
}
@@ -200,38 +169,32 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*
* @param keyView Reference to the keyView currently controlling the popup.
*/
fun show(keyView: T_KV) {
fun show(keyView: T_KV, keyHintMode: KeyHintMode) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE) {
return
}
calc(keyView)
val keyPopupX = keyPopupDiffX
val keyPopupY = -keyPopupHeight
if (window.isShowing) {
window.update(keyView, keyPopupX, keyPopupY, keyPopupWidth, keyPopupHeight)
} else {
window.width = keyPopupWidth
window.height = keyPopupHeight
window.showAsDropDown(keyView, keyPopupX, keyPopupY, Gravity.NO_GRAVITY)
}
if (keyView is KeyView) {
popupView.symbol.layoutParams.height = (keyPopupHeight * 0.4f).toInt()
popupView.symbol.setTextSize(TypedValue.COMPLEX_UNIT_PX, keyPopupTextSize)
popupView.symbol.text = keyView.getComputedLetter()
popupView.threedots.visibility = when {
keyView.dataPopupWithHint.isEmpty() -> View.INVISIBLE
else -> View.VISIBLE
popupView.properties.apply {
width = keyPopupWidth
height = keyPopupHeight
xOffset = keyPopupDiffX
yOffset = -keyPopupHeight
innerLabelFactor = 0.4f
label = when (keyView) {
is KeyView -> keyView.getComputedLetter()
is EmojiKeyView -> keyView.data.getCodePointsAsString()
else -> ""
}
} else if (keyView is EmojiKeyView) {
popupView.symbol.text = keyView.data.getCodePointsAsString()
popupView.threedots.visibility = when {
keyView.data.popup.isEmpty() -> View.INVISIBLE
else -> View.VISIBLE
labelTextSize = keyPopupTextSize
shouldIndicateExtendedPopups = when (keyView) {
is KeyView -> keyView.data.popup.size(keyHintMode) > 0
is EmojiKeyView -> keyView.data.popup.isNotEmpty()
else -> false
}
}
popupView.root.visibility = View.VISIBLE
popupView.show(keyView)
}
/**
@@ -255,7 +218,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*
* @param keyView Reference to the keyView currently controlling the popup.
*/
fun extend(keyView: T_KV) {
fun extend(keyView: T_KV, keyHintMode: KeyHintMode) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE
&& !exceptionsForKeyCodes.contains(keyView.data.code)) {
return
@@ -271,11 +234,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
// Determine key counts for each row
val n = when (keyView) {
is KeyView -> keyView.dataPopupWithHint.size
is KeyView -> keyView.data.popup.size(keyHintMode)
is EmojiKeyView -> keyView.data.popup.size
else -> 0
}
when {
n <= 0 -> return
n <= 5 -> {
row1count = 0
row0count = n
@@ -318,38 +282,76 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
}
// Build UI
popupViewExt.root.removeAllViews()
val indices = when (keyView) {
is KeyView -> keyView.dataPopupWithHint.indices
is EmojiKeyView -> keyView.data.popup.indices
else -> IntRange(0, 0)
popupViewExt.properties.elements.clear()
val initUiIndex = when {
anchorLeft -> anchorOffset + row1count
anchorRight -> row0count - 1 - anchorOffset + row1count
else -> 0
}
var hasShownFirst = false
for (k in indices) {
val isInitActive =
anchorLeft && (k - row1count == anchorOffset) ||
anchorRight && (k - row1count == row0count - 1 - anchorOffset)
val kk = when (keyView) {
is KeyView -> when {
isInitActive -> {
hasShownFirst = true
0
val popupIndices: IntArray
val uiIndices = IntRange(0, (n - 1).coerceAtLeast(0))
if (keyView is KeyView) {
popupIndices = IntArray(n) { 0 }
when (keyHintMode) {
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
keyView.data.popup.main != null -> {
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
if (keyView.data.popup.hint != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
}
}
keyView.data.popup.hint != null -> when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
else -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
}
hasShownFirst -> k
else -> k + 1
}
else -> k
KeyHintMode.ENABLED_HINT_PRIORITY -> when {
keyView.data.popup.hint != null -> {
popupIndices[initUiIndex] = PopupSet.HINT_INDEX
if (keyView.data.popup.main != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.MAIN_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.MAIN_INDEX
}
}
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
}
KeyHintMode.ENABLED_SMART_PRIORITY -> when {
keyView.data.popup.main != null -> {
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
if (keyView.data.popup.hint != null) when {
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
}
}
keyView.data.popup.hint != null -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
}
KeyHintMode.DISABLED -> when {
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
}
}
popupViewExt.root.addView(
createTextView(
keyView, kk, isInitActive, (row1count > 0) && (k - row1count == 0)
)
)
if (isInitActive) {
activeExtIndex = k
var offset = 0
for (uiIndex in uiIndices) {
if (popupIndices[uiIndex] < 0) {
offset++
} else {
popupIndices[uiIndex] = uiIndex - offset
}
}
} else {
popupIndices = IntArray(n) { it }
}
if (row1count > 0) {
popupViewExt.properties.elements.add(mutableListOf())
}
popupViewExt.properties.elements.add(mutableListOf())
for (uiIndex in uiIndices) {
val rowIndex = if (uiIndex < row1count && row1count > 0) { 1 } else { 0 }
popupViewExt.properties.elements[rowIndex].add(
createElement(keyView, popupIndices[uiIndex])
)
}
popupView.threedots.visibility = View.INVISIBLE
// Calculate layout params
val extWidth = row0count * keyPopupWidth
@@ -357,19 +359,6 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
row1count > 0 -> keyPopupHeight * 0.4f * 2.0f
else -> keyPopupHeight * 0.4f
}.toInt()
popupViewExt.root.justifyContent = if (anchorLeft) {
JustifyContent.FLEX_START
} else {
JustifyContent.FLEX_END
}
if (popupViewExt.root.layoutParams == null) {
popupViewExt.root.layoutParams = ViewGroup.LayoutParams(extWidth, extHeight)
} else {
popupViewExt.root.layoutParams.apply {
width = extWidth
height = extHeight
}
}
val x = ((keyView.measuredWidth - keyPopupWidth) / 2) + when {
anchorLeft -> -anchorOffset * keyPopupWidth
anchorRight -> -extWidth + keyPopupWidth + anchorOffset * keyPopupWidth
@@ -380,14 +369,19 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
else -> 0
}
// Position and show popup window
if (windowExt.isShowing) {
windowExt.update(keyView, x, y, extWidth, extHeight)
} else {
windowExt.width = extWidth
windowExt.height = extHeight
windowExt.showAsDropDown(keyView, x, y, Gravity.NO_GRAVITY)
popupViewExt.properties.apply {
width = extWidth
height = extHeight
xOffset = x
yOffset = y
gravity = if (anchorLeft) { Gravity.START } else { Gravity.END }
labelTextSize = keyPopupTextSize
activeElementIndex = initUiIndex
}
popupViewExt.show(keyView)
popupView.properties.shouldIndicateExtendedPopups = false
popupView.invalidate()
}
/**
@@ -410,7 +404,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
return false
}
activeExtIndex = when {
popupViewExt.properties.activeElementIndex = when {
anchorLeft -> when {
// check if out of boundary on x-axis
event.x < keyPopupDiffX - (anchorOffset + 1) * keyPopupWidth ||
@@ -456,24 +450,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
}
else -> -1
}
if (keyView is KeyView) {
for (k in keyView.dataPopupWithHint.indices) {
val view = popupViewExt.root.getChildAt(k)
if (view != null) {
val textView = view as KeyPopupExtendedSingleView
textView.isActive = k == activeExtIndex
}
}
} else if (keyView is EmojiKeyView) {
for (k in keyView.data.popup.indices) {
val view = popupViewExt.root.getChildAt(k)
if (view != null) {
val textView = view as KeyPopupExtendedSingleView
textView.isActive = k == activeExtIndex
}
}
}
popupViewExt.invalidate()
return true
}
@@ -488,14 +465,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*/
fun getActiveKeyData(keyView: T_KV): KeyData? {
return if (keyView is KeyView) {
val activeExtIndex = activeExtIndex
if (activeExtIndex != null) {
val singleView = popupViewExt.root[activeExtIndex]
if (singleView is KeyPopupExtendedSingleView) {
keyView.dataPopupWithHint.getOrNull(singleView.adjustedIndex) ?: keyView.data
} else {
keyView.data
}
val element = popupViewExt.properties.getElementOrNull()
if (element != null) {
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
} else {
keyView.data
}
@@ -514,7 +486,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
*/
fun getActiveEmojiKeyData(keyView: T_KV): EmojiKeyData? {
return if (keyView is EmojiKeyView) {
keyView.data.popup.getOrNull(activeExtIndex ?: -1) ?: keyView.data
val element = popupViewExt.properties.getElementOrNull()
if (element != null) {
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
} else {
keyView.data
}
} else {
null
}
@@ -524,12 +501,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
* Hides the key preview popup as well as the extended popup.
*/
fun hide() {
popupView.root.visibility = View.INVISIBLE
if (windowExt.isShowing) {
windowExt.dismiss()
}
activeExtIndex = null
popupView.hide()
popupViewExt.hide()
popupViewExt.properties.activeElementIndex = -1
}
/**
@@ -537,13 +511,10 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
* is closing.
*/
fun dismissAllPopups() {
if (window.isShowing) {
window.dismiss()
}
if (windowExt.isShowing) {
windowExt.dismiss()
}
activeExtIndex = null
popupView.hide()
popupLayerView?.removeView(popupView)
popupViewExt.hide()
popupViewExt.properties.activeElementIndex = -1
popupLayerView?.removeView(popupViewExt)
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
/**
* A popup set for a single key. This set describes, if the key has a [hint] character,
* a [main] character and other [relevant] popups.
*
* Note, that a hint character should **never** be set in a json extended popup file, rather it
* should only be dynamically set by the LayoutManager.
*
* The order in which these defined popups will be shown depends on the current [KeyHintMode],
* al well as the calculations made by the KeyPopupManager.
*
* The popup set can be accessed like an array with the addition that negative indexes defined
* within this companion object are allowed (as long as the corresponding [hint] or [main]
* character is *not* null).
*/
class PopupSet<T> (
var hint: T? = null,
var main: T? = null,
var relevant: List<T> = listOf()
) : Collection<T> {
companion object {
const val HINT_INDEX: Int = -2
const val MAIN_INDEX: Int = -1
}
override val size: Int
get() = if (hint != null) { 1 } else { 0 } + if (main != null) { 1 } else { 0 } +
relevant.size
fun size(keyHintMode: KeyHintMode): Int {
return if (keyHintMode == KeyHintMode.DISABLED && hint != null) {
size - 1
} else {
size
}
}
operator fun get(index: Int): T {
val item = getOrNull(index)
if (item == null) {
throw IndexOutOfBoundsException(
"Specified index $index is not an valid entry in this PopupSet!"
)
} else {
return item
}
}
fun getOrNull(index: Int): T? {
if (index >= relevant.size || index < HINT_INDEX) {
return null
}
return when (index) {
HINT_INDEX -> hint
MAIN_INDEX -> main
else -> relevant.getOrNull(index)
}
}
override fun contains(element: T): Boolean {
return hint == element || main == element || relevant.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
for (element in elements) {
if (!contains(element)) {
return false
}
}
return true
}
override fun iterator(): Iterator<T> {
TODO("Not yet implemented")
}
override fun isEmpty(): Boolean {
return size == 0
}
fun merge(other: PopupSet<T>) {
val tempRelevant = relevant.toMutableList()
tempRelevant.addAll(other.relevant)
other.hint?.let {
if (hint == null) {
hint = it
} else {
tempRelevant.add(it)
}
}
other.main?.let {
if (main == null) {
main = it
} else {
tempRelevant.add(it)
}
}
relevant = tempRelevant.toList()
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.util.ViewLayoutUtils
class PopupView : View, ThemeManager.OnThemeUpdatedListener {
private val themeManager: ThemeManager = ThemeManager.default()
private var backgroundDrawable: PaintDrawable = PaintDrawable()
private val labelPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = resources.getDimension(R.dimen.key_textSize)
typeface = Typeface.DEFAULT
}
private val threeDotsDrawable: Drawable? =
ContextCompat.getDrawable(context, R.drawable.ic_more_horiz)
val properties: Properties = Properties(
width = resources.getDimension(R.dimen.key_width).toInt(),
height = resources.getDimension(R.dimen.key_height).toInt(),
xOffset = 0,
yOffset = 0,
innerLabelFactor = 0.4f,
label = "",
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
shouldIndicateExtendedPopups = false
)
val isShowing: Boolean
get() = visibility == VISIBLE
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init {
visibility = GONE
background = backgroundDrawable
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
backgroundDrawable.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
threeDotsDrawable?.apply {
setTint(theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color)
}
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
if (isShowing) {
invalidate()
}
}
private fun applyProperties(anchor: View) {
val anchorCoords = IntArray(2)
anchor.getLocationInWindow(anchorCoords)
val anchorX = anchorCoords[0]
val anchorY = anchorCoords[1] + anchor.measuredHeight
when (val lp = layoutParams) {
is FrameLayout.LayoutParams -> lp.apply {
width = properties.width
height = properties.height
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
else -> {
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
setMargins(
anchorX + properties.xOffset,
anchorY + properties.yOffset,
0,
0
)
}
}
}
labelPaint.textSize = properties.labelTextSize
if (isShowing) {
requestLayout()
invalidate()
}
}
fun show(anchor: View) {
applyProperties(anchor)
visibility = VISIBLE
requestLayout()
invalidate()
}
fun hide() {
visibility = GONE
requestLayout()
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
// Draw label
val label = properties.label
if (label.isNotEmpty()) {
val centerX = measuredWidth / 2.0f
val centerY =
(measuredHeight * properties.innerLabelFactor) / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
canvas.drawText(label, centerX, centerY, labelPaint)
}
// Draw drawable
val drawable = threeDotsDrawable
if (drawable != null && properties.shouldIndicateExtendedPopups) {
val marginTop = measuredHeight * properties.innerLabelFactor
val drawableSize = marginTop * 0.25f
drawable.setBounds(
(measuredWidth * 0.95f - drawableSize).toInt(),
marginTop.toInt(),
(measuredWidth * 0.95f).toInt(),
(marginTop + drawableSize).toInt()
)
drawable.draw(canvas)
}
}
data class Properties(
var width: Int,
var height: Int,
var xOffset: Int,
var yOffset: Int,
var innerLabelFactor: Float,
var label: String,
var labelTextSize: Float,
var shouldIndicateExtendedPopups: Boolean
)
}

View File

@@ -18,13 +18,11 @@ package dev.patrickgold.florisboard.ime.text
import android.content.Context
import android.os.Handler
import android.util.Log
import android.view.KeyEvent
import android.view.inputmethod.*
import android.widget.LinearLayout
import android.widget.Toast
import android.widget.ViewFlipper
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.*
import dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView
@@ -121,9 +119,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
val keyboardView = KeyboardView(florisboard.context)
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(mode, florisboard.activeSubtype, florisboard.prefs).await()
keyboardViews[mode] = keyboardView
withContext(Dispatchers.Main) {
textViewFlipper?.addView(keyboardView)
}
textViewFlipper?.addView(keyboardView)
}
/**
@@ -132,16 +128,14 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
override fun onRegisterInputView(inputView: InputView) {
Timber.i("onRegisterInputView(inputView)")
launch(Dispatchers.Default) {
launch(Dispatchers.Main) {
textViewGroup = inputView.findViewById(R.id.text_input)
textViewFlipper = inputView.findViewById(R.id.text_input_view_flipper)
editingKeyboardView = inputView.findViewById(R.id.editing)
val activeKeyboardMode = getActiveKeyboardMode()
addKeyboardView(activeKeyboardMode)
withContext(Dispatchers.Main) {
setActiveKeyboardMode(activeKeyboardMode)
}
setActiveKeyboardMode(activeKeyboardMode)
for (mode in KeyboardMode.values()) {
if (mode != activeKeyboardMode && mode != KeyboardMode.SMARTBAR_NUMBER_ROW) {
addKeyboardView(mode)
@@ -659,7 +653,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
}
else -> when (keyData.type) {
KeyType.CHARACTER -> when (keyData.code) {
KeyType.CHARACTER, KeyType.NUMERIC -> when (keyData.code) {
KeyCode.SPACE -> handleSpace()
KeyCode.URI_COMPONENT_TLD -> {
val tld = when (caps) {

View File

@@ -33,15 +33,19 @@ import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeValue
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postAtScheduledRate
/**
* View class for managing and rendering an editing key.
*/
class EditingKeyView : AppCompatImageButton {
class EditingKeyView : AppCompatImageButton, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private val data: KeyData
private var isKeyPressed: Boolean = false
private val repeatedKeyPressHandler: Handler = Handler(context.mainLooper)
@@ -57,6 +61,10 @@ class EditingKeyView : AppCompatImageButton {
typeface = Typeface.DEFAULT
}
private var colorHighlightedEnabled: ThemeValue = ThemeValue.SolidColor(0)
private var colorEnabled: ThemeValue = ThemeValue.SolidColor(0)
private var colorDefault: ThemeValue = ThemeValue.SolidColor(0)
var isHighlighted: Boolean = false
set(value) { field = value; invalidate() }
@@ -78,13 +86,23 @@ class EditingKeyView : AppCompatImageButton {
R.id.select_all -> KeyCode.CLIPBOARD_SELECT_ALL
else -> 0
}
data = KeyData(code)
data = KeyData(code = code)
context.obtainStyledAttributes(attrs, R.styleable.EditingKeyView).apply {
label = getString(R.styleable.EditingKeyView_android_text)
recycle()
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (!isEnabled || event == null) {
@@ -125,6 +143,16 @@ class EditingKeyView : AppCompatImageButton {
return true
}
override fun onThemeUpdated(theme: Theme) {
imageTintList = ColorStateList.valueOf(when {
isEnabled -> theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND).toSolidColor().color
else -> theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND_ALT).toSolidColor().color
})
colorHighlightedEnabled = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY)
colorEnabled = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND_ALT)
colorDefault = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND)
}
/**
* Draw the key label / drawable.
*/
@@ -133,20 +161,15 @@ class EditingKeyView : AppCompatImageButton {
canvas ?: return
imageTintList = ColorStateList.valueOf(when {
isEnabled -> prefs.theme.smartbarFgColor
else -> prefs.theme.smartbarFgColorAlt
})
// Draw label
val label = label
if (label != null) {
labelPaint.color = if (isHighlighted && isEnabled) {
prefs.theme.colorPrimary
colorHighlightedEnabled.toSolidColor().color
} else if (!isEnabled) {
prefs.theme.smartbarFgColorAlt
colorEnabled.toSolidColor().color
} else {
prefs.theme.smartbarFgColor
colorDefault.toSolidColor().color
}
val isPortrait =
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT

View File

@@ -17,22 +17,23 @@
package dev.patrickgold.florisboard.ime.text.editing
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
import kotlin.math.roundToInt
/**
* View class for updating the key views depending on the current selection and clipboard state.
*/
class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener,
ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private var arrowUpKey: EditingKeyView? = null
private var arrowDownKey: EditingKeyView? = null
@@ -50,6 +51,7 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
themeManager.registerOnThemeUpdatedListener(this)
arrowUpKey = findViewById(R.id.arrow_up)
arrowDownKey = findViewById(R.id.arrow_down)
@@ -60,6 +62,15 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
pasteKey = findViewById(R.id.clipboard_paste)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
themeManager.unregisterOnThemeUpdatedListener(this)
}
override fun onThemeUpdated(theme: Theme) {
setBackgroundTintColor2(this, theme.getAttr(Theme.Attr.SMARTBAR_BACKGROUND).toSolidColor().color)
}
override fun onUpdateSelection() {
val isSelectionActive = florisboard?.activeEditorInstance?.selection?.isSelectionMode ?: false
val isSelectionMode = florisboard?.textInputManager?.isManualSelectionMode ?: false
@@ -98,9 +109,4 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY))
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setBackgroundTintColor2(this, prefs.theme.smartbarBgColor)
}
}

View File

@@ -30,11 +30,11 @@ enum class DistanceThreshold {
companion object {
fun fromString(string: String): DistanceThreshold {
return valueOf(string.toUpperCase(Locale.ROOT))
return valueOf(string.toUpperCase(Locale.ENGLISH))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
return super.toString().toLowerCase(Locale.ENGLISH)
}
}

View File

@@ -33,15 +33,16 @@ enum class SwipeAction {
MOVE_CURSOR_RIGHT,
SHIFT,
SWITCH_TO_PREV_SUBTYPE,
SWITCH_TO_NEXT_SUBTYPE;
SWITCH_TO_NEXT_SUBTYPE,
SWITCH_TO_PREV_KEYBOARD;
companion object {
fun fromString(string: String): SwipeAction {
return valueOf(string.toUpperCase(Locale.ROOT))
return valueOf(string.toUpperCase(Locale.ENGLISH))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
return super.toString().toLowerCase(Locale.ENGLISH)
}
}

View File

@@ -30,11 +30,11 @@ enum class VelocityThreshold {
companion object {
fun fromString(string: String): VelocityThreshold {
return valueOf(string.toUpperCase(Locale.ROOT))
return valueOf(string.toUpperCase(Locale.ENGLISH))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
return super.toString().toLowerCase(Locale.ENGLISH)
}
}

View File

@@ -16,33 +16,68 @@
package dev.patrickgold.florisboard.ime.text.key
import dev.patrickgold.florisboard.ime.popup.PopupSet
/**
* Data class which describes a single key and its variants.
* Data class which describes a single key and its attributes.
*
* @property type The type of the key. Some actions require both [code] and [type] to match in order
* to be successfully executed. Defaults to [KeyType.CHARACTER].
* @property code The UTF-8 encoded code of the character. The code defined here is used as the
* data passed to the system.
* data passed to the system. Defaults to 0.
* @property label The string used to display the key in the UI. Is not used for the actual data
* passed to the system. Should normally be the exact same as the [code]. Defaults to an empty
* string.
* @property hintedNumber The hinted number which will be dynamically inserted into the long-press
* [popup]. Leave null to disable the hinted popup for this key. The visibility of the hinted number
* is controlled by the preferences. Defaults to null.
* @property hintedSymbol The hinted symbol which will be dynamically inserted into the long-press
* [popup]. Leave null to disable the hinted popup for this key. The visibility of the hinted symbol
* is controlled by the preferences. Defaults to null.
* @property popup List of keys which will be accessible while long pressing the key. Defaults to
* an empty list (no extended popup).
* @property type The type of the key. Some actions require both [code] and [type] to match in order
* to be successfully executed. Defaults to [KeyType.CHARACTER].
*/
open class KeyData(
var type: KeyType = KeyType.CHARACTER,
var code: Int = 0,
var label: String = ""
)
/**
* Data class which describes a single key and its attributes, while also providing additional
* characters via the extended popup menu.
*
* @property groupId The Id of the group this key belongs to. An valid number between 0 and INT_MAX
* may be used. Custom group Ids can be used, but must not be in the range [0;99], as these group
* Ids are reserved for default and internal usage. Defaults to [GROUP_DEFAULT].
* @property variation Controls if the key should only be shown in some contexts (e.g.: url input)
* or if the key should always be visible. Defaults to [KeyVariation.ALL].
* @property popup List of keys which will be accessible while long pressing the key. Defaults to
* an empty set (no extended popup).
*/
data class KeyData(
var code: Int,
var label: String = "",
var hintedNumber: KeyData? = null,
var hintedSymbol: KeyData? = null,
var popup: MutableList<KeyData> = mutableListOf(),
var type: KeyType = KeyType.CHARACTER,
var variation: KeyVariation = KeyVariation.ALL
)
class FlorisKeyData(
type: KeyType = KeyType.CHARACTER,
code: Int = 0,
label: String = "",
var groupId: Int = GROUP_DEFAULT,
var variation: KeyVariation = KeyVariation.ALL,
var popup: PopupSet<KeyData> = PopupSet()
) : KeyData(type, code, label) {
companion object {
/**
* Constant for the default group. If not otherwise specified, any key is automatically
* assigned to this group.
*/
const val GROUP_DEFAULT: Int = 0
/**
* Constant for the Left modifier key group. Any key belonging to this group will get the
* popups specified for "~left" in the popup mapping.
*/
const val GROUP_LEFT: Int = 1
/**
* Constant for the right modifier key group. Any key belonging to this group will get the
* popups specified for "~right" in the popup mapping.
*/
const val GROUP_RIGHT: Int = 2
/**
* Constant for the enter modifier key group. Any key belonging to this group will get the
* popups specified for "~enter" in the popup mapping.
*/
const val GROUP_ENTER: Int = 3
}
}

View File

@@ -29,11 +29,11 @@ enum class KeyHintMode {
companion object {
fun fromString(string: String): KeyHintMode {
return valueOf(string.toUpperCase(Locale.ROOT))
return valueOf(string.toUpperCase(Locale.ENGLISH))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
return super.toString().toLowerCase(Locale.ENGLISH)
}
}

View File

@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard.ime.text.key
import android.annotation.SuppressLint
import com.squareup.moshi.FromJson
import java.util.*
/**
* Enum for declaring the type of the key.
@@ -26,18 +27,19 @@ import com.squareup.moshi.FromJson
*/
enum class KeyType {
CHARACTER,
MODIFIER,
ENTER_EDITING,
SYSTEM_GUI,
NAVIGATION,
FUNCTION,
LOCK,
MODIFIER,
NAVIGATION,
SYSTEM_GUI,
NUMERIC,
LOCK;
PLACEHOLDER,
UNSPECIFIED;
companion object {
@SuppressLint("DefaultLocale")
fun fromString(string: String): KeyType {
return valueOf(string.toUpperCase())
return valueOf(string.toUpperCase(Locale.ENGLISH))
}
}
}

View File

@@ -20,14 +20,13 @@ import android.annotation.SuppressLint
import android.content.res.Configuration
import android.graphics.*
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.children
import com.google.android.flexbox.FlexboxLayout
import dev.patrickgold.florisboard.R
@@ -38,10 +37,10 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postAtScheduledRate
import dev.patrickgold.florisboard.util.postDelayed
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeValue
import dev.patrickgold.florisboard.util.*
import java.util.*
/**
@@ -55,20 +54,23 @@ import java.util.*
@SuppressLint("ViewConstructor")
class KeyView(
private val keyboardView: KeyboardView,
val data: KeyData
) : View(keyboardView.context), SwipeGesture.Listener {
val dataPopupWithHint: MutableList<KeyData>
val data: FlorisKeyData
) : View(keyboardView.context), SwipeGesture.Listener, ThemeManager.OnThemeUpdatedListener {
private var isKeyPressed: Boolean = false
set(value) {
field = value
updateKeyPressedBackground()
}
private var hasTriggeredGestureMove: Boolean = false
private var keyHintMode: KeyHintMode = KeyHintMode.DISABLED
private val longKeyPressHandler: Handler = Handler(context.mainLooper)
private val repeatedKeyPressHandler: Handler = Handler(context.mainLooper)
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private var shouldBlockNextKeyCode: Boolean = false
private var backgroundDrawable: PaintDrawable = PaintDrawable().apply {
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
}
private var desiredWidth: Int = 0
private var desiredHeight: Int = 0
private var drawable: Drawable? = null
@@ -96,6 +98,7 @@ class KeyView(
typeface = Typeface.DEFAULT
}
private val tempRect: Rect = Rect()
private var themeValueCache: ThemeValueCache = ThemeValueCache()
var florisboard: FlorisBoard? = null
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
@@ -142,31 +145,14 @@ class KeyView(
}
setPadding(0, 0, 0, 0)
background = getDrawable(context, R.drawable.shape_rect_rounded)
elevation = 4.0f
background = backgroundDrawable
elevation = if(themeValueCache.shouldShowBorder) 4.0f else 0.0f
var hintKeyData: KeyData? = null
var hintKeyMode: KeyHintMode = KeyHintMode.DISABLED
val hintedNumber = data.hintedNumber
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && hintedNumber != null) {
hintKeyData = hintedNumber
hintKeyMode = prefs.keyboard.hintedNumberRowMode
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && data.popup.hint?.type == KeyType.NUMERIC) {
keyHintMode = prefs.keyboard.hintedNumberRowMode
}
val hintedSymbol = data.hintedSymbol
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && hintedSymbol != null) {
hintKeyData = hintedSymbol
hintKeyMode = prefs.keyboard.hintedSymbolsMode
}
dataPopupWithHint = if (hintKeyData == null) {
data.popup.toMutableList()
} else {
val popupList = data.popup.toMutableList()
if (hintKeyMode == KeyHintMode.ENABLED_HINT_PRIORITY) {
popupList.add(0, hintKeyData)
} else {
popupList.add(hintKeyData)
}
popupList
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && data.popup.hint?.type == KeyType.CHARACTER) {
keyHintMode = prefs.keyboard.hintedNumberRowMode
}
updateKeyPressedBackground()
@@ -227,44 +213,51 @@ class KeyView(
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
val delayMillis = prefs.keyboard.longPressDelay.toLong()
hasTriggeredGestureMove = false
shouldBlockNextKeyCode = false
florisboard?.prefs?.keyboard?.let {
if (it.popupEnabled){
keyboardView.popupManager.show(this)
if (data.code == KeyCode.SHIFT) {
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
florisboard?.textInputManager?.sendKeyPress(data)
} else {
val delayMillis = prefs.keyboard.longPressDelay.toLong()
hasTriggeredGestureMove = false
shouldBlockNextKeyCode = false
florisboard?.prefs?.keyboard?.let {
if (it.popupEnabled){
keyboardView.popupManager.show(this, keyHintMode)
}
}
}
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
when (data.code) {
KeyCode.ARROW_DOWN,
KeyCode.ARROW_LEFT,
KeyCode.ARROW_RIGHT,
KeyCode.ARROW_UP,
KeyCode.DELETE -> {
repeatedKeyPressHandler.postAtScheduledRate(delayMillis, 25) {
if (isKeyPressed) {
florisboard?.textInputManager?.sendKeyPress(data)
} else {
repeatedKeyPressHandler.cancelAll()
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
when (data.code) {
KeyCode.ARROW_DOWN,
KeyCode.ARROW_LEFT,
KeyCode.ARROW_RIGHT,
KeyCode.ARROW_UP,
KeyCode.DELETE -> {
repeatedKeyPressHandler.postAtScheduledRate(delayMillis, 25) {
if (isKeyPressed) {
florisboard?.textInputManager?.sendKeyPress(data)
} else {
repeatedKeyPressHandler.cancelAll()
}
}
}
}
}
longKeyPressHandler.postDelayed(delayMillis) {
if (dataPopupWithHint.isNotEmpty()) {
keyboardView.popupManager.extend(this)
}
if (data.code == KeyCode.SPACE) {
florisboard?.textInputManager?.sendKeyPress(
KeyData(
KeyCode.SHOW_INPUT_METHOD_PICKER,
type = KeyType.FUNCTION
longKeyPressHandler.postDelayed(delayMillis) {
if (data.popup.isNotEmpty()) {
keyboardView.popupManager.extend(this, keyHintMode)
}
if (data.code == KeyCode.SPACE) {
florisboard?.textInputManager?.sendKeyPress(
KeyData(
type = KeyType.FUNCTION,
code = KeyCode.SHOW_INPUT_METHOD_PICKER,
)
)
)
shouldBlockNextKeyCode = true
shouldBlockNextKeyCode = true
}
}
}
}
@@ -289,25 +282,27 @@ class KeyView(
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isKeyPressed = false
longKeyPressHandler.cancelAll()
repeatedKeyPressHandler.cancelAll()
if (hasTriggeredGestureMove && data.code == KeyCode.DELETE) {
hasTriggeredGestureMove = false
florisboard?.activeEditorInstance?.apply {
if (selection.isSelectionMode) {
deleteBackwards()
if (data.code != KeyCode.SHIFT) {
if (hasTriggeredGestureMove && data.code == KeyCode.DELETE) {
hasTriggeredGestureMove = false
florisboard?.activeEditorInstance?.apply {
if (selection.isSelectionMode) {
deleteBackwards()
}
}
}
} else {
val retData = keyboardView.popupManager.getActiveKeyData(this)
keyboardView.popupManager.hide()
if (event.actionMasked != MotionEvent.ACTION_CANCEL && !shouldBlockNextKeyCode && retData != null) {
florisboard?.textInputManager?.sendKeyPress(retData)
} else {
shouldBlockNextKeyCode = false
val retData = keyboardView.popupManager.getActiveKeyData(this)
if (event.actionMasked != MotionEvent.ACTION_CANCEL && !shouldBlockNextKeyCode && retData != null) {
florisboard?.textInputManager?.sendKeyPress(retData)
} else {
shouldBlockNextKeyCode = false
}
keyboardView.popupManager.hide()
}
}
isKeyPressed = false
}
else -> return false
}
@@ -372,6 +367,11 @@ class KeyView(
}
KeyCode.SPACE -> when (type) {
SwipeGesture.Type.TOUCH_MOVE -> when (direction) {
SwipeGesture.Direction.UP -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeUp)
shouldBlockNextKeyCode = true
true
}
SwipeGesture.Direction.LEFT -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
shouldBlockNextKeyCode = true
@@ -493,50 +493,51 @@ class KeyView(
}
}
override fun onThemeUpdated(theme: Theme) {
if (keyboardView.isSmartbarKeyboardView) {
themeValueCache.apply {
keyBackground = theme.getAttr(Theme.Attr.SMARTBAR_BACKGROUND)
keyBackgroundPressed = theme.getAttr(Theme.Attr.SMARTBAR_BUTTON_BACKGROUND)
keyForeground = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND)
keyForegroundAlt = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND_ALT)
keyForegroundPressed = theme.getAttr(Theme.Attr.SMARTBAR_FOREGROUND)
shouldShowBorder = false
}
} else {
val label = data.label
val capsSpecific = when {
florisboard?.textInputManager?.capsLock == true -> {
"capslock"
}
florisboard?.textInputManager?.caps == true -> {
"caps"
}
else -> {
null
}
}
themeValueCache.apply {
keyBackground = theme.getAttr(Theme.Attr.KEY_BACKGROUND, label, capsSpecific)
keyBackgroundPressed = theme.getAttr(Theme.Attr.KEY_BACKGROUND_PRESSED, label, capsSpecific)
keyForeground = theme.getAttr(Theme.Attr.KEY_FOREGROUND, label, capsSpecific)
keyForegroundAlt = ThemeValue.SolidColor(0)
keyForegroundPressed = theme.getAttr(Theme.Attr.KEY_FOREGROUND_PRESSED, label, capsSpecific)
shouldShowBorder = theme.getAttr(Theme.Attr.KEY_SHOW_BORDER, label, capsSpecific).toOnOff().state
}
}
updateKeyPressedBackground()
}
/**
* Updates the background depending on [isKeyPressed] and [data].
*/
private fun updateKeyPressedBackground() {
when {
keyboardView.isSmartbarKeyboardView -> {
elevation = 0.0f
setBackgroundTintColor2(
this, when {
isKeyPressed && isEnabled -> prefs.theme.smartbarButtonBgColor
else -> prefs.theme.smartbarBgColor
}
)
}
else -> {
elevation = 4.0f
when (data.code) {
KeyCode.ENTER -> {
setBackgroundTintColor2(
this, when {
isKeyPressed && isEnabled -> prefs.theme.keyEnterBgColorPressed
else -> prefs.theme.keyEnterBgColor
}
)
}
KeyCode.SHIFT -> {
setBackgroundTintColor2(
this, when {
isKeyPressed && isEnabled -> prefs.theme.keyShiftBgColorPressed
else -> prefs.theme.keyShiftBgColor
}
)
}
else -> {
setBackgroundTintColor2(
this, when {
isKeyPressed && isEnabled -> prefs.theme.keyBgColorPressed
else -> prefs.theme.keyBgColor
}
)
}
}
}
}
elevation = if (themeValueCache.shouldShowBorder) 4.0f else 0.0f
backgroundDrawable.setTint(when {
isKeyPressed && isEnabled -> themeValueCache.keyBackgroundPressed.toSolidColor().color
else -> themeValueCache.keyBackground.toSolidColor().color
})
invalidate()
}
/**
@@ -590,16 +591,7 @@ class KeyView(
}
else -> if (data.variation != KeyVariation.ALL) {
val keyVariation = florisboard?.textInputManager?.keyVariation ?: KeyVariation.NORMAL
visibility =
if (data.variation == KeyVariation.NORMAL && (keyVariation == KeyVariation.NORMAL
|| keyVariation == KeyVariation.PASSWORD)
) {
VISIBLE
} else if (data.variation == keyVariation) {
VISIBLE
} else {
GONE
}
visibility = if (data.variation == keyVariation) { VISIBLE } else { GONE }
updateTouchHitBox()
}
}
@@ -646,50 +638,40 @@ class KeyView(
canvas ?: return
updateKeyPressedBackground()
if (data.type == KeyType.CHARACTER && data.code != KeyCode.SPACE
&& data.code != KeyCode.HALF_SPACE && data.code != KeyCode.KESHIDA || data.type == KeyType.NUMERIC
) {
label = getComputedLetter()
val hintedNumber = data.hintedNumber
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && hintedNumber != null) {
hintedLabel = getComputedLetter(hintedNumber)
val hint = data.popup.hint
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && hint?.type == KeyType.NUMERIC) {
hintedLabel = getComputedLetter(hint)
}
val hintedSymbol = data.hintedSymbol
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && hintedSymbol != null) {
hintedLabel = getComputedLetter(hintedSymbol)
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && hint?.type == KeyType.CHARACTER) {
hintedLabel = getComputedLetter(hint)
}
} else {
when (data.code) {
KeyCode.ARROW_LEFT -> {
drawable = getDrawable(context, R.drawable.ic_keyboard_arrow_left)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.ARROW_RIGHT -> {
drawable = getDrawable(context, R.drawable.ic_keyboard_arrow_right)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.CLIPBOARD_COPY -> {
drawable = getDrawable(context, R.drawable.ic_content_copy)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.CLIPBOARD_CUT -> {
drawable = getDrawable(context, R.drawable.ic_content_cut)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.CLIPBOARD_PASTE -> {
drawable = getDrawable(context, R.drawable.ic_content_paste)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.CLIPBOARD_SELECT_ALL -> {
drawable = getDrawable(context, R.drawable.ic_select_all)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.DELETE -> {
drawable = getDrawable(context, R.drawable.ic_backspace)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.ENTER -> {
val imeOptions = florisboard?.activeEditorInstance?.imeOptions ?: ImeOptions.default()
@@ -703,31 +685,19 @@ class KeyView(
ImeOptions.Action.SEND -> R.drawable.ic_send
ImeOptions.Action.UNSPECIFIED -> R.drawable.ic_keyboard_return
})
drawableColor = prefs.theme.keyEnterFgColor
if (imeOptions.flagNoEnterAction) {
drawable = getDrawable(context, R.drawable.ic_keyboard_return)
}
}
KeyCode.LANGUAGE_SWITCH -> {
drawable = getDrawable(context, R.drawable.ic_language)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.PHONE_PAUSE -> label = resources.getString(R.string.key__phone_pause)
KeyCode.PHONE_WAIT -> label = resources.getString(R.string.key__phone_wait)
KeyCode.SHIFT -> {
drawable = getDrawable(context, when {
florisboard?.textInputManager?.caps ?: false && florisboard?.textInputManager?.capsLock ?: false -> {
drawableColor = prefs.theme.keyShiftFgColorCapsLock
R.drawable.ic_keyboard_capslock
}
florisboard?.textInputManager?.caps ?: false && !(florisboard?.textInputManager?.capsLock ?: false) -> {
drawableColor = prefs.theme.keyShiftFgColor
R.drawable.ic_keyboard_capslock
}
else -> {
drawableColor = prefs.theme.keyShiftFgColor
R.drawable.ic_keyboard_arrow_up
}
drawable = getDrawable(context, when (florisboard?.textInputManager?.caps) {
true -> R.drawable.ic_keyboard_capslock
else -> R.drawable.ic_keyboard_arrow_up
})
}
KeyCode.SPACE -> {
@@ -737,7 +707,6 @@ class KeyView(
KeyboardMode.PHONE,
KeyboardMode.PHONE2 -> {
drawable = getDrawable(context, R.drawable.ic_space_bar)
drawableColor = prefs.theme.keyFgColor
}
KeyboardMode.CHARACTERS -> {
label = florisboard?.activeSubtype?.locale?.displayName
@@ -747,7 +716,6 @@ class KeyView(
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
drawable = getDrawable(context, R.drawable.ic_sentiment_satisfied)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.SWITCH_TO_TEXT_CONTEXT,
KeyCode.VIEW_CHARACTERS -> {
@@ -781,8 +749,10 @@ class KeyView(
// Draw drawable
val drawable = drawable
if (drawable != null) {
if (keyboardView.isSmartbarKeyboardView && !isEnabled) {
drawableColor = prefs.theme.smartbarFgColorAlt
drawableColor = if (keyboardView.isSmartbarKeyboardView && !isEnabled) {
themeValueCache.keyForegroundAlt.toSolidColor().color
} else {
themeValueCache.keyForeground.toSolidColor().color
}
var marginV = 0
var marginH = 0
@@ -797,10 +767,7 @@ class KeyView(
marginV + drawablePaddingV,
measuredWidth - marginH - drawablePaddingV,
measuredHeight - marginV - drawablePaddingV)
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
drawableColor,
BlendModeCompat.SRC_ATOP
)
drawable.setTint(drawableColor)
drawable.draw(canvas)
}
@@ -849,7 +816,7 @@ class KeyView(
}
}
}
labelPaint.color = prefs.theme.keyFgColor
labelPaint.color = themeValueCache.keyForeground.toSolidColor().color
labelPaint.alpha = if (keyboardView.computedLayout?.mode == KeyboardMode.CHARACTERS &&
data.code == KeyCode.SPACE) { 120 } else { 255 }
val centerX = measuredWidth / 2.0f
@@ -876,7 +843,7 @@ class KeyView(
// they will all look different and weird...
"X"
)
hintedLabelPaint.color = prefs.theme.keyFgColor
hintedLabelPaint.color = labelPaint.color
hintedLabelPaint.alpha = 120
val centerX = measuredWidth * 5.0f / 6.0f
val centerY = measuredHeight * 1.0f / 6.0f + (hintedLabelPaint.textSize - hintedLabelPaint.descent()) / 2
@@ -884,10 +851,19 @@ class KeyView(
}
}
private data class ThemeValueCache(
var keyBackground: ThemeValue = ThemeValue.SolidColor(0),
var keyBackgroundPressed: ThemeValue = ThemeValue.SolidColor(0),
var keyForeground: ThemeValue = ThemeValue.SolidColor(0),
var keyForegroundAlt: ThemeValue = ThemeValue.SolidColor(0),
var keyForegroundPressed: ThemeValue = ThemeValue.SolidColor(0),
var shouldShowBorder: Boolean = true
)
/**
* Custom Outline Provider, needed for the [KeyView] elevation rendering.
*/
private class KeyViewOutline(
private inner class KeyViewOutline(
private val width: Int,
private val height: Int
) : ViewOutlineProvider() {

View File

@@ -28,12 +28,14 @@ import com.google.android.flexbox.FlexboxLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
import dev.patrickgold.florisboard.ime.popup.PopupManager
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.layout.ComputedLayoutData
import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlin.math.roundToInt
/**
@@ -44,7 +46,8 @@ import kotlin.math.roundToInt
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Listener {
class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Listener,
ThemeManager.OnThemeUpdatedListener {
private var activeKeyView: KeyView? = null
private var activePointerId: Int? = null
private var activeX: Float = 0.0f
@@ -59,15 +62,21 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
var desiredKeyHeight: Int = resources.getDimension(R.dimen.key_height).toInt()
var florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private var initialKeyCode: Int = 0
var isPreviewMode: Boolean = false
var isSmartbarKeyboardView: Boolean = false
var popupManager = KeyPopupManager<KeyboardView, KeyView>(this)
private val isPreviewMode: Boolean
val isSmartbarKeyboardView: Boolean
var popupManager = PopupManager<KeyboardView, KeyView>(this, florisboard?.popupLayerView)
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
context.obtainStyledAttributes(attrs, R.styleable.KeyboardView).apply {
isPreviewMode = getBoolean(R.styleable.KeyboardView_isPreviewKeyboard, false)
isSmartbarKeyboardView = getBoolean(R.styleable.KeyboardView_isSmartbarKeyboard, false)
recycle()
}
orientation = VERTICAL
layoutParams = layoutParams ?: FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
@@ -92,6 +101,11 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
}
addView(rowView)
}
if (!isPreviewMode) {
themeManager.requestThemeUpdate(this)
} else {
updateVisibility()
}
}
/**
@@ -101,12 +115,22 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
removeAllViews()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!isPreviewMode) {
themeManager.registerOnThemeUpdatedListener(this)
}
}
/**
* Dismisses all shown key popups when keyboard is detached from window.
*/
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
popupManager.dismissAllPopups()
if (!isPreviewMode) {
themeManager.unregisterOnThemeUpdatedListener(this)
}
}
override fun onWindowShown() {
@@ -314,9 +338,18 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
)
}
override fun onApplyThemeAttributes() {
override fun onThemeUpdated(theme: Theme) {
if (isPreviewMode) {
setBackgroundColor(prefs.theme.keyboardBgColor)
setBackgroundColor(theme.getAttr(Theme.Attr.KEYBOARD_BACKGROUND).toSolidColor().color)
}
for (row in children) {
if (row is ViewGroup) {
for (keyView in row.children) {
if (keyView is ThemeManager.OnThemeUpdatedListener) {
keyView.onThemeUpdated(theme)
}
}
}
}
}
@@ -336,18 +369,11 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
}
/**
* Queues a redraw for all keys.
* Queues a redraw for all keys. The ThemeManager's event automatically triggers an invalidate
* call on the KeyView's, so no need to manually loop through all KeyViews here.
*/
fun invalidateAllKeys() {
for (row in children) {
if (row is FlexboxLayout) {
for (keyView in row.children) {
if (keyView is KeyView) {
keyView.invalidate()
}
}
}
}
themeManager.requestThemeUpdate(this)
}
/**

View File

@@ -16,10 +16,10 @@
package dev.patrickgold.florisboard.ime.text.layout
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.FlorisKeyData
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
typealias LayoutDataArrangement = List<List<KeyData>>
typealias LayoutDataArrangement = List<List<FlorisKeyData>>
data class LayoutData(
val type: LayoutType,
val name: String,
@@ -28,9 +28,9 @@ data class LayoutData(
val arrangement: LayoutDataArrangement = listOf()
) {
private fun getComputedLayoutDataArrangement(): ComputedLayoutDataArrangement {
val ret = mutableListOf<MutableList<KeyData>>()
val ret = mutableListOf<MutableList<FlorisKeyData>>()
for (row in arrangement) {
val retRow = mutableListOf<KeyData>()
val retRow = mutableListOf<FlorisKeyData>()
for (keyData in row) {
retRow.add(keyData)
}
@@ -46,7 +46,7 @@ data class LayoutData(
}
}
typealias ComputedLayoutDataArrangement = MutableList<MutableList<KeyData>>
typealias ComputedLayoutDataArrangement = MutableList<MutableList<FlorisKeyData>>
data class ComputedLayoutData(
val mode: KeyboardMode,
val name: String,

View File

@@ -17,12 +17,13 @@
package dev.patrickgold.florisboard.ime.text.layout
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.github.michaelbull.result.getOr
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.popup.PopupExtension
import dev.patrickgold.florisboard.ime.popup.PopupSet
import dev.patrickgold.florisboard.ime.text.key.*
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import kotlinx.coroutines.*
@@ -63,34 +64,10 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
return layoutAdapter.fromJson(rawJsonData)
}
private fun loadExtendedPopups(subtype: Subtype): Map<String, List<KeyData>> {
val lang = subtype.locale.language
val map = loadExtendedPopupsInternal("ime/text/characters/extended_popups/$lang.json")
return map ?: mapOf()
}
private fun loadExtendedPopupsInternal(path: String): Map<String, List<KeyData>>? {
val rawJsonData: String = try {
context.assets.open(path).bufferedReader().use { it.readText() }
} catch (e: Exception) {
null
} ?: return null
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(KeyTypeAdapter())
.build()
val mapAdaptor: JsonAdapter<Map<String, List<KeyData>>> =
moshi.adapter(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Types.newParameterizedType(
List::class.java,
KeyData::class.java
)
)
)
return mapAdaptor.fromJson(rawJsonData)
private fun loadExtendedPopups(subtype: Subtype? = null): PopupExtension {
val lang = subtype?.locale?.language ?: "\$default"
val map = PopupExtension.fromFile(context, "ime/text/characters/extended_popups/$lang.json")
return map.getOr(PopupExtension.empty())
}
/**
@@ -142,7 +119,7 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
computedArrangement.add(mainRow.toMutableList())
} else {
// merge main and mod here
val mergedRow = mutableListOf<KeyData>()
val mergedRow = mutableListOf<FlorisKeyData>()
val firstModRow = modifierLayout.arrangement.firstOrNull()
for (modKey in (firstModRow ?: listOf())) {
if (modKey.code == 0) {
@@ -168,31 +145,54 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
}
}
// TODO: rewrite this part
// Add popup to keys
if (keyboardMode == KeyboardMode.CHARACTERS) {
val extendedPopupsDefault = loadExtendedPopups()
val extendedPopups = loadExtendedPopups(subtype)
for (computedRow in computedArrangement) {
for (keyData in computedRow) {
if (keyData.variation != KeyVariation.ALL) {
if (keyData.label == "." && modifierLayout?.name != "dvorak" ||
keyData.label == "z" && modifierLayout?.name == "dvorak") {
val label = "." // keyData.label
if (keyData.variation == KeyVariation.NORMAL ||
keyData.variation == KeyVariation.PASSWORD) {
if (extendedPopups.containsKey("$label~normal")) {
keyData.popup.addAll(extendedPopups["$label~normal"] ?: listOf())
}
}
if (keyData.variation == KeyVariation.EMAIL_ADDRESS ||
keyData.variation == KeyVariation.URI) {
if (extendedPopups.containsKey("$label~uri")) {
keyData.popup.addAll(extendedPopups["$label~uri"] ?: listOf())
}
}
}
} else if (extendedPopups.containsKey(keyData.label)) {
keyData.popup.addAll(extendedPopups[keyData.label] ?: listOf())
for (row in computedArrangement) {
var kOffset = 0
for ((k, key) in row.withIndex()) {
val lastKey = row.getOrNull(k - 1)
if (lastKey != null && lastKey.groupId == key.groupId && key.groupId != FlorisKeyData.GROUP_DEFAULT) {
kOffset++
}
val label = when (key.groupId) {
FlorisKeyData.GROUP_ENTER -> {
"~enter"
}
FlorisKeyData.GROUP_LEFT -> {
"~left"
}
FlorisKeyData.GROUP_RIGHT -> {
"~right"
}
else -> {
key.label
}
}
var popupSet: PopupSet<KeyData>? = null
val kv = key.variation
if (popupSet == null && kv == KeyVariation.PASSWORD) {
popupSet = extendedPopups.mapping[KeyVariation.PASSWORD]?.get(label) ?:
extendedPopupsDefault.mapping[KeyVariation.PASSWORD]?.get(label)
}
if (popupSet == null && (kv == KeyVariation.NORMAL || kv == KeyVariation.PASSWORD)) {
popupSet = extendedPopups.mapping[KeyVariation.NORMAL]?.get(label) ?:
extendedPopupsDefault.mapping[KeyVariation.NORMAL]?.get(label)
}
if (popupSet == null && kv == KeyVariation.EMAIL_ADDRESS) {
popupSet = extendedPopups.mapping[KeyVariation.EMAIL_ADDRESS]?.get(label) ?:
extendedPopupsDefault.mapping[KeyVariation.EMAIL_ADDRESS]?.get(label)
}
if (popupSet == null && (kv == KeyVariation.EMAIL_ADDRESS || kv == KeyVariation.URI)) {
popupSet = extendedPopups.mapping[KeyVariation.URI]?.get(label) ?:
extendedPopupsDefault.mapping[KeyVariation.URI]?.get(label)
}
if (popupSet == null) {
popupSet = extendedPopups.mapping[KeyVariation.ALL]?.get(label) ?:
extendedPopupsDefault.mapping[KeyVariation.ALL]?.get(label)
}
popupSet?.let { key.popup.merge(it) }
}
}
}
@@ -210,16 +210,14 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
if (symbolRow != null) {
for ((k, key) in row.withIndex()) {
val lastKey = row.getOrNull(k - 1)
if (key.variation != KeyVariation.ALL && lastKey != null && lastKey.variation != KeyVariation.ALL) {
if (lastKey != null && lastKey.groupId == key.groupId && key.groupId != FlorisKeyData.GROUP_DEFAULT) {
kOffset++
}
val symbol = symbolRow.getOrNull(k - kOffset)
if (key.type == KeyType.CHARACTER && symbol?.type == KeyType.CHARACTER) {
if (r == minRow) {
key.hintedNumber = symbol
} else if (r > minRow) {
key.hintedSymbol = symbol
}
if (r == minRow && key.type == KeyType.CHARACTER && symbol?.type == KeyType.NUMERIC) {
key.popup.hint = symbol
} else if (r > minRow && key.type == KeyType.CHARACTER && symbol?.type == KeyType.CHARACTER) {
key.popup.hint = symbol
}
}
}

Some files were not shown because too many files have changed in this diff Show More