diff --git a/README.md b/README.md index df52266b..8969d1da 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ This project was initially developed to help with the creator's system-wide prac - **Auto-hide**: The keyboard hides automatically when not in use - **Keymap styles**: Supports staggered, matrix, and split matrix (5-col and [6-col](/docs/advanced/6-column-layouts.md)) styles - **User configurations**: Add and use [custom keyboard layouts](/docs/advanced/custom-layouts.md) through configuration files +- **Key modifiers**: Add top labels and held-state keys to custom layouts with [key modifier objects](/docs/advanced/key-modifiers.md) - **Side-by-side layouts**: Display [alternative layouts](/docs/advanced/alternative-layouts.md) alongside the default layout - **Top row/Number row**: Optional row above the main keyboard for numbers or user-configured keys - **[Layer switching](/docs/advanced/layer-switching.md)**: Switch between multiple custom keyboard layers for QMK, ZMK, or other programmable keyboard firmware using configurable triggers and toggle modes @@ -180,7 +181,6 @@ OverKeys can be installed through several methods: Note: Please check if `winget` version is updated to the latest version as in the repo. Otherwise, use the [installer](https://github.com/conventoangelo/OverKeys/releases/latest) to have the latest version. 2. **Using the Installer** - - Download and run the latest [EXE installer](https://github.com/conventoangelo/OverKeys/releases/latest). 3. **Portable Version** @@ -202,6 +202,7 @@ Complete documentation for OverKeys is available in the [docs](docs/index.md) fo ### User Guide - [Preferences](/docs/user-guide/preferences.md) +- [Text Settings](/docs/user-guide/text.md) - [Learning Mode](/docs/user-guide/learning-mode.md) - [Built-in Layouts](#features) @@ -209,6 +210,7 @@ Complete documentation for OverKeys is available in the [docs](docs/index.md) fo - [Custom Font](/docs/advanced/custom-font.md) - [Custom Layouts](/docs/advanced/custom-layouts.md) +- [Key Modifiers](/docs/advanced/key-modifiers.md) - [Alternative Layouts](/docs/advanced/alternative-layouts.md) - [6-Column Layouts](/docs/advanced/6-column-layouts.md) - [Layer Switching](/docs/advanced/layer-switching.md) @@ -223,7 +225,6 @@ Complete documentation for OverKeys is available in the [docs](docs/index.md) fo Contributions are what make the open-source community such an amazing place to learn and collaborate. Any contributions to **OverKeys** are greatly appreciated. If you have suggestions for improvements, bug fixes, or new features, please feel free to open an issue or submit a pull request. For detailed contribution guidelines, see the [CONTRIBUTING.md](.github/CONTRIBUTING.md) file. -

(back to top)

## License diff --git a/docs/advanced/image-1.png b/docs/advanced/image-1.png new file mode 100644 index 00000000..3627fa85 Binary files /dev/null and b/docs/advanced/image-1.png differ diff --git a/docs/advanced/image.png b/docs/advanced/image.png new file mode 100644 index 00000000..dbf0bfc1 Binary files /dev/null and b/docs/advanced/image.png differ diff --git a/docs/advanced/key-modifiers.md b/docs/advanced/key-modifiers.md new file mode 100644 index 00000000..6924257f --- /dev/null +++ b/docs/advanced/key-modifiers.md @@ -0,0 +1,74 @@ +# Key Modifiers + +OverKeys supports richer key entries inside custom layouts so you can show a top label, track a different underlying key, and keep certain keys visually held. This is useful for layer-style layouts, modifier keys, and layouts that need two labels on the same key. This was greatly inspired from the key modifier support in [caksoylar's keymap-drawer](https://github.com/caksoylar/keymap-drawer). + +## Overview + +In `userLayouts`, each key can still be a plain string. You can also use an object with these fields: + +- `t`: Tracked key used for key press detection and shift mapping +- `h`: Top label shown near the top of the key +- `type`: Optional key state hint; use `held` to keep the key rendered as pressed + +Plain string entries continue to work exactly as before, so you can mix both formats within the same layout. + +![Space as tracked key and shift as the top label.](image.png) +*Space as tracked key and shift as the top label.* + +![NUM layer change key with type pressed](image-1.png) +*NUM layer change key with type pressed* + +## Example + +```jsonc +{ + "userLayouts": [ + { + "name": "Layer", + "keys": [ + ["A", { "h": "Shift", "t": "T", "type": "held" }], + ["B", "C"], + ], + }, + ], +} +``` + +In this example: + +- `A` is rendered as a normal key +- The second key displays `Shift` at the top and `T` as the tracked key +- `type: "held"` keeps the key styled like it is active, useful for modifier keys or layer keys that should stay visually active + +## Setup Instructions + +### Turning the setting on + +1. Open OverKeys +2. Right-click the OverKeys icon in the system tray +3. Select **Preferences** +4. Go to the **Advanced** tab +5. Toggle **Turn on advanced settings** +6. Toggle **Use user layouts** if you want the custom layout to be loaded + +### Using the Configuration File + +1. Right-click the OverKeys tray icon +2. Select **Preferences** +3. Go to the **Advanced** tab +4. Click **Open Config** +5. Edit the `userLayouts` array using the object form where needed +6. Save the file +7. Right-click the tray icon and click **Reload config** to apply changes + +## Behavior Notes + +- `t` controls the actual key OverKeys tracks for press state and alias/shift behavior +- `h` is only visual and can be omitted when you do not need a separate top label +- `held` is useful for modifier or layer keys that should stay visually active + +## Troubleshooting + +- If a modifier key does not display correctly, make sure the JSON object uses the exact field names `h`, `t`, and `type` +- If the tracked key is missing, OverKeys falls back to the top label or the object value when possible +- If the label looks cramped, adjust the text sizes in the [Text Settings](../user-guide/text.md) page diff --git a/docs/index.md b/docs/index.md index 0aecd187..a23e25a9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,45 +1,47 @@ -# OverKeys Documentation - -Welcome to the OverKeys documentation! This guide will help you get started with OverKeys, a customizable, open-source on-screen keyboard visualizer for alternative layouts. - -## Getting Started - -- [Installation](getting-started/installation.md) - Install and set up OverKeys on your Windows machine -- [Basic Usage](getting-started/basic-usage.md) - Learn how to use OverKeys's core features - -## User Guide - -- [Preferences](user-guide/preferences.md) - Configure OverKeys to match your needs -- [Learning Mode](user-guide/learning-mode.md) - Use the specialized color scheme for learning new layouts -- [Supported Layouts](../README.md#features) - View the list of built-in keyboard layouts - -## Advanced Features - -- [Custom Font](advanced/custom-font.md) - Use a system-installed font not found in the dropdown -- [Custom Layouts](advanced/custom-layouts.md) - Create and use your own keyboard layouts -- [Alternative Layouts](advanced/alternative-layouts.md) - Quickly switch between different layouts -- [6-Column Layouts](advanced/6-column-layouts.md) - Set up OverKeys for a 6-column split matrix keyboard -- [Layer Switching](advanced/layer-switching.md) - Switch between multiple custom keyboard layers with custom triggers -- [Ignored Keys](advanced/ignored-keys.md) - Prevent specific keys from triggering the keyboard overlay -- [Kanata Integration](advanced/kanata-integration.md) - Connect OverKeys with Kanata for advanced functionality -- [Custom Aliases](advanced/custom-aliases.md) - Define custom aliases for key combinations -- [Shift Mappings](advanced/shift-mappings.md) - Customize shift-key behavior for specific characters -- [Supported Keys](advanced/supported-keys.md) - Complete list of keys you can use in custom layouts -- [Locales](advanced/locales.md) - Add locale-specific keys in user configuration for key press recognition - -## Additional Resources - -- [Project README](../README.md) - Overview of the project -- [Changelog](../CHANGELOG.md) - Release history and features -- [GitHub Repository](https://github.com/conventoangelo/OverKeys) - Source code and issue tracking - -## Common Tasks - -- [Creating a custom layout](advanced/custom-layouts.md#creating-custom-layouts) -- [Setting up alternative layout switching](advanced/alternative-layouts.md) -- [Finding key names for custom layouts](advanced/supported-keys.md) -- [Troubleshooting custom layouts](advanced/custom-layouts.md#troubleshooting) - -## Support - -If you encounter any issues or have questions, please visit the [GitHub Discussions](https://github.com/conventoangelo/OverKeys/discussions) page or [report a bug](https://github.com/conventoangelo/OverKeys/issues/new?template=bug_report.md). +# OverKeys Documentation + +Welcome to the OverKeys documentation! This guide will help you get started with OverKeys, a customizable, open-source on-screen keyboard visualizer for alternative layouts. + +## Getting Started + +- [Installation](getting-started/installation.md) - Install and set up OverKeys on your Windows machine +- [Basic Usage](getting-started/basic-usage.md) - Learn how to use OverKeys's core features + +## User Guide + +- [Preferences](user-guide/preferences.md) - Configure OverKeys to match your needs +- [Text Settings](user-guide/text.md) - Tune key label sizing for long labels and top labels +- [Learning Mode](user-guide/learning-mode.md) - Use the specialized color scheme for learning new layouts +- [Supported Layouts](../README.md#features) - View the list of built-in keyboard layouts + +## Advanced Features + +- [Custom Font](advanced/custom-font.md) - Use a system-installed font not found in the dropdown +- [Custom Layouts](advanced/custom-layouts.md) - Create and use your own keyboard layouts +- [Key Modifiers](advanced/key-modifiers.md) - Add top labels and held-state keys to custom layouts +- [Alternative Layouts](advanced/alternative-layouts.md) - Quickly switch between different layouts +- [6-Column Layouts](advanced/6-column-layouts.md) - Set up OverKeys for a 6-column split matrix keyboard +- [Layer Switching](advanced/layer-switching.md) - Switch between multiple custom keyboard layers with custom triggers +- [Ignored Keys](advanced/ignored-keys.md) - Prevent specific keys from triggering the keyboard overlay +- [Kanata Integration](advanced/kanata-integration.md) - Connect OverKeys with Kanata for advanced functionality +- [Custom Aliases](advanced/custom-aliases.md) - Define custom aliases for key combinations +- [Shift Mappings](advanced/shift-mappings.md) - Customize shift-key behavior for specific characters +- [Supported Keys](advanced/supported-keys.md) - Complete list of keys you can use in custom layouts +- [Locales](advanced/locales.md) - Add locale-specific keys in user configuration for key press recognition + +## Additional Resources + +- [Project README](../README.md) - Overview of the project +- [Changelog](../CHANGELOG.md) - Release history and features +- [GitHub Repository](https://github.com/conventoangelo/OverKeys) - Source code and issue tracking + +## Common Tasks + +- [Creating a custom layout](advanced/custom-layouts.md#creating-custom-layouts) +- [Setting up alternative layout switching](advanced/alternative-layouts.md) +- [Finding key names for custom layouts](advanced/supported-keys.md) +- [Troubleshooting custom layouts](advanced/custom-layouts.md#troubleshooting) + +## Support + +If you encounter any issues or have questions, please visit the [GitHub Discussions](https://github.com/conventoangelo/OverKeys/discussions) page or [report a bug](https://github.com/conventoangelo/OverKeys/issues/new?template=bug_report.md). diff --git a/docs/user-guide/preferences.md b/docs/user-guide/preferences.md index 717862c5..8a5fc01e 100644 --- a/docs/user-guide/preferences.md +++ b/docs/user-guide/preferences.md @@ -1,133 +1,137 @@ -# Preferences - -OverKeys offers extensive customization options through its Preferences window. -OverKeys preferences are organized into ten tabs: - -1. **[General](#general-tab)** - Basic application settings -2. **[Keyboard](#keyboard-tab)** - Layout and key configuration -3. **[Text](#text-tab)** - Font and text styling options -4. **[Markers](#markers-tab)** - Tactile marker customization -5. **[Colors](#colors-tab)** - Visual color customization options -6. **[Animations](#animations-tab)** - Key animation settings -7. **[Hotkeys](#hotkeys-tab)** - Keyboard shortcuts for controlling OverKeys -8. **[Learn](./learning-mode.md)** - Add guide color to keys for learning proper touch typing -9. **[Advanced](#advanced-tab)** - Additional configuration options -10. **[About](#about-tab)** - Application information and version details - -## Accessing Preferences - -To open the Preferences window: - -1. Right-click the OverKeys icon in the system tray -2. Select **Preferences** from the context menu - -## General Tab - -The General tab contains basic configuration options: - -- **Open on system startup**: Start OverKeys automatically when your system boots -- **Auto-hide keyboard**: Automatically hide the keyboard after a period of inactivity -- **Auto-hide duration**: Set how many seconds of inactivity before hiding (0.5-5.0 seconds) -- **Opacity**: Control the overall transparency of the keyboard overlay (10-100%) -- **Layout**: Select the keyboard layout (QWERTY, etc.) - -## Keyboard Tab - -Configure the keyboard layout and physical appearance: - -- **Keymap style**: Choose between Staggered, Matrix, and Split Matrix (5-col) keymaps -- **Show top row**: Display the number row above the main keyboard -- **Show grave key**: Display the backtick/tilde key -- **Key size**: Adjust the size of individual keys -- **Key border radius**: Control how rounded the keys appear -- **Key border thickness**: Set the thickness of key borders -- **Key padding**: Set the spacing between keys -- **Space width**: Adjust the width of the spacebar -- **Split width**: Set the gap width for split keyboard layouts -- **Last row split width**: Set the gap width of the last row for split keyboard layouts -- **Key shadow blur radius**: Control the softness of key shadows -- **Key shadow offset X**: Adjust the horizontal shadow position -- **Key shadow offset Y**: Adjust the vertical shadow position - -## Text Tab - -Customize the text appearance on keys: - -- **Font family**: Choose the typeface used for key labels -- **Font weight**: Adjust how bold the text appears -- **Key font size**: Set the text size for regular keys -- **Space font size**: Set the text size for the spacebar - -## Markers Tab - -Configure the tactile markers on home row keys: - -- **Marker offset**: Adjust the positioning of tactile markers -- **Marker width**: Set the width of tactile markers -- **Marker height**: Set the height of tactile markers -- **Marker border radius**: Adjust the rounded corners of tactile markers - -## Colors Tab - -Controls the visual colors of the keyboard: - -- **Key (pressed)**: Set the color of keys when pressed -- **Key (not pressed)**: Set the color of keys when not pressed -- **Marker (pressed)**: Set the color of tactile markers when pressed -- **Marker (not pressed)**: Set the color of tactile markers when not pressed -- **Text (pressed)**: Set the text color when keys are pressed -- **Text (not pressed)**: Set the text color when keys are not pressed -- **Border (pressed)**: Set the color of borders around keys when pressed -- **Border (not pressed)**: Set the color of borders around keys when not pressed - -## Animations Tab - -Customize keyboard animation settings: - -- **Enable animations**: Turn key press animations on or off -- **Animation style**: Choose between Depress, Raise, Grow, or Shrink animations -- **Animation duration**: Set how long animations last (50-300ms) -- **Animation scale**: Adjust the intensity of animations (1.0-5.0x) - -## Hotkeys Tab - -Configure keyboard shortcuts for controlling OverKeys: - -- **Enable hotkeys**: Turn keyboard shortcuts on or off -- **Toggle Visibility**: Set a shortcut to force show/hide the keyboard (regardless if auto-hide is on/off) -- **Toggle Auto Hide**: Set a shortcut to toggle the auto-hide feature -- **Toggle Move**: Set a shortcut to toggle the ability to move the keyboard -- **Open Preferences**: Set a shortcut to quickly open the preferences window -- **Increase Opacity**: Set a shortcut to make the keyboard more opaque -- **Decrease Opacity**: Set a shortcut to make the keyboard more transparent - -## Learn Tab - -Configure learning mode settings to help with touch typing: - -- **Enable Learning Mode**: Color-code keys based on proper finger positions for touch typing -- **Finger color customization**: Set specific colors for each finger: - - Left hand: Pinky, Ring, Middle, and Index finger colors - - Right hand: Pinky, Ring, Middle, and Index finger colors -- **Reset color configuration**: Restore all finger colors to their default values - -## Advanced Tab - -Access additional configuration options: - -- **Turn on advanced settings**: Unlock additional functionalities and customization options -- **Use user layouts**: Use a custom keyboard layout and enable layers functionality -- **Show alternative layout**: Display another layout alongside the main one -- **Use custom font**: Enable using a custom font for key labels -- **Use 6-column layout**: Enable 6-column split matrix layout -- **Connect to Kanata**: Enable integration with Kanata keyboard software -- **Keyboard follows mouse**: EXPERIMENTAL - Makes the keyboard follow your mouse cursor across monitors -- **Open config file**: Access and modify the configuration file directly - -## About Tab - -The About tab provides information about the application: - -- **App version**: Displays the current version of OverKeys -- **Copyright information**: Shows attribution and ownership details -- **GitHub link**: Direct access to the OverKeys repository +# Preferences + +OverKeys offers extensive customization options through its Preferences window. +OverKeys preferences are organized into ten tabs: + +1. **[General](#general-tab)** - Basic application settings +2. **[Keyboard](#keyboard-tab)** - Layout and key configuration +3. **[Text](#text-tab)** - Font and text styling options +4. **[Markers](#markers-tab)** - Tactile marker customization +5. **[Colors](#colors-tab)** - Visual color customization options +6. **[Animations](#animations-tab)** - Key animation settings +7. **[Hotkeys](#hotkeys-tab)** - Keyboard shortcuts for controlling OverKeys +8. **[Learn](./learning-mode.md)** - Add guide color to keys for learning proper touch typing +9. **[Advanced](#advanced-tab)** - Additional configuration options +10. **[About](#about-tab)** - Application information and version details + +## Accessing Preferences + +To open the Preferences window: + +1. Right-click the OverKeys icon in the system tray +2. Select **Preferences** from the context menu + +## General Tab + +The General tab contains basic configuration options: + +- **Open on system startup**: Start OverKeys automatically when your system boots +- **Auto-hide keyboard**: Automatically hide the keyboard after a period of inactivity +- **Auto-hide duration**: Set how many seconds of inactivity before hiding (0.5-5.0 seconds) +- **Opacity**: Control the overall transparency of the keyboard overlay (10-100%) +- **Layout**: Select the keyboard layout (QWERTY, etc.) + +## Keyboard Tab + +Configure the keyboard layout and physical appearance: + +- **Keymap style**: Choose between Staggered, Matrix, and Split Matrix (5-col) keymaps +- **Show top row**: Display the number row above the main keyboard +- **Show grave key**: Display the backtick/tilde key +- **Key size**: Adjust the size of individual keys +- **Key border radius**: Control how rounded the keys appear +- **Key border thickness**: Set the thickness of key borders +- **Key padding**: Set the spacing between keys +- **Space width**: Adjust the width of the spacebar +- **Split width**: Set the gap width for split keyboard layouts +- **Last row split width**: Set the gap width of the last row for split keyboard layouts +- **Key shadow blur radius**: Control the softness of key shadows +- **Key shadow offset X**: Adjust the horizontal shadow position +- **Key shadow offset Y**: Adjust the vertical shadow position + +## Text Tab + +Customize the text appearance on keys: + +- **Font family**: Choose the typeface used for key labels +- **Font weight**: Adjust how bold the text appears +- **Key font size**: Set the text size for regular keys +- **Long key font size**: Set the text size for longer labels and alternate labels +- **Top label font size**: Set the text size for top labels used by key modifiers +- **Space font size**: Set the text size for the spacebar + +For a dedicated reference, see [Text Settings](./text.md). + +## Markers Tab + +Configure the tactile markers on home row keys: + +- **Marker offset**: Adjust the positioning of tactile markers +- **Marker width**: Set the width of tactile markers +- **Marker height**: Set the height of tactile markers +- **Marker border radius**: Adjust the rounded corners of tactile markers + +## Colors Tab + +Controls the visual colors of the keyboard: + +- **Key (pressed)**: Set the color of keys when pressed +- **Key (not pressed)**: Set the color of keys when not pressed +- **Marker (pressed)**: Set the color of tactile markers when pressed +- **Marker (not pressed)**: Set the color of tactile markers when not pressed +- **Text (pressed)**: Set the text color when keys are pressed +- **Text (not pressed)**: Set the text color when keys are not pressed +- **Border (pressed)**: Set the color of borders around keys when pressed +- **Border (not pressed)**: Set the color of borders around keys when not pressed + +## Animations Tab + +Customize keyboard animation settings: + +- **Enable animations**: Turn key press animations on or off +- **Animation style**: Choose between Depress, Raise, Grow, or Shrink animations +- **Animation duration**: Set how long animations last (50-300ms) +- **Animation scale**: Adjust the intensity of animations (1.0-5.0x) + +## Hotkeys Tab + +Configure keyboard shortcuts for controlling OverKeys: + +- **Enable hotkeys**: Turn keyboard shortcuts on or off +- **Toggle Visibility**: Set a shortcut to force show/hide the keyboard (regardless if auto-hide is on/off) +- **Toggle Auto Hide**: Set a shortcut to toggle the auto-hide feature +- **Toggle Move**: Set a shortcut to toggle the ability to move the keyboard +- **Open Preferences**: Set a shortcut to quickly open the preferences window +- **Increase Opacity**: Set a shortcut to make the keyboard more opaque +- **Decrease Opacity**: Set a shortcut to make the keyboard more transparent + +## Learn Tab + +Configure learning mode settings to help with touch typing: + +- **Enable Learning Mode**: Color-code keys based on proper finger positions for touch typing +- **Finger color customization**: Set specific colors for each finger: + - Left hand: Pinky, Ring, Middle, and Index finger colors + - Right hand: Pinky, Ring, Middle, and Index finger colors +- **Reset color configuration**: Restore all finger colors to their default values + +## Advanced Tab + +Access additional configuration options: + +- **Turn on advanced settings**: Unlock additional functionalities and customization options +- **Use user layouts**: Use a custom keyboard layout and enable layers functionality +- **Show alternative layout**: Display another layout alongside the main one +- **Use custom font**: Enable using a custom font for key labels +- **Use 6-column layout**: Enable 6-column split matrix layout +- **Connect to Kanata**: Enable integration with Kanata keyboard software +- **Keyboard follows mouse**: EXPERIMENTAL - Makes the keyboard follow your mouse cursor across monitors +- **Open config file**: Access and modify the configuration file directly + +## About Tab + +The About tab provides information about the application: + +- **App version**: Displays the current version of OverKeys +- **Copyright information**: Shows attribution and ownership details +- **GitHub link**: Direct access to the OverKeys repository diff --git a/docs/user-guide/text.md b/docs/user-guide/text.md new file mode 100644 index 00000000..4bdcbedf --- /dev/null +++ b/docs/user-guide/text.md @@ -0,0 +1,35 @@ +# Text Settings + +The Text tab controls how labels fit on keys. It is especially useful when you use long key names, alternative layout labels, or top labels from key modifiers. + +## Overview + +Open **Preferences** and switch to the **Text** tab to adjust the typography used by OverKeys. These settings affect the keyboard overlay immediately and are saved with your preferences. + +## Available Controls + +- **Font family**: Choose the font used for key labels +- **Font weight**: Adjust how bold key text appears +- **Key font size**: Set the size for short key labels +- **Long key font size**: Set the size for longer key labels and alternate labels +- **Top label font size**: Set the size for labels shown at the top of modifier-style keys +- **Space font size**: Set the size for the spacebar label + +## When To Adjust Them + +- Use a smaller **Long key font size** when your layout contains many labels such as `Backspace`, `Enter`, or custom layer names +- Use a smaller **Top label font size** when key modifiers need to show both a top label and a tracked key on the same keycap +- Keep **Space font size** close to the key font size so the spacebar matches the rest of the layout visually + +## Setup Instructions + +1. Open OverKeys +2. Right-click the OverKeys icon in the system tray +3. Select **Preferences** +4. Go to the **Text** tab +5. Adjust the sliders until the labels fit your layout cleanly + +## Related Docs + +- [Custom Font](../advanced/custom-font.md) +- [Key Modifiers](../advanced/key-modifiers.md) diff --git a/lib/models/keyboard_layouts.dart b/lib/models/keyboard_layouts.dart index 22e4716b..d945ae82 100644 --- a/lib/models/keyboard_layouts.dart +++ b/lib/models/keyboard_layouts.dart @@ -1,6 +1,22 @@ +class KeyboardKeySpec { + final String trackedKey; + final String? topLabel; + final String? type; + + const KeyboardKeySpec({ + required this.trackedKey, + this.topLabel, + this.type, + }); + + bool get isPressedType => type == 'held'; + bool get hasTopLabel => topLabel != null && topLabel!.isNotEmpty; +} + class KeyboardLayout { final String name; final List> keys; + final List>? keySpecs; final String? trigger; final String? type; final bool? foreign; @@ -9,10 +25,28 @@ class KeyboardLayout { const KeyboardLayout( {required this.name, required this.keys, + this.keySpecs, this.trigger, this.type, this.foreign, this.wide}); + + KeyboardKeySpec? keySpecAt(int rowIndex, int keyIndex) { + if (keySpecs == null || rowIndex >= keySpecs!.length) { + return null; + } + + final rowSpecs = keySpecs![rowIndex]; + if (keyIndex >= rowSpecs.length) { + return null; + } + + return rowSpecs[keyIndex]; + } + + bool isKeyPressedType(int rowIndex, int keyIndex) { + return keySpecAt(rowIndex, keyIndex)?.isPressedType ?? false; + } } const qwerty = KeyboardLayout( diff --git a/lib/models/user_config.dart b/lib/models/user_config.dart index 35875de2..770df221 100644 --- a/lib/models/user_config.dart +++ b/lib/models/user_config.dart @@ -29,11 +29,53 @@ class UserConfig { List userLayouts = []; if (json['userLayouts'] != null) { for (var userLayout in json['userLayouts']) { + final parsedRows = >[]; + final keySpecs = >[]; + + final rawRows = userLayout['keys'] as List; + for (int rowIndex = 0; rowIndex < rawRows.length; rowIndex++) { + final rawRow = rawRows[rowIndex] as List; + final parsedRow = []; + final parsedSpecs = []; + + for (int keyIndex = 0; keyIndex < rawRow.length; keyIndex++) { + final rawKey = rawRow[keyIndex]; + + if (rawKey is String) { + parsedRow.add(rawKey); + parsedSpecs.add(null); + continue; + } + + if (rawKey is Map) { + final topLabel = rawKey['h'] as String?; + final trackedKey = (rawKey['t'] as String?) ?? topLabel ?? ''; + final type = rawKey['type'] as String?; + + parsedRow.add(trackedKey); + parsedSpecs.add( + KeyboardKeySpec( + trackedKey: trackedKey, + topLabel: topLabel, + type: type, + ), + ); + continue; + } + + final fallbackKey = rawKey?.toString() ?? ''; + parsedRow.add(fallbackKey); + parsedSpecs.add(null); + } + + parsedRows.add(parsedRow); + keySpecs.add(parsedSpecs); + } + userLayouts.add(KeyboardLayout( name: userLayout['name'], - keys: List>.from( - userLayout['keys'].map((row) => List.from(row)), - ), + keys: parsedRows, + keySpecs: keySpecs, trigger: userLayout['trigger'], type: userLayout['type'], foreign: userLayout['foreign'], @@ -82,7 +124,24 @@ class UserConfig { ? userLayouts! .map((userLayout) => { 'name': userLayout.name, - 'keys': userLayout.keys, + 'keys': userLayout.keys.asMap().entries.map((rowEntry) { + return rowEntry.value.asMap().entries.map((keyEntry) { + final keySpec = userLayout.keySpecAt( + rowEntry.key, + keyEntry.key, + ); + + if (keySpec == null) { + return keyEntry.value; + } + + return { + if (keySpec.topLabel != null) 'h': keySpec.topLabel, + 't': keySpec.trackedKey, + if (keySpec.type != null) 'type': keySpec.type, + }; + }).toList(); + }).toList(), if (userLayout.trigger != null) 'trigger': userLayout.trigger, if (userLayout.type != null) 'type': userLayout.type, if (userLayout.foreign != null) 'foreign': userLayout.foreign, diff --git a/lib/providers/keyboard_provider.dart b/lib/providers/keyboard_provider.dart index 8bd39cc1..43b4191d 100644 --- a/lib/providers/keyboard_provider.dart +++ b/lib/providers/keyboard_provider.dart @@ -27,6 +27,8 @@ class KeyboardState { final String? initialFontFamily; final FontWeight fontWeight; final double keyFontSize; + final double longKeyFontSize; + final double topLabelFontSize; final double spaceFontSize; final double markerOffset; final double markerWidth; @@ -79,6 +81,8 @@ class KeyboardState { this.initialFontFamily, this.fontWeight = FontWeight.w500, this.keyFontSize = 22, + this.longKeyFontSize = 16, + this.topLabelFontSize = 13, this.spaceFontSize = 21, this.markerOffset = 10, this.markerWidth = 10, @@ -132,6 +136,8 @@ class KeyboardState { String? initialFontFamily, FontWeight? fontWeight, double? keyFontSize, + double? longKeyFontSize, + double? topLabelFontSize, double? spaceFontSize, double? markerOffset, double? markerWidth, @@ -184,6 +190,8 @@ class KeyboardState { initialFontFamily: initialFontFamily ?? this.initialFontFamily, fontWeight: fontWeight ?? this.fontWeight, keyFontSize: keyFontSize ?? this.keyFontSize, + longKeyFontSize: longKeyFontSize ?? this.longKeyFontSize, + topLabelFontSize: topLabelFontSize ?? this.topLabelFontSize, spaceFontSize: spaceFontSize ?? this.spaceFontSize, markerOffset: markerOffset ?? this.markerOffset, markerWidth: markerWidth ?? this.markerWidth, @@ -242,6 +250,8 @@ class KeyboardState { 'initialFontFamily': initialFontFamily, 'fontWeightIndex': fontWeight.value, 'keyFontSize': keyFontSize, + 'longKeyFontSize': longKeyFontSize, + 'topLabelFontSize': topLabelFontSize, 'spaceFontSize': spaceFontSize, 'markerOffset': markerOffset, 'markerWidth': markerWidth, @@ -337,6 +347,8 @@ class KeyboardState { initialFontFamily: json['initialFontFamily'] as String?, fontWeight: fontWeightFromValue(json['fontWeightIndex'] as int? ?? 500), keyFontSize: (json['keyFontSize'] as num?)?.toDouble() ?? 22, + longKeyFontSize: (json['longKeyFontSize'] as num?)?.toDouble() ?? 16, + topLabelFontSize: (json['topLabelFontSize'] as num?)?.toDouble() ?? 13, spaceFontSize: (json['spaceFontSize'] as num?)?.toDouble() ?? 21, markerOffset: (json['markerOffset'] as num?)?.toDouble() ?? 10, markerWidth: (json['markerWidth'] as num?)?.toDouble() ?? 10, @@ -474,6 +486,14 @@ class KeyboardNotifier extends _$KeyboardNotifier { state = state.copyWith(keyFontSize: size); } + void updateLongKeyFontSize(double size) { + state = state.copyWith(longKeyFontSize: size); + } + + void updateTopLabelFontSize(double size) { + state = state.copyWith(topLabelFontSize: size); + } + void updateSpaceFontSize(double size) { state = state.copyWith(spaceFontSize: size); } diff --git a/lib/screens/keyboard_screen.dart b/lib/screens/keyboard_screen.dart index 521db391..20ed3d4b 100644 --- a/lib/screens/keyboard_screen.dart +++ b/lib/screens/keyboard_screen.dart @@ -162,10 +162,14 @@ class KeyboardScreen extends ConsumerWidget { required PreferencesState prefsState, KeyboardLayout? altLayout, }) { + final keySpec = keyboardState.layout.keySpecAt(rowIndex, keyIndex); key = _getShiftedKey(key, keyboardState, prefsState); + final layoutKeyIndex = keyIndex; + final trackedKey = keySpec?.trackedKey ?? key; + // For foreign layouts, map to Foreign QWERTY if the key exists, otherwise use the key itself - String realKey = key; - if (keyboardState.layout.foreign ?? false) { + String realKey = trackedKey; + if (keySpec == null && (keyboardState.layout.foreign ?? false)) { if (rowIndex < qwertyForeign.keys.length && keyIndex < qwertyForeign.keys[rowIndex].length) { realKey = qwertyForeign.keys[rowIndex][keyIndex]; @@ -173,7 +177,9 @@ class KeyboardScreen extends ConsumerWidget { } String keyStateKey = Mappings.getKeyForSymbol(realKey); - bool isPressed = keyboardState.keyPressStates[keyStateKey] ?? false; + bool isPressed = + keyboardState.layout.isKeyPressedType(rowIndex, layoutKeyIndex) || + (keyboardState.keyPressStates[keyStateKey] ?? false); // Adjust key index for 6-column layouts (extra backtick column shifts indices by 1) keyIndex -= (prefsState.use6ColLayout && prefsState.advancedSettingsEnabled) @@ -203,6 +209,12 @@ class KeyboardScreen extends ConsumerWidget { : (isLastKeyFirstRow ? keyboardState.keySize * 2 + keyboardState.keyPadding / 2 : keyboardState.keySize); + final hasTopLabel = keySpec?.hasTopLabel ?? false; + final keyFontSize = key.length > 2 + ? keyboardState.longKeyFontSize + : keyboardState.keyFontSize; + final topLabel = keySpec?.topLabel; + final topLabelFontSize = keyboardState.topLabelFontSize; Widget keyWidget = Padding( padding: EdgeInsets.all(keyboardState.keyPadding), @@ -247,49 +259,78 @@ class KeyboardScreen extends ConsumerWidget { ), ), ) - : altLayout != null + : (altLayout != null || hasTopLabel) ? Stack( children: [ - // Primary layout key (top left) - Positioned( - top: 4, - left: 8, - child: Text( - key, - textAlign: TextAlign.left, - style: TextStyle( - color: textColor, - fontSize: key.length > 2 - ? keyboardState.keyFontSize * 0.6 - : keyboardState.keyFontSize * 0.85, - fontWeight: keyboardState.fontWeight, + if (altLayout != null) + Positioned( + top: 4, + left: 8, + child: Text( + key, + textAlign: TextAlign.left, + style: TextStyle( + color: textColor, + fontSize: key.length > 2 + ? keyboardState.longKeyFontSize + : keyboardState.keyFontSize, + fontWeight: keyboardState.fontWeight, + ), + ), + ) + else + Center( + child: Text( + key, + textAlign: TextAlign.center, + style: TextStyle( + color: textColor, + fontSize: keyFontSize, + fontWeight: keyboardState.fontWeight, + ), ), ), - ), - // Alt layout key (bottom right) - Positioned( - bottom: 4, - right: 8, - child: Text( - _getAltLayoutKey(rowIndex, keyIndex, keyboardState, - prefsState, altLayout), - textAlign: TextAlign.right, - style: TextStyle( - color: textColor, - fontSize: _getAltLayoutKey( - rowIndex, - keyIndex, - keyboardState, - prefsState, - altLayout) - .length > - 2 - ? keyboardState.keyFontSize * 0.6 - : keyboardState.keyFontSize * 0.85, - fontWeight: keyboardState.fontWeight, + if (hasTopLabel) + Positioned( + top: 2, + left: 0, + right: 0, + child: Center( + child: Text( + topLabel!, + textAlign: TextAlign.center, + style: TextStyle( + color: textColor, + fontSize: topLabelFontSize, + fontWeight: keyboardState.fontWeight, + ), + ), + ), + ), + if (altLayout != null) + Positioned( + bottom: 4, + right: 8, + child: Text( + _getAltLayoutKey(rowIndex, keyIndex, keyboardState, + prefsState, altLayout), + textAlign: TextAlign.right, + style: TextStyle( + color: textColor, + fontSize: _getAltLayoutKey( + rowIndex, + keyIndex, + keyboardState, + prefsState, + altLayout) + .length > + 2 + ? keyboardState.longKeyFontSize + : keyboardState.keyFontSize, + fontWeight: keyboardState.fontWeight, + ), ), ), - ), ], ) : Center( @@ -298,9 +339,7 @@ class KeyboardScreen extends ConsumerWidget { textAlign: TextAlign.center, style: TextStyle( color: textColor, - fontSize: key.length > 2 - ? keyboardState.keyFontSize * 0.7 - : keyboardState.keyFontSize, + fontSize: keyFontSize, fontWeight: keyboardState.fontWeight, ), ), @@ -489,7 +528,8 @@ class KeyboardScreen extends ConsumerWidget { if (keyIndex >= altRow.length) { return ""; } - String altKey = altRow[keyIndex]; + final altSpec = altLayout.keySpecAt(rowIndex, keyIndex); + String altKey = altSpec?.trackedKey ?? altRow[keyIndex]; altKey = _getShiftedKey(altKey, keyboardState, prefsState); return altKey; } diff --git a/lib/services/method_call_handler.dart b/lib/services/method_call_handler.dart index 2f74cc6e..4062afa9 100644 --- a/lib/services/method_call_handler.dart +++ b/lib/services/method_call_handler.dart @@ -171,6 +171,14 @@ class MethodCallHandler { final keyFontSize = _safeArgument(call.arguments, 28.0); keyboardNotifier.updateKeyFontSize(keyFontSize); + case 'updateLongKeyFontSize': + final longKeyFontSize = _safeArgument(call.arguments, 18.0); + keyboardNotifier.updateLongKeyFontSize(longKeyFontSize); + + case 'updateTopLabelFontSize': + final topLabelFontSize = _safeArgument(call.arguments, 12.0); + keyboardNotifier.updateTopLabelFontSize(topLabelFontSize); + case 'updateSpaceFontSize': final spaceFontSize = _safeArgument(call.arguments, 20.0); keyboardNotifier.updateSpaceFontSize(spaceFontSize); diff --git a/lib/widgets/options/base_option.dart b/lib/widgets/options/base_option.dart index 0ccea05b..d508777a 100644 --- a/lib/widgets/options/base_option.dart +++ b/lib/widgets/options/base_option.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; class OptionContainer extends StatelessWidget { @@ -8,7 +10,7 @@ class OptionContainer extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return Container( + final container = Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -18,5 +20,17 @@ class OptionContainer extends StatelessWidget { ), child: child, ); + + // Disable splash effects during widget tests to avoid engine shader + // runtime issues (ink_sparkle.frag mismatches). Only override in test + // environment so production behavior isn't changed. + if (Platform.environment.containsKey('FLUTTER_TEST')) { + return Theme( + data: Theme.of(context).copyWith(splashFactory: NoSplash.splashFactory), + child: container, + ); + } + + return container; } } diff --git a/lib/widgets/tabs/text_tab.dart b/lib/widgets/tabs/text_tab.dart index 52a5a46a..f0d3f30f 100644 --- a/lib/widgets/tabs/text_tab.dart +++ b/lib/widgets/tabs/text_tab.dart @@ -18,6 +18,8 @@ class TextTab extends ConsumerStatefulWidget { class _TextTabState extends ConsumerState { late double _localKeyFontSize; + late double _localLongKeyFontSize; + late double _localTopLabelFontSize; late double _localSpaceFontSize; @override @@ -26,6 +28,8 @@ class _TextTabState extends ConsumerState { // Initialize with current provider values final keyboardState = ref.read(keyboardProvider); _localKeyFontSize = keyboardState.keyFontSize; + _localLongKeyFontSize = keyboardState.longKeyFontSize; + _localTopLabelFontSize = keyboardState.topLabelFontSize; _localSpaceFontSize = keyboardState.spaceFontSize; } @@ -39,6 +43,12 @@ class _TextTabState extends ConsumerState { if (_localKeyFontSize != next.keyFontSize) { setState(() => _localKeyFontSize = next.keyFontSize); } + if (_localLongKeyFontSize != next.longKeyFontSize) { + setState(() => _localLongKeyFontSize = next.longKeyFontSize); + } + if (_localTopLabelFontSize != next.topLabelFontSize) { + setState(() => _localTopLabelFontSize = next.topLabelFontSize); + } if (_localSpaceFontSize != next.spaceFontSize) { setState(() => _localSpaceFontSize = next.spaceFontSize); } @@ -140,6 +150,34 @@ class _TextTabState extends ConsumerState { widget.onUpdateMainWindow('updateKeyFontSize', value); }, ), + SliderOption( + label: 'Long key font size', + value: _localLongKeyFontSize, + min: 8, + max: 24, + divisions: 32, + onChanged: (value) { + setState(() => _localLongKeyFontSize = value); + }, + onChangeEnd: (value) { + ref.read(keyboardProvider.notifier).updateLongKeyFontSize(value); + widget.onUpdateMainWindow('updateLongKeyFontSize', value); + }, + ), + SliderOption( + label: 'Top label font size', + value: _localTopLabelFontSize, + min: 6, + max: 20, + divisions: 28, + onChanged: (value) { + setState(() => _localTopLabelFontSize = value); + }, + onChangeEnd: (value) { + ref.read(keyboardProvider.notifier).updateTopLabelFontSize(value); + widget.onUpdateMainWindow('updateTopLabelFontSize', value); + }, + ), SliderOption( label: 'Space font size', value: _localSpaceFontSize, diff --git a/pubspec.lock b/pubspec.lock index 40e41891..937ae5ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -801,10 +801,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "93ae5884a9df5d3bb696825bceb3a17590754548b5d740eba51500afc8d088f5" + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" url: "https://pub.dev" source: hosted - version: "2.4.26" + version: "2.4.27" shared_preferences_foundation: dependency: transitive description: @@ -1086,10 +1086,10 @@ packages: dependency: transitive description: name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.6.0" vector_math: dependency: transitive description: diff --git a/test/providers/keyboard_provider_test.dart b/test/providers/keyboard_provider_test.dart index 3cfd3873..569fac07 100644 --- a/test/providers/keyboard_provider_test.dart +++ b/test/providers/keyboard_provider_test.dart @@ -15,6 +15,9 @@ void main() { expect(state.showTopRow, false); expect(state.showGraveKey, false); expect(state.keySize, 52); + expect(state.keyFontSize, 22); + expect(state.longKeyFontSize, 16); + expect(state.topLabelFontSize, 13); expect(state.keyBorderRadius, 14); expect(state.animationEnabled, true); expect(state.learningModeEnabled, false); @@ -167,6 +170,9 @@ void main() { layout: qwerty, keymapStyle: 'Matrix', keySize: 60, + keyFontSize: 24, + longKeyFontSize: 17, + topLabelFontSize: 12, animationEnabled: false, ); @@ -175,6 +181,9 @@ void main() { expect(json['layoutName'], 'QWERTY'); expect(json['keymapStyle'], 'Matrix'); expect(json['keySize'], 60); + expect(json['keyFontSize'], 24); + expect(json['longKeyFontSize'], 17); + expect(json['topLabelFontSize'], 12); expect(json['animationEnabled'], false); }); @@ -183,6 +192,9 @@ void main() { 'layoutName': 'Colemak', 'keymapStyle': 'Split Matrix', 'keySize': 65.0, + 'keyFontSize': 25.0, + 'longKeyFontSize': 18.0, + 'topLabelFontSize': 11.0, 'animationEnabled': true, 'learningModeEnabled': true, }; @@ -192,6 +204,9 @@ void main() { expect(state.layout.name, 'Colemak'); expect(state.keymapStyle, 'Split Matrix'); expect(state.keySize, 65); + expect(state.keyFontSize, 25); + expect(state.longKeyFontSize, 18); + expect(state.topLabelFontSize, 11); expect(state.animationEnabled, true); expect(state.learningModeEnabled, true); }); diff --git a/test/user_config_test.dart b/test/user_config_test.dart index 7650b74c..30209567 100644 --- a/test/user_config_test.dart +++ b/test/user_config_test.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; +import 'package:overkeys/models/keyboard_layouts.dart'; import 'package:overkeys/models/user_config.dart'; void main() { @@ -34,4 +35,53 @@ void main() { expect(json['ignoredKeys'], isNotNull); expect(json['ignoredKeys'], ["X", "Y"]); }); + + test('UserConfig parses typed key objects with top labels', () { + final jsonString = ''' + { + "userLayouts": [ + { + "name": "Layer", + "keys": [ + ["A", {"h": "Shift", "t": "T", "type": "held"}] + ] + } + ] + } + '''; + final json = jsonDecode(jsonString); + final config = UserConfig.fromJson(json); + + final layout = config.userLayouts!.first; + expect(layout.keys[0][1], 'T'); + expect(layout.keySpecAt(0, 1), isNotNull); + expect(layout.keySpecAt(0, 1)!.topLabel, 'Shift'); + expect(layout.keySpecAt(0, 1)!.trackedKey, 'T'); + expect(layout.isKeyPressedType(0, 1), isTrue); + }); + + test('UserConfig toJson preserves typed key objects', () { + const layout = KeyboardLayout( + name: 'Layer', + keys: [ + ['A', 'T'] + ], + keySpecs: [ + [ + null, + KeyboardKeySpec(trackedKey: 'T', topLabel: 'Shift', type: 'held') + ] + ], + ); + final config = UserConfig(userLayouts: [layout]); + final json = config.toJson(); + + final userLayouts = json['userLayouts'] as List; + final keys = userLayouts.first['keys'] as List; + final typedKey = keys[0][1] as Map; + + expect(typedKey['h'], 'Shift'); + expect(typedKey['t'], 'T'); + expect(typedKey['type'], 'held'); + }); }