diff --git a/.github/README.md b/.github/README.md index ccdb41923..b142ef0b6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -160,4 +160,4 @@ A few features in various points in the horizon are: ## Backports? This mod is only and will only be available for **1.19.4** and above, this is because in 1.19.4, Mojang -introduced arrow key navigation which was easily ported to controller, below 1.19.4, this is not possible. +introduced arrow keyFunction navigation which was easily ported to controller, below 1.19.4, this is not possible. diff --git a/docs/architecture/mod-comparison.mdx b/docs/architecture/mod-comparison.mdx index af494e4a4..98ee5b561 100644 --- a/docs/architecture/mod-comparison.mdx +++ b/docs/architecture/mod-comparison.mdx @@ -13,7 +13,7 @@ exhaustive, and there are many more features that are not listed here.** | **Open source** | ✅ Yes | ✅ Yes | ✅ Yes | ⛔ No | | **Library used** | SDL 3.x / GLFW | GLFW | SDL 2.x / GLFW | | | **Custom Screen Compatibility** | Convenient APIs to hook into controller support directly from `Screen` implementation. | No API. Sometimes necessary to mixin into Midnight Controls and edge-case code required. | ⛔ | ⛔ | -| **Screen Navigation** | 4-axis navigation, emulating arrow key navigation with optional cursor emulation | 4-axis navigation, emulating arrow key navigation | Cursor emulation only. | 2-axis tab-key emulation | +| **Screen Navigation** | 4-axis navigation, emulating arrow keyFunction navigation with optional cursor emulation | 4-axis navigation, emulating arrow keyFunction navigation | Cursor emulation only. | 2-axis tab-keyFunction emulation | | **Controller rumble** | ✅ | ⛔ | ✅ | ⛔ | | **In-game button guide** | ✅ Extensible by 3rd party mods | Hardcoded buttons and positions | Hardcoded buttons and positions | ⛔ | | **Input latency** | 20Hz - follows Minecraft's game loop rate | 1000Hz for axes, 20Hz for buttons | Unknown | Unknown | diff --git a/docs/reference/_meta.json b/docs/reference/_meta.json index e1bc71e0e..fcee6fc8e 100644 --- a/docs/reference/_meta.json +++ b/docs/reference/_meta.json @@ -1,5 +1,5 @@ { "builtin-bindings.mdx": "Built-in bindings", "builtin-inputs.mdx": "Built-in inputs", - "builtin-controller-namespaces": "Built-in controller namespaces", + "builtin-controller-namespaces.mdx": "Built-in controller namespaces" } diff --git a/docs/reference/builtin-bindings.mdx b/docs/reference/builtin-bindings.mdx index fa85c8bcf..feeb322ee 100644 --- a/docs/reference/builtin-bindings.mdx +++ b/docs/reference/builtin-bindings.mdx @@ -2,105 +2,125 @@ title: Built-in Bindings --- -This is a list of all Controlify's built-in bindings. +This is a list of all Controlify's built-in bindings with brief descriptions and availability. These are the entries on the 'Controls' page of controller settings. ### Movement -- `controlify:walk_forward` -- `controlify:walk_backward` -- `controlify:strafe_left` -- `controlify:strafe_right` -- `controlify:look_up` -- `controlify:look_down` -- `controlify:look_left` -- `controlify:look_right` -- `controlify:gyro_button` -- `controlify:jump` -- `controlify:sprint` -- `controlify:sneak` + +| ID | Description | Availability | +|----------------------------|----------------------------|----------------------------------| +| `controlify:walk_forward` | Move forward | | +| `controlify:walk_backward` | Move backward | | +| `controlify:strafe_left` | Strafe left | | +| `controlify:strafe_right` | Strafe right | | +| `controlify:look_up` | Look up | | +| `controlify:look_down` | Look down | | +| `controlify:look_left` | Look left | | +| `controlify:look_right` | Look right | | +| `controlify:gyro_button` | Hold to enable gyro aiming | Requires gyro-capable controller | +| `controlify:jump` | Jump | | +| `controlify:sprint` | Sprint | | +| `controlify:sneak` | Sneak / crouch | | ### Gameplay -- `controlify:attack` (equivalent to left-click) -- `controlify:use` (equivalent to right-click) -- `controlify:drop` -- `controlify:drop_stack` -- `controlify:pause` -- `controlify:change_perspective` -- `controlify:swap_hands` -- `controlify:next_slot` (hotbar) -- `controlify:prev_slot` (hotbar) -- `controlify:hotbar_item_select_radial` -- `controlify:game_mode_switcher` + +| ID | Description | Availability | +|----------------------------------------|--------------------------------|--------------| +| `controlify:attack` | Primary action (left-click) | | +| `controlify:use` | Secondary action (right-click) | | +| `controlify:drop` | Drop selected item | | +| `controlify:drop_stack` | Drop entire stack | | +| `controlify:pause` | Pause / open menu | | +| `controlify:change_perspective` | Cycle camera perspective | | +| `controlify:swap_hands` | Swap main/off-hand items | | +| `controlify:next_slot` | Select next hotbar slot | | +| `controlify:prev_slot` | Select previous hotbar slot | | +| `controlify:hotbar_item_select_radial` | Open hotbar selection radial | | +| `controlify:game_mode_switcher` | Open game mode switcher | | ### Inventory -- `controlify:inventory` -- `controlify:inv_select` -- `controlify:inv_quick_move` -- `controlify:inv_take_half` -- `controlify:drop_inventory` -- `controlify:bundle_navi_up` -- `controlify:bundle_navi_down` -- `controlify:bundle_navi_left` -- `controlify:bundle_navi_right` + +| ID | Description | Availability | +|-----------------------------|----------------------------|--------------| +| `controlify:inventory` | Open inventory | | +| `controlify:inv_select` | Select slot / pick up item | | +| `controlify:inv_quick_move` | Quick-move (shift-click) | | +| `controlify:inv_take_half` | Take half from stack | | +| `controlify:drop_inventory` | Drop from inventory | | ### Creative -- `controlify:pick_block` -- `controlify:pick_block_nbt` -- `controlify:hotbar_load_radial` -- `controlify:hotbar_save_radial` + +| ID | Description | Availability | +|---------------------------------|----------------------------|---------------| +| `controlify:pick_block` | Pick block under crosshair | | +| `controlify:pick_block_nbt` | Pick block with NBT | Creative only | +| `controlify:hotbar_load_radial` | Load saved hotbar radial | Creative only | +| `controlify:hotbar_save_radial` | Save hotbar radial | Creative only | ### Misc -- `controlify:open_chat` -- `controlify:toggle_hud_visibility` -- `controlify:show_player_list` -- `controlify:take_screenshot` -- `controlify:debug_radial` -- `controlify:toggle_debug_menu` -- `controlify:toggle_debug_menu_fps` -- `controlify:toggle_debug_menu_net` (>= 1.20.3 targets only) -- `controlify:toggle_debug_menu_prof` (>= 1.20.3 targets only) -- `controlify:toggle_debug_menu_charts` (< 1.20.3 targets only) + +| ID | Description | Availability | +|-------------------------------------|-------------------------------|----------------------| +| `controlify:open_chat` | Open chat | | +| `controlify:toggle_hud_visibility` | Toggle HUD visibility | | +| `controlify:show_player_list` | Show player list | | +| `controlify:take_screenshot` | Take screenshot | | +| `controlify:debug_radial` | Open debug radial | | +| `controlify:toggle_debug_menu` | Toggle debug screen | | +| `controlify:toggle_debug_menu_fps` | Toggle debug (FPS) panel | | +| `controlify:toggle_debug_menu_net` | Toggle debug (Network) panel | 1.20.3+ targets only | +| `controlify:toggle_debug_menu_prof` | Toggle debug (Profiler) panel | 1.20.3+ targets only | ### GUI -- `controlify:gui_press` -- `controlify:gui_back` -- `controlify:gui_next_tab` -- `controlify:gui_prev_tab` -- `controlify:gui_abstract_action_1` -- `controlify:gui_abstract_action_2` -- `controlify:gui_abstract_action_3` -- `controlify:gui_navi_up` -- `controlify:gui_navi_down` -- `controlify:gui_navi_left` -- `controlify:gui_navi_right` -- `controlify:cycle_opt_forward` -- `controlify:cycle_opt_backward` + +| ID | Description | Availability | +|---------------------------------------|----------------------------------|--------------| +| `controlify:gui_press` | Activate / press focused control | | +| `controlify:gui_back` | Go back / close | | +| `controlify:gui_next_tab` | Next tab / page | | +| `controlify:gui_prev_tab` | Previous tab / page | | +| `controlify:gui_abstract_action_1` | Context action 1 | | +| `controlify:gui_abstract_action_2` | Context action 2 | | +| `controlify:gui_abstract_action_3` | Context action 3 | | +| `controlify:gui_navi_up` | Navigate focus up | | +| `controlify:gui_navi_down` | Navigate focus down | | +| `controlify:gui_navi_left` | Navigate focus left | | +| `controlify:gui_navi_right` | Navigate focus right | | +| `controlify:gui_secondary_navi_up` | Navigate secondary area up | | +| `controlify:gui_secondary_navi_down` | Navigate secondary area down | | +| `controlify:gui_secondary_navi_left` | Navigate secondary area left | | +| `controlify:gui_secondary_navi_right` | Navigate secondary area right | | ### Radial Menu -- `controlify:radial_menu` -- `controlify:radial_axis_up` -- `controlify:radial_axis_down` -- `controlify:radial_axis_left` -- `controlify:radial_axis_right` + +| ID | Description | Availability | +|--------------------------------|-----------------------------|--------------| +| `controlify:radial_menu` | Open the radial menu | | +| `controlify:radial_axis_up` | Select radial segment up | | +| `controlify:radial_axis_down` | Select radial segment down | | +| `controlify:radial_axis_left` | Select radial segment left | | +| `controlify:radial_axis_right` | Select radial segment right | | ### Virtual Mouse -- `controlify:vmouse_move_up` -- `controlify:vmouse_move_down` -- `controlify:vmouse_move_left` -- `controlify:vmouse_move_right` -- `controlify:vmouse_snap_up` -- `controlify:vmouse_snap_down` -- `controlify:vmouse_snap_left` -- `controlify:vmouse_snap_right` -- `controlify:vmouse_lclick` -- `controlify:vmouse_rclick` -- `controlify:vmouse_shift_click` -- `controlify:vmouse_scroll_down` -- `controlify:vmouse_scroll_up` -- `controlify:vmouse_shift` -- `controlify:vmouse_page_next` -- `controlify:vmouse_page_prev` -- `controlify:vmouse_page_down` -- `controlify:vmouse_page_up` -- `controlify:vmouse_toggle` +| ID | Description | Availability | +|---------------------------------|-------------------------------|--------------| +| `controlify:vmouse_move_up` | Move cursor up | | +| `controlify:vmouse_move_down` | Move cursor down | | +| `controlify:vmouse_move_left` | Move cursor left | | +| `controlify:vmouse_move_right` | Move cursor right | | +| `controlify:vmouse_snap_up` | Snap to nearest element up | | +| `controlify:vmouse_snap_down` | Snap to nearest element down | | +| `controlify:vmouse_snap_left` | Snap to nearest element left | | +| `controlify:vmouse_snap_right` | Snap to nearest element right | | +| `controlify:vmouse_lclick` | Left-click | | +| `controlify:vmouse_rclick` | Right-click | | +| `controlify:vmouse_shift_click` | Shift-click | | +| `controlify:vmouse_scroll_down` | Scroll down | | +| `controlify:vmouse_scroll_up` | Scroll up | | +| `controlify:vmouse_shift` | Hold Shift modifier | | +| `controlify:vmouse_page_next` | Page next | | +| `controlify:vmouse_page_prev` | Page previous | | +| `controlify:vmouse_page_down` | Page down | | +| `controlify:vmouse_page_up` | Page up | | +| `controlify:vmouse_toggle` | Toggle virtual mouse | | diff --git a/docs/resource-packs/_meta.json b/docs/resource-packs/_meta.json index 760d2aa64..88c3d21ff 100644 --- a/docs/resource-packs/_meta.json +++ b/docs/resource-packs/_meta.json @@ -2,5 +2,6 @@ "custom-controller-identification.mdx": "Custom Controller Identification", "default-binds.mdx": "Default Binds", "input-glyphs.mdx": "Input Glyphs", - "guides.mdx": "Button Guides" + "guides.mdx": "Button Guides", + "keyboard-layouts.mdx": "Keyboard Layouts" } diff --git a/docs/resource-packs/guides.mdx b/docs/resource-packs/guides.mdx index 0e1a80fc2..f8c7dfe58 100644 --- a/docs/resource-packs/guides.mdx +++ b/docs/resource-packs/guides.mdx @@ -59,7 +59,7 @@ When the _rule_ applies, it shows the text "Jump" and the glyph for the `control As well as literal text such as `"Jump"`, Controlify supports the Minecraft [text component format](https://minecraft.wiki/w/Text_component_format) which allows you to use translations and styling. -For example, you can use `"then": {"translate": "mypack.jump"}` to use a translation key sourced from your pack's language files. +For example, you can use `"then": {"translate": "mypack.jump"}` to use a translation keyFunction sourced from your pack's language files. A rule can be displayed either on the `left` or `right` side of the screen, this is defined by the `"where"` field. @@ -134,10 +134,10 @@ These facts are available in all domains. | `controlify:in_water` | When the player is touching water. | | `controlify:under_water` | When the player has their eyes underwater. | | `controlify:in_lava` | When the player is touching lava. | -| `controlify:sneaking` | When the player is attempting to sneak (pressing the sneak key, or it is toggled on). | +| `controlify:sneaking` | When the player is attempting to sneak (pressing the sneak keyFunction, or it is toggled on). | | `controlify:is_toggle_sneak` | When the player is using toggle sneak (does not mean it is currently toggled on). | | `controlify:is_toggle_sprint` | When the player is using toggle sprint (does not mean it is currently toggled on). | -| `controlify:sprinting` | When the player is attempting to sprint (pressing the sprint key, or it is toggled on). | +| `controlify:sprinting` | When the player is attempting to sprint (pressing the sprint keyFunction, or it is toggled on). | | `controlify:input_moving` | When the player is applying movement input—even if the player is not physically moving, if they're trying to, this fact goes. | | `controlify:is_spectator` | When the player is in spectator mode. | | `controlify:is_creative` | When the player is in creative mode. | diff --git a/docs/resource-packs/keyboard-layouts.mdx b/docs/resource-packs/keyboard-layouts.mdx new file mode 100644 index 000000000..339dd5184 --- /dev/null +++ b/docs/resource-packs/keyboard-layouts.mdx @@ -0,0 +1,183 @@ +--- +title: Keyboard Layouts +--- + +Design, data format, and examples for Controlify's on‑screen keyboard layouts. + +## What is it? +A keyboard layout is a data file that describes the rows and keys shown by the on‑screen keyboard. Layouts are column‑aligned grids: each row’s key widths must sum to the same total “unit width.” Keys have an optional shifted function to support Shift and Caps‑style behavior. + +Layouts are discovered from resource packs and selected per language with a fallback to en_us. + +- Location: `assets//keyboard_layout//.json` +- Resolved id: `:` +- Language fallback: if `.json` is missing, `en_us.json` is used +- Built‑ins: `controlify:full`, `controlify:simple`, `controlify:server_ip` (see KeyboardLayouts) +- Fallback: if a requested layout can’t be found/parsed, an internal QWERTY fallback is used + + +Row widths are validated. If any row’s key widths don’t add up to the layout width, the file won’t load. + + +## File format +Top level +- `width` (number ≥ 1): the unit width of the keyboard; every row must sum to this +- `keys` (array of rows): each row is an array of Key entries; rows are rendered top‑to‑bottom + +A Key can be specified in multiple forms: +- Short: a single KeyFunction (e.g. a string like "a"), meaning regular=shifted=createShifted() +- Pair: `[regular, shifted]` array +- Object: `{ regular, shifted?, width?, shortcut?, identifier? }` + +Key object fields +- `regular` (KeyFunction): action when not shifted +- `shifted` (KeyFunction, optional): action when shifted; default is `regular.createShifted()` (may return itself if not supported) +- `width` (number ≥ 0.1, default 1): unit width of this key +- `shortcut` (ResourceLocation, optional): binding id whose glyph is shown on the key and which can trigger this key directly (see [Built-in Bindings](../reference/builtin-bindings)) +- `identifier` (string, optional): stable id used to preserve focus when switching layouts + +### KeyFunction types +You can mix any of these in a row. + +#### String insert +A KeyFunction that inserts a string of characters. It can be specified in two forms: + +1. Short form: a JSON string value (e.g. `"a"`) +2. Object form: `{ chars: string, display_name: Component? }` + +Shifted behavior: by default, the shifted variant uses `toUpperCase()` of `chars` unless you provide an explicit `shifted` KeyFunction. + +#### Key codes +A KeyFunction that sends one or more key codes ([GLFW/Minecraft `InputConstants` values](https://www.glfw.org/docs/latest/group__keys.html)). +A key code is a numeric identifier for a key on a keyboard, such as `GLFW_KEY_A (65)` or `GLFW_KEY_ENTER (257)`. +They represent a physical key on your keyboard, rather than a printable character. + + +Common key codes used in keyboards can be replaced with [special actions](#special-actions) for better readability and +localisation benefits. + + +It can be specified in two forms: + +1. Short form: a JSON array of integer key codes (e.g. `[257, 259]`) +2. Object form: a JSON array of key code objects, each with: + - `keycode` (integer): the key code + - `scancode` (integer, optional, default 0): the physical scancode (if applicable) + - `modifier` (integer, optional, default 0): [modifier bitflags](https://www.glfw.org/docs/latest/group__mods.html) (e.g. `GLFW_MOD_SHIFT`) + +There is no default shifted variant for key codes; if you need a shifted version, provide it explicitly using the `shifted` KeyFunction. + +```json +{ "codes": [ + 257, + { "keycode": 259, "scancode": 0, "modifier": 0 } +]} +``` + +#### Special actions +A KeyFunction that performs a predefined action, such as pressing Enter, Shift Key, copying text, etc. + +```json +{ "action": "shift" } +``` +Available actions +- `shift`, `shift_lock` +- `enter`, `backspace`, `tab` +- `left_arrow`, `right_arrow`, `up_arrow`, `down_arrow` +- `copy_all`, `paste` +- `previous_layout` + +Display names are translatable via `controlify.keyboard.special.`, e.g. `controlify.keyboard.special.enter`. + +#### Change layout +A KeyFunction that switches to another layout by id. It can be specified as: +1. Object form: `{ layout: ResourceLocation, display_name: Component }` + +```json +{ "layout": "my_pack:emojis", "display_name": { "text": "Emoji" } } +``` + +### Display names +Where a display name is supported, use a [Minecraft Component](https://minecraft.wiki/w/Text_component_format#Java_Edition). +Examples: +```json +{ "text": "Enter" } +{ "translate": "my.lang.key" } +``` + +## Examples +Minimal keyboard +```json +{ + "width": 10, + "keys": [ + [ "q", "w", "e", "r", "t", "y", "u", "i", "o", "p" ], + [ "a", "s", "d", "f", "g", "h", "j", "k", "l", [".", ","] ], + [ "z", "x", "c", "v", "b", "n", "m", ["-", "_"], { "regular": { "action": "enter" }, "width": 2.0 } ] + ] +} +``` + +Explicit shifted with width and shortcut glyphs +```json +{ + "width": 13, + "keys": [ + [ + { "regular": { "action": "tab" }, "width": 1 }, + [ ["a", "A"] ], + { "regular": { "chars": "b" }, "shifted": { "chars": "B" }, "width": 2, "shortcut": "controlify:gui_abstract_action_1", "identifier": "big-b" }, + { "action": "enter" } + ] + ] +} +``` + +Switch to another layout and back +```json +{ + "width": 5, + "keys": [ + [ + { "layout": "my_pack:numeric", "display_name": { "text": "123" }, "identifier": "to-numeric" }, + { "action": "previous_layout" } + ] + ] +} +``` + +## Behavior notes +- Keys are column‑aligned; spanning multiple columns isn’t supported. Use `width` to vary sizes. +- Shift toggles the shifted layer for the next press; Shift‑lock keeps it engaged until toggled off. +- Paste inserts clipboard text as characters; Copy‑all calls the target’s copy handler. +- When changing layouts, focus is preserved using `identifier` if possible, or by focusing a `previous_layout` key. + + +If you only provide a single KeyFunction (e.g. a string), the key uses that for both layers, with the string’s uppercase form as the shifted display by default. + + +## Common conventions + +It's recommended to follow these conventions when creating your own keyboard layouts: + +- Backspace key (if present) should have a shortcut of `controlify:gui_abstract_action_1`. +- Space key (if present) should have a shortcut of `controlify:gui_abstract_action_2`. +- Shift key (if present) should have a shortcut of `controlify:gui_abstract_action_3`. +- Enter key (if present) should have a shortcut of `controlify:pause`. +- The user expects `controlify:gui_next_tab` and `controlify:gui_prev_tab` should not be used as shortcuts, + as the user expects them to be used to move the text cursor. +- Prefer using [special actions](#special-actions) for common keys like Enter, Shift, and Backspace, + as opposed to using key codes, for better readability and localisation. +- When localising keyboards, prefer to keep the same special actions in the same order where possible. +- When localising keyboards, prefer the most standard layout for the language, such as QWERTY for English, AZERTY for French, etc. +- It is not necessary to localise the display names, as they're already in a localised keyboard layout file. + +## JSON Schema + +Add the following to the top of your keyboard layout JSON file to get schema validation in your IDE: + +```json +{ + "$schema": "https://erebus.moddedmc.wiki/api/v1/docs/controlify/asset/controlify:schemas/keyboard_layout" +} +``` diff --git a/docs/schemas/keyboard_layout.json b/docs/schemas/keyboard_layout.json new file mode 100644 index 000000000..61e4b1505 --- /dev/null +++ b/docs/schemas/keyboard_layout.json @@ -0,0 +1,141 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://controlify.dev/schemas/keyboard-layout.json", + "title": "Controlify Keyboard Layout", + "type": "object", + "additionalProperties": false, + "required": ["width", "keys"], + "properties": { + "width": { + "type": "number", + "minimum": 1, + "description": "Unit width of the layout; each row's key widths must sum to this value." + }, + "keys": { + "type": "array", + "minItems": 1, + "items": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/keyEntry" }, + "description": "Row of keys, left-to-right." + }, + "description": "Rows, rendered top-to-bottom." + } + }, + "$defs": { + "resourceLocation": { + "type": "string", + "pattern": "^[a-z0-9_.-]+:[a-z0-9/._-]+$", + "description": "Minecraft resource location (namespace:path)." + }, + "component": { + "oneOf": [ + { + "type": "object", + "description": "Minecraft text component (e.g. {\"text\":\"...\"} or {\"translate\":\"...\"}).", + "additionalProperties": true, + "minProperties": 1 + }, + { + "type": "string", + "description": "Short literal form of a text component, e.g. \"Hello World\"." + } + ] + }, + "keyCode": { + "oneOf": [ + { "type": "integer", "description": "Keycode (GLFW/Minecraft logical key)." }, + { + "type": "object", + "additionalProperties": false, + "required": ["keycode"], + "properties": { + "keycode": { "type": "integer" }, + "scancode": { "type": "integer", "default": 0 }, + "modifier": { "type": "integer", "default": 0 } + } + } + ] + }, + "keyFunction": { + "oneOf": [ + { "type": "string", "description": "String insertion (short form)." }, + { + "type": "object", + "additionalProperties": false, + "required": ["chars"], + "properties": { + "chars": { "type": "string" }, + "display_name": { "$ref": "#/$defs/component" } + }, + "description": "String insertion (object form)." + }, + { + "type": "object", + "additionalProperties": false, + "required": ["codes", "display_name"], + "properties": { + "codes": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/keyCode" } }, + "display_name": { "$ref": "#/$defs/component" } + }, + "description": "Key code sequence." + }, + { + "type": "object", + "additionalProperties": false, + "required": ["action"], + "properties": { + "action": { + "type": "string", + "enum": [ + "shift", "shift_lock", + "enter", "backspace", "tab", + "left_arrow", "right_arrow", "up_arrow", "down_arrow", + "copy_all", "paste", + "previous_layout" + ] + } + }, + "description": "Special action key." + }, + { + "type": "object", + "additionalProperties": false, + "required": ["layout", "display_name"], + "properties": { + "layout": { "$ref": "#/$defs/resourceLocation" }, + "display_name": { "$ref": "#/$defs/component" } + }, + "description": "Change layout key." + } + ] + }, + "keyObject": { + "type": "object", + "additionalProperties": false, + "required": ["regular"], + "properties": { + "regular": { "$ref": "#/$defs/keyFunction" }, + "shifted": { "$ref": "#/$defs/keyFunction" }, + "width": { "type": "number", "minimum": 0.1, "default": 1 }, + "shortcut": { "$ref": "#/$defs/resourceLocation" }, + "identifier": { "type": "string" } + }, + "description": "Full key definition with optional shifted function, width, shortcut glyph binding, and focus identifier." + }, + "keyEntry": { + "oneOf": [ + { "$ref": "#/$defs/keyFunction" }, + { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { "$ref": "#/$defs/keyFunction" }, { "$ref": "#/$defs/keyFunction" } ], + "description": "[regular, shifted] pair" + }, + { "$ref": "#/$defs/keyObject" } + ] + } + } +} diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index ddbee2025..b6137ad4f 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -4,7 +4,6 @@ import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.api.bind.ControlifyBindApi; import dev.isxander.controlify.api.entrypoint.InitContext; -import dev.isxander.controlify.api.entrypoint.PreInitContext; import dev.isxander.controlify.api.guide.ContainerCtx; import dev.isxander.controlify.api.guide.GuideDomainRegistries; import dev.isxander.controlify.api.guide.GuideDomainRegistry; @@ -36,6 +35,7 @@ import dev.isxander.controlify.platform.main.PlatformMainUtil; import dev.isxander.controlify.platform.network.SidedNetworkApi; import dev.isxander.controlify.rumble.RumbleManager; +import dev.isxander.controlify.screenop.keyboard.KeyboardLayoutManager; import dev.isxander.controlify.server.*; import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.config.ControlifyConfig; @@ -81,6 +81,7 @@ public class Controlify implements ControlifyApi { private InputFontMapper inputFontMapper; private DefaultBindManager defaultBindManager; private ControllerTypeManager controllerTypeManager; + private KeyboardLayoutManager keyboardLayoutManager; private Set thisTickContexts; private ControllerHIDService controllerHIDService; @@ -116,9 +117,11 @@ public void preInitialiseControlify() { this.inputFontMapper = new InputFontMapper(); this.defaultBindManager = new DefaultBindManager(); this.controllerTypeManager = new ControllerTypeManager(); + this.keyboardLayoutManager = new KeyboardLayoutManager(); PlatformClientUtil.registerAssetReloadListener(inputFontMapper); PlatformClientUtil.registerAssetReloadListener(defaultBindManager); PlatformClientUtil.registerAssetReloadListener(controllerTypeManager); + PlatformClientUtil.registerAssetReloadListener(keyboardLayoutManager); PlatformClientUtil.registerAssetReloadListener(GuideDomains.IN_GAME); PlatformClientUtil.registerAssetReloadListener(GuideDomains.CONTAINER); @@ -682,6 +685,10 @@ public ControllerTypeManager controllerTypeManager() { return controllerTypeManager; } + public KeyboardLayoutManager keyboardLayoutManager() { + return keyboardLayoutManager; + } + public Set thisTickBindContexts() { return this.thisTickContexts; } diff --git a/src/main/java/dev/isxander/controlify/api/guide/Rule.java b/src/main/java/dev/isxander/controlify/api/guide/Rule.java index c48afbfc9..3d689e3f0 100644 --- a/src/main/java/dev/isxander/controlify/api/guide/Rule.java +++ b/src/main/java/dev/isxander/controlify/api/guide/Rule.java @@ -3,7 +3,8 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.isxander.controlify.api.bind.InputBindingSupplier; -import dev.isxander.controlify.utils.SetCodec; +import dev.isxander.controlify.utils.codec.CExtraCodecs; +import dev.isxander.controlify.utils.codec.SetCodec; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.resources.ResourceLocation; @@ -35,8 +36,8 @@ public record Rule( instance -> instance.group( InputBindingSupplier.CODEC.fieldOf("for").forGetter(Rule::binding), ActionLocation.CODEC.fieldOf("where").forGetter(Rule::where), - new SetCodec<>(ResourceLocation.CODEC).optionalFieldOf("when", Set.of()).forGetter(Rule::when), - new SetCodec<>(ResourceLocation.CODEC).optionalFieldOf("forbid", Set.of()).forGetter(Rule::forbid), + CExtraCodecs.set(ResourceLocation.CODEC).optionalFieldOf("when", Set.of()).forGetter(Rule::when), + CExtraCodecs.set(ResourceLocation.CODEC).optionalFieldOf("forbid", Set.of()).forGetter(Rule::forbid), ComponentSerialization.CODEC.fieldOf("then").forGetter(Rule::then) ).apply(instance, Rule::new) ); diff --git a/src/main/java/dev/isxander/controlify/bindings/ControlifyBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControlifyBindings.java index 4e94fd78e..0e4651a36 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControlifyBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControlifyBindings.java @@ -110,7 +110,7 @@ public final class ControlifyBindings { public static final InputBindingSupplier PAUSE = ControlifyBindApi.get().registerBinding(builder -> builder .id("controlify", "pause") .category(GAMEPLAY_CATEGORY) - .allowedContexts(BindContext.IN_GAME) + .allowedContexts(BindContext.IN_GAME, BindContext.REGULAR_SCREEN) .radialCandidate(RadialIcons.getItem(Items.STRUCTURE_VOID))); public static final InputBindingSupplier CHANGE_PERSPECTIVE = ControlifyBindApi.get().registerBinding(builder -> builder .id("controlify", "change_perspective") @@ -159,23 +159,7 @@ public final class ControlifyBindings { public static final InputBindingSupplier DROP_INVENTORY = ControlifyBindApi.get().registerBinding(builder -> builder .id("controlify", "drop_inventory") .category(INVENTORY_CATEGORY) - .allowedContexts(BindContext.CONTAINER)); - public static final InputBindingSupplier BUNDLE_NAVI_UP = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "bundle_navi_up") - .category(INVENTORY_CATEGORY) - .allowedContexts(BindContext.CONTAINER)); - public static final InputBindingSupplier BUNDLE_NAVI_DOWN = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "bundle_navi_down") - .category(INVENTORY_CATEGORY) - .allowedContexts(BindContext.CONTAINER)); - public static final InputBindingSupplier BUNDLE_NAVI_LEFT = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "bundle_navi_left") - .category(INVENTORY_CATEGORY) - .allowedContexts(BindContext.CONTAINER)); - public static final InputBindingSupplier BUNDLE_NAVI_RIGHT = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "bundle_navi_right") - .category(INVENTORY_CATEGORY) - .allowedContexts(BindContext.CONTAINER)); + .allowedContexts(BindContext.CONTAINER, BindContext.REGULAR_SCREEN)); public static final InputBindingSupplier PICK_BLOCK = ControlifyBindApi.get().registerBinding(builder -> builder .id("controlify", "pick_block") @@ -286,14 +270,26 @@ public final class ControlifyBindings { .id("controlify", "gui_navi_right") .category(GUI_CATEGORY) .allowedContexts(BindContext.REGULAR_SCREEN)); - public static final InputBindingSupplier CYCLE_OPT_FORWARD = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "cycle_opt_forward") + public static final InputBindingSupplier GUI_SECONDARY_NAVI_UP = ControlifyBindApi.get().registerBinding(builder -> builder + .id("controlify", "gui_secondary_navi_up") .category(GUI_CATEGORY) - .allowedContexts(BindContext.REGULAR_SCREEN)); - public static final InputBindingSupplier CYCLE_OPT_BACKWARD = ControlifyBindApi.get().registerBinding(builder -> builder - .id("controlify", "cycle_opt_backward") + .allowedContexts(BindContext.CONTAINER, BindContext.REGULAR_SCREEN)); + public static final InputBindingSupplier GUI_SECONDARY_NAVI_DOWN = ControlifyBindApi.get().registerBinding(builder -> builder + .id("controlify", "gui_secondary_navi_down") .category(GUI_CATEGORY) - .allowedContexts(BindContext.REGULAR_SCREEN)); + .allowedContexts(BindContext.CONTAINER, BindContext.REGULAR_SCREEN)); + public static final InputBindingSupplier GUI_SECONDARY_NAVI_LEFT = ControlifyBindApi.get().registerBinding(builder -> builder + .id("controlify", "gui_secondary_navi_left") + .category(GUI_CATEGORY) + .allowedContexts(BindContext.CONTAINER, BindContext.REGULAR_SCREEN)); + public static final InputBindingSupplier GUI_SECONDARY_NAVI_RIGHT = ControlifyBindApi.get().registerBinding(builder -> builder + .id("controlify", "gui_secondary_navi_right") + .category(GUI_CATEGORY) + .allowedContexts(BindContext.CONTAINER, BindContext.REGULAR_SCREEN)); + @Deprecated + public static final InputBindingSupplier CYCLE_OPT_FORWARD = GUI_SECONDARY_NAVI_RIGHT; + @Deprecated + public static final InputBindingSupplier CYCLE_OPT_BACKWARD = GUI_SECONDARY_NAVI_LEFT; public static final InputBindingSupplier RADIAL_MENU = ControlifyBindApi.get().registerBinding(builder -> builder .id("controlify", "radial_menu") diff --git a/src/main/java/dev/isxander/controlify/bindings/input/InputType.java b/src/main/java/dev/isxander/controlify/bindings/input/InputType.java index 7726c3f25..a8affa507 100644 --- a/src/main/java/dev/isxander/controlify/bindings/input/InputType.java +++ b/src/main/java/dev/isxander/controlify/bindings/input/InputType.java @@ -1,21 +1,18 @@ package dev.isxander.controlify.bindings.input; import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import dev.isxander.controlify.utils.CUtil; -import dev.isxander.controlify.utils.FuzzyMapCodec; -import dev.isxander.controlify.utils.StrictEitherMapCodec; +import dev.isxander.controlify.utils.codec.CExtraCodecs; +import dev.isxander.controlify.utils.codec.FuzzyMapCodec; +import dev.isxander.controlify.utils.codec.StrictEitherMapCodec; import net.minecraft.Util; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import org.jetbrains.annotations.NotNull; import java.util.Arrays; -import java.util.Optional; import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; public record InputType(String id, MapCodec codec) implements StringRepresentable { @@ -31,7 +28,7 @@ public record InputType(String id, MapCodec codec) implement public static MapCodec createCodec( T[] types, Function> codecGetter, Function typeGetter, String typeFieldName ) { - MapCodec fuzzyCodec = new FuzzyMapCodec<>( + MapCodec fuzzyCodec = CExtraCodecs.fuzzyMap( Stream.of(types).map(codecGetter).toList(), obj -> codecGetter.apply(typeGetter.apply(obj)) ); diff --git a/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/CycleControlProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/CycleControlProcessor.java index f6df27c2b..372722cc7 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/CycleControlProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/CycleControlProcessor.java @@ -16,13 +16,13 @@ public CycleControlProcessor(Consumer cycleMethod) { @Override public boolean overrideControllerButtons(ScreenProcessor screen, ControllerEntity controller) { - if (ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).justPressed() + if (ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).justPressed() || ControlifyBindings.GUI_PRESS.on(controller).justPressed() ) { cycleMethod.accept(false); return true; } - if (ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).justPressed()) { + if (ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).justPressed()) { cycleMethod.accept(true); return true; } diff --git a/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/TickBoxControlProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/TickBoxControlProcessor.java index 3d68cbaa3..1c5393f9a 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/TickBoxControlProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/sodium/screenop/TickBoxControlProcessor.java @@ -18,7 +18,7 @@ public boolean overrideControllerButtons(ScreenProcessor screen, ControllerEn toggleMethod.run(); return true; } - if (ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).justPressed() || ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).justPressed()) { + if (ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).justPressed() || ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).justPressed()) { toggleMethod.run(); return true; } diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementAccessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementAccessor.java new file mode 100644 index 000000000..67ddd2cb4 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementAccessor.java @@ -0,0 +1,11 @@ +package dev.isxander.controlify.compatibility.yacl.mixins; + +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(StringControllerElement.class) +public interface StringControllerElementAccessor { + @Invoker + boolean callDoCopy(); +} diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementMixin.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementMixin.java new file mode 100644 index 000000000..ed1be0005 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/mixins/StringControllerElementMixin.java @@ -0,0 +1,21 @@ +package dev.isxander.controlify.compatibility.yacl.mixins; + +import dev.isxander.controlify.compatibility.yacl.screenop.StringControllerElementComponentProcessor; +import dev.isxander.controlify.screenop.ComponentProcessor; +import dev.isxander.controlify.screenop.ComponentProcessorProvider; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(StringControllerElement.class) +public class StringControllerElementMixin implements ComponentProcessorProvider { + @Unique + private final ComponentProcessor componentProcessor = new StringControllerElementComponentProcessor( + (StringControllerElement) (Object) this + ); + + @Override + public ComponentProcessor componentProcessor() { + return componentProcessor; + } +} diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/CyclingControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/CyclingControllerElementComponentProcessor.java index a44105a94..4bebcd796 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/CyclingControllerElementComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/CyclingControllerElementComponentProcessor.java @@ -18,8 +18,8 @@ public CyclingControllerElementComponentProcessor(CyclingControllerElement cycli @Override public boolean overrideControllerNavigation(ScreenProcessor screen, ControllerEntity controller) { - boolean left = ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).digitalNow(); - boolean right = ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).digitalNow(); + boolean left = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).digitalNow(); + boolean right = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).digitalNow(); if (!((ControllerWidgetAccessor) cyclingController).getControl().option().available()) { return false; diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/SliderControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/SliderControllerElementComponentProcessor.java index 81c2fc4ce..ab74eec39 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/SliderControllerElementComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/SliderControllerElementComponentProcessor.java @@ -18,10 +18,10 @@ public SliderControllerElementComponentProcessor(SliderControllerElement element @Override public boolean overrideControllerButtons(ScreenProcessor screen, ControllerEntity controller) { - var left = ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).digitalNow(); - var leftPrev = ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).digitalPrev(); - var right = ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).digitalNow(); - var rightPrev = ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).digitalPrev(); + var left = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).digitalNow(); + var leftPrev = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).digitalPrev(); + var right = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).digitalNow(); + var rightPrev = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).digitalPrev(); if (!((ControllerWidgetAccessor) slider).getControl().option().available()) { return false; diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/StringControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/StringControllerElementComponentProcessor.java new file mode 100644 index 000000000..ae7ce4d57 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/screenop/StringControllerElementComponentProcessor.java @@ -0,0 +1,79 @@ +package dev.isxander.controlify.compatibility.yacl.screenop; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.controlify.compatibility.yacl.mixins.StringControllerElementAccessor; +import dev.isxander.controlify.controller.ControllerEntity; +import dev.isxander.controlify.screenop.ComponentProcessor; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.keyboard.ComponentKeyboardBehaviour; +import dev.isxander.controlify.screenop.keyboard.InputTarget; +import dev.isxander.controlify.screenop.keyboard.KeyboardLayouts; +import dev.isxander.controlify.screenop.keyboard.KeyboardOverlayScreen; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; + +public class StringControllerElementComponentProcessor implements ComponentProcessor { + private final StringControllerElement element; + + public StringControllerElementComponentProcessor(StringControllerElement element) { + this.element = element; + } + + @Override + public ComponentKeyboardBehaviour getKeyboardBehaviour(ScreenProcessor screen, ControllerEntity controller) { + return new ComponentKeyboardBehaviour.Handled( + KeyboardLayouts.simple(), + new StringInputTarget(), + KeyboardOverlayScreen.aboveOrBelowWidgetPositioner( + (int) (screen.screen.width * 0.8f), (int) (screen.screen.height * 0.4f), + 1, + element::getRectangle + ) + ); + } + + private class StringInputTarget implements InputTarget { + @Override + public boolean supportsCharInput() { + return true; + } + + @Override + public boolean acceptChar(char ch, int modifiers) { + return element.charTyped(ch, modifiers); + } + + @Override + public boolean supportsKeyCodeInput() { + return true; + } + + @Override + public boolean acceptKeyCode(int keycode, int scancode, int modifiers) { + return element.keyPressed(keycode, scancode, modifiers); + } + + @Override + public boolean supportsCursorMovement() { + return true; + } + + @Override + public boolean moveCursor(int amount) { + int keycode = amount > 0 ? InputConstants.KEY_RIGHT : InputConstants.KEY_LEFT; + for (int i = 0; i < amount; i++) { + element.keyPressed(keycode, 0, 0); + } + return true; + } + + @Override + public boolean supportsCopying() { + return true; + } + + @Override + public boolean copy() { + return ((StringControllerElementAccessor) element).callDoCopy(); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/config/GlobalSettings.java b/src/main/java/dev/isxander/controlify/config/GlobalSettings.java index a4879f15b..54b04f66a 100644 --- a/src/main/java/dev/isxander/controlify/config/GlobalSettings.java +++ b/src/main/java/dev/isxander/controlify/config/GlobalSettings.java @@ -2,7 +2,6 @@ import com.google.common.collect.Lists; import com.google.gson.annotations.SerializedName; -import dev.isxander.controlify.driver.steamdeck.SteamDeckUtil; import dev.isxander.controlify.reacharound.ReachAroundMode; import dev.isxander.controlify.server.ServerPolicies; import net.minecraft.client.Minecraft; @@ -27,7 +26,7 @@ public class GlobalSettings { public boolean vibrationOnboarded = false; public ReachAroundMode reachAround = ReachAroundMode.OFF; public boolean allowServerRumble = true; - public boolean uiSounds = false; + public boolean extraUiSounds = true; public boolean notifyLowBattery = true; public float ingameButtonGuideScale = 1f; public boolean useEnhancedSteamDeckDriver = true; diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerEntity.java b/src/main/java/dev/isxander/controlify/controller/ControllerEntity.java index 5b66b3d11..23b50d51a 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerEntity.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerEntity.java @@ -11,7 +11,6 @@ import dev.isxander.controlify.controller.info.DriverNameComponent; import dev.isxander.controlify.controller.info.GUIDComponent; import dev.isxander.controlify.controller.info.UIDComponent; -import dev.isxander.controlify.controller.keyboard.NativeKeyboardComponent; import dev.isxander.controlify.controller.led.LEDComponent; import dev.isxander.controlify.controller.serialization.ConfigHolder; import dev.isxander.controlify.controller.serialization.IConfig; @@ -159,11 +158,6 @@ public Optional bluetooth() { return this.getComponent(BluetoothDeviceComponent.ID); } - @Contract(pure = true) - public Optional nativeKeyboard() { - return this.getComponent(NativeKeyboardComponent.ID); - } - public void update(boolean outOfFocus) { this.driver.update(this, outOfFocus); } diff --git a/src/main/java/dev/isxander/controlify/controller/GenericControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/GenericControllerConfig.java index ed9e0969e..5e6d0e334 100644 --- a/src/main/java/dev/isxander/controlify/controller/GenericControllerConfig.java +++ b/src/main/java/dev/isxander/controlify/controller/GenericControllerConfig.java @@ -25,4 +25,8 @@ public class GenericControllerConfig implements ConfigClass { public boolean showOnScreenKeyboard = true; public boolean dontShowControllerSubmission = false; + + public boolean hintKeyboardCursor = true; + public boolean hintKeyboardCommandSuggester = true; + public boolean hintKeyboardSignLine = true; } diff --git a/src/main/java/dev/isxander/controlify/controller/id/ControllerTypeManager.java b/src/main/java/dev/isxander/controlify/controller/id/ControllerTypeManager.java index cdcdd7b35..81e8634d5 100644 --- a/src/main/java/dev/isxander/controlify/controller/id/ControllerTypeManager.java +++ b/src/main/java/dev/isxander/controlify/controller/id/ControllerTypeManager.java @@ -33,7 +33,7 @@ public class ControllerTypeManager implements SimpleControlifyReloadListener ENTRY_CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.list(HIDIdentifier.LIST_CODEC) + Codec.list(HIDIdentifier.CODEC) .comapFlatMap(list -> list.isEmpty() ? DataResult.error(() -> "At least one HID must be present") : DataResult.success(list), list -> list) .fieldOf("hids") .forGetter(ControllerTypeEntry::hid), @@ -51,20 +51,19 @@ public Map getTypeMap() { @Override public CompletableFuture load(ResourceManager manager, Executor executor) { return CompletableFuture.supplyAsync(() -> manager.getResourceStack(CUtil.rl("controllers/controller_identification.json5")), executor) - .thenComposeAsync(resources -> { + .thenCompose(resources -> { List>>> futures = new ArrayList<>(); for (Resource resource : resources) { futures.add(CompletableFuture.supplyAsync(() -> readIdentificationResource(resource), executor)); } return Util.sequence(futures) - .thenApplyAsync(listOfEntries -> listOfEntries.stream() + .thenApply(listOfEntries -> listOfEntries.stream() .flatMap(List::stream) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b)), - executor); + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b))); - }, executor) - .thenApplyAsync(Preparations::new, executor); + }) + .thenApply(Preparations::new); } private List> readIdentificationResource(Resource resource) { diff --git a/src/main/java/dev/isxander/controlify/controller/keyboard/NativeKeyboardComponent.java b/src/main/java/dev/isxander/controlify/controller/keyboard/NativeKeyboardComponent.java deleted file mode 100644 index 1cbb4f27e..000000000 --- a/src/main/java/dev/isxander/controlify/controller/keyboard/NativeKeyboardComponent.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.isxander.controlify.controller.keyboard; - -import dev.isxander.controlify.controller.ECSComponent; -import dev.isxander.controlify.controller.impl.ConfigImpl; -import dev.isxander.controlify.controller.serialization.ConfigClass; -import dev.isxander.controlify.controller.serialization.ConfigHolder; -import dev.isxander.controlify.controller.serialization.IConfig; -import dev.isxander.controlify.utils.CUtil; -import net.minecraft.resources.ResourceLocation; - -public class NativeKeyboardComponent implements ECSComponent, ConfigHolder { - public static final ResourceLocation ID = CUtil.rl("native_keyboard"); - - private final ConfigImpl config = new ConfigImpl<>(Config::new, Config.class); - - private final Runnable onOpen; - private final float keyboardHeight; - - public NativeKeyboardComponent(Runnable onOpen, float keyboardHeight) { - this.onOpen = onOpen; - this.keyboardHeight = keyboardHeight; - } - - public void open() { - this.onOpen.run(); - } - - public float getKeyboardHeight() { - return this.keyboardHeight; - } - - @Override - public IConfig config() { - return config; - } - - @Override - public ResourceLocation id() { - return ID; - } - - public static class Config implements ConfigClass { - public boolean useNativeKeyboard = false; - } -} diff --git a/src/main/java/dev/isxander/controlify/driver/steamdeck/SteamDeckDriver.java b/src/main/java/dev/isxander/controlify/driver/steamdeck/SteamDeckDriver.java index 15539509e..7ceb53286 100644 --- a/src/main/java/dev/isxander/controlify/driver/steamdeck/SteamDeckDriver.java +++ b/src/main/java/dev/isxander/controlify/driver/steamdeck/SteamDeckDriver.java @@ -12,7 +12,6 @@ import dev.isxander.controlify.controller.info.UIDComponent; import dev.isxander.controlify.controller.input.GamepadInputs; import dev.isxander.controlify.controller.input.InputComponent; -import dev.isxander.controlify.controller.keyboard.NativeKeyboardComponent; import dev.isxander.controlify.controller.steamdeck.SteamDeckComponent; import dev.isxander.controlify.controller.touchpad.TouchpadComponent; import dev.isxander.controlify.controller.touchpad.Touchpads; @@ -42,7 +41,6 @@ public class SteamDeckDriver implements Driver { private GyroComponent gyroComponent; private BatteryLevelComponent batteryLevelComponent; private TouchpadComponent touchpadComponent; - private NativeKeyboardComponent keyboardComponent; private ControlifyLogger logger; @@ -88,10 +86,6 @@ public void addComponents(ControllerEntity controller) { ) // there are two touchpads, one for each thumb ); - controller.setComponent( - this.keyboardComponent = new NativeKeyboardComponent(this::openKeyboard, 0.5f) - ); - controller.setComponent(new SteamDeckComponent()); } @@ -217,10 +211,6 @@ private void updateTouchpad(Touchpads.Touchpad touchpad, short x, short y, short } } - private void openKeyboard() { - deck.openModalKeyboard(true); - } - @Override public void close() { logger.debugLog("Closing driver..."); diff --git a/src/main/java/dev/isxander/controlify/font/BindingFontHelper.java b/src/main/java/dev/isxander/controlify/font/BindingFontHelper.java index 37ea07124..1ceca51b8 100644 --- a/src/main/java/dev/isxander/controlify/font/BindingFontHelper.java +++ b/src/main/java/dev/isxander/controlify/font/BindingFontHelper.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.font.GlyphInfo; import com.mojang.blaze3d.font.SheetGlyphInfo; import dev.isxander.controlify.api.bind.InputBinding; +import dev.isxander.controlify.api.bind.InputBindingSupplier; import dev.isxander.controlify.mixins.feature.font.FontAccessor; import dev.isxander.controlify.utils.CUtil; import net.minecraft.client.gui.Font; @@ -53,6 +54,10 @@ public static Component binding(InputBinding binding) { return binding(binding.id()); } + public static Component binding(InputBindingSupplier bindingSupplier) { + return binding(bindingSupplier.bindId()); + } + public static int getComponentHeight(Font font, FormattedCharSequence text) { MutableInt mutableInt = new MutableInt(); text.accept((index, style, codePoint) -> { diff --git a/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java b/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java index 92e3bac94..7fd941a64 100644 --- a/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java +++ b/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java @@ -18,9 +18,10 @@ public InGameButtonGuide(ControllerEntity controller, Minecraft minecraft) { public void renderHud(GuiGraphics graphics, float tickDelta) { boolean debugOpen = minecraft.getDebugOverlay().showDebugScreen(); boolean hideGui = minecraft.options.hideGui; + boolean screenOpen = minecraft.screen != null; GenericControllerConfig config = controller.genericConfig().config(); - if (!debugOpen && !hideGui && config.showIngameGuide) { + if (!debugOpen && !hideGui && !screenOpen && config.showIngameGuide) { GuideRenderer.render(graphics, GuideDomains.IN_GAME, minecraft, config.ingameGuideBottom, true); } } diff --git a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java index 51ba19445..0ad7399a2 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java @@ -15,7 +15,6 @@ import dev.isxander.controlify.controller.input.DeadzoneGroup; import dev.isxander.controlify.controller.input.InputComponent; import dev.isxander.controlify.controller.input.Inputs; -import dev.isxander.controlify.controller.keyboard.NativeKeyboardComponent; import dev.isxander.controlify.controller.rumble.RumbleComponent; import dev.isxander.controlify.gui.controllers.BindController; import dev.isxander.controlify.gui.controllers.Deadzone2DImageRenderer; @@ -258,19 +257,13 @@ private Optional makeAccessibilityGroup(ControllerEntity controller .binding(def.showScreenGuides, () -> config.showScreenGuides, v -> config.showScreenGuides = v) .controller(TickBoxControllerBuilder::create) .build()) - .option(Option.createBuilder() + .option(Option.createBuilder() .name(Component.translatable("controlify.gui.show_keyboard")) .description(OptionDescription.createBuilder() .text(Component.translatable("controlify.gui.show_keyboard.tooltip")) .build()) - .binding( - OnScreenKeyboardMode.getForController(controller), - () -> OnScreenKeyboardMode.getForController(controller), - v -> v.setForController(controller) - ) - .controller(opt -> CyclingListControllerBuilder.create(opt) - .values(OnScreenKeyboardMode.valuesForController(controller)) - .formatValue(OnScreenKeyboardMode::getDisplayName)) + .binding(def.showOnScreenKeyboard, () -> config.showOnScreenKeyboard, v -> config.showOnScreenKeyboard = v) + .controller(TickBoxControllerBuilder::create) .build()) .build()); } @@ -733,60 +726,4 @@ private static ResourceLocation screenshot(String filename) { private record OptionBindPair(Option option, InputBinding binding) { } - - private enum OnScreenKeyboardMode implements NameableEnum { - OFF("controlify.gui.show_keyboard.off"), - CONTROLIFY("controlify.gui.show_keyboard.controlify"), - SYSTEM("controlify.gui.show_keyboard.system"); - - public static OnScreenKeyboardMode[] valuesForController(ControllerEntity controller) { - if (controller.nativeKeyboard().isPresent()) { - return new OnScreenKeyboardMode[]{ OFF, CONTROLIFY, SYSTEM }; - } else { - return new OnScreenKeyboardMode[]{ OFF, CONTROLIFY }; - } - } - - public static OnScreenKeyboardMode getForController(ControllerEntity controller) { - if (controller.genericConfig().config().showOnScreenKeyboard) { - if (controller.nativeKeyboard().map(nativeKeyboard -> nativeKeyboard.confObj().useNativeKeyboard).orElse(false)) { - return SYSTEM; - } else { - return CONTROLIFY; - } - } else { - return OFF; - } - } - - public void setForController(ControllerEntity controller) { - GenericControllerConfig genericConfig = controller.genericConfig().config(); - Optional nativeConfig = controller.nativeKeyboard().map(NativeKeyboardComponent::confObj); - switch (this) { - case OFF -> { - genericConfig.showOnScreenKeyboard = false; - nativeConfig.ifPresent(config -> config.useNativeKeyboard = false); - } - case CONTROLIFY -> { - genericConfig.showOnScreenKeyboard = true; - nativeConfig.ifPresent(config -> config.useNativeKeyboard = false); - } - case SYSTEM -> { - genericConfig.showOnScreenKeyboard = true; - nativeConfig.ifPresent(config -> config.useNativeKeyboard = true); - } - } - } - - private final Component displayName; - - OnScreenKeyboardMode(String key) { - this.displayName = Component.translatable(key); - } - - @Override - public Component getDisplayName() { - return displayName; - } - } } diff --git a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java index 7fee33f90..2e28a5cad 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java @@ -5,7 +5,6 @@ import dev.isxander.controlify.config.GlobalSettings; import dev.isxander.controlify.controller.ControllerEntity; import dev.isxander.controlify.driver.steamdeck.SteamDeckUtil; -import dev.isxander.controlify.gui.controllers.FormattableStringController; import dev.isxander.controlify.reacharound.ReachAroundMode; import dev.isxander.controlify.server.ServerPolicies; import dev.isxander.controlify.server.ServerPolicy; @@ -129,7 +128,7 @@ public static Screen createGlobalSettingsScreen(Screen parent) { .description(OptionDescription.createBuilder() .text(Component.translatable("controlify.gui.ui_sounds.tooltip")) .build()) - .binding(GlobalSettings.DEFAULT.uiSounds, () -> globalSettings.uiSounds, v -> globalSettings.uiSounds = v) + .binding(GlobalSettings.DEFAULT.extraUiSounds, () -> globalSettings.extraUiSounds, v -> globalSettings.extraUiSounds = v) .controller(TickBoxControllerBuilder::create) .build()) .option(Option.createBuilder() diff --git a/src/main/java/dev/isxander/controlify/hid/HIDIdentifier.java b/src/main/java/dev/isxander/controlify/hid/HIDIdentifier.java index bc3ec5f89..6f50d16f5 100644 --- a/src/main/java/dev/isxander/controlify/hid/HIDIdentifier.java +++ b/src/main/java/dev/isxander/controlify/hid/HIDIdentifier.java @@ -1,24 +1,20 @@ package dev.isxander.controlify.hid; import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; +import dev.isxander.controlify.utils.codec.CExtraCodecs; +import org.jetbrains.annotations.NotNull; import java.util.HexFormat; -import java.util.List; public record HIDIdentifier(int vendorId, int productId) { - public static final Codec LIST_CODEC = Codec.list(Codec.INT).comapFlatMap( - parts -> { - if (parts.size() != 2) { - return DataResult.error(() -> "HID identifier list must have exactly two elements, found " + parts.size()); - } - return DataResult.success(new HIDIdentifier(parts.get(0), parts.get(1))); - }, - hid -> List.of(hid.vendorId(), hid.productId()) + public static final Codec CODEC = CExtraCodecs.arrayPair( + Codec.INT, + HIDIdentifier::vendorId, HIDIdentifier::productId, + HIDIdentifier::new ); @Override - public String toString() { + public @NotNull String toString() { var hex = HexFormat.of(); return "HID[" + "VID=0x" + hex.toHexDigits(vendorId, 4) + diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/font/KeybindContentsMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/font/KeybindContentsMixin.java index 3c6783255..c8e288342 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/font/KeybindContentsMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/font/KeybindContentsMixin.java @@ -18,6 +18,9 @@ import java.util.Optional; +//? if >=1.21.9 +import net.minecraft.network.chat.FontDescription; + @Mixin(KeybindContents.class) public class KeybindContentsMixin { @Shadow @@ -26,7 +29,12 @@ public class KeybindContentsMixin { @WrapOperation(method = "visit(Lnet/minecraft/network/chat/FormattedText$StyledContentConsumer;Lnet/minecraft/network/chat/Style;)Ljava/util/Optional;", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/contents/KeybindContents;getNestedComponent()Lnet/minecraft/network/chat/Component;")) private Component testVisitWithStyle(KeybindContents instance, Operation original, @Local(argsOnly = true) Style style) { - boolean wrapperFont = BindingFontHelper.WRAPPER_FONT.equals(style.getFont()); + //? if >=1.21.9 { + boolean wrapperFont = style.getFont() instanceof FontDescription.Resource(ResourceLocation font) + && BindingFontHelper.WRAPPER_FONT.equals(font); + //?} else { + /*boolean wrapperFont = BindingFontHelper.WRAPPER_FONT.equals(style.getFont()); + *///?} if (wrapperFont) { Optional inputText = ControlifyApi.get().getCurrentController() diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/BookEditScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/patches/bookfocusfix/BookEditScreenMixin.java similarity index 94% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/BookEditScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/patches/bookfocusfix/BookEditScreenMixin.java index a02ab77c4..b0ab58ecc 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/BookEditScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/patches/bookfocusfix/BookEditScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.patches.bookfocusfix; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import dev.isxander.controlify.Controlify; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatScreenMixin.java deleted file mode 100644 index fba917e56..000000000 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatScreenMixin.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.isxander.controlify.mixins.feature.screenkeyboard; - -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import dev.isxander.controlify.api.ControlifyApi; -import dev.isxander.controlify.controller.keyboard.NativeKeyboardComponent; -import dev.isxander.controlify.screenkeyboard.ChatKeyboardDucky; -import dev.isxander.controlify.screenkeyboard.ChatKeyboardWidget; -import dev.isxander.controlify.screenkeyboard.KeyPressConsumer; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.gui.screens.ChatScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyArg; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.Optional; - -@Mixin(ChatScreen.class) -public abstract class ChatScreenMixin extends Screen implements ChatKeyboardDucky { - @Unique - private ChatKeyboardWidget keyboard; - @Unique - private float shiftChatAmt = 0f; - - @Shadow - protected EditBox input; - - @Shadow - public abstract boolean keyPressed(int keyCode, int scanCode, int modifiers); - - protected ChatScreenMixin(Component title) { - super(title); - } - - @Inject(method = "init", at = @At("HEAD")) - private void addKeyboard(CallbackInfo ci) { - ControlifyApi.get().getCurrentController().ifPresent(c -> { - if (!ControlifyApi.get().currentInputMode().isController()) return; - if (!c.genericConfig().config().showOnScreenKeyboard) return; - - Optional nativeKeyboardOpt = c.nativeKeyboard(); - if (nativeKeyboardOpt.isPresent() && nativeKeyboardOpt.get().confObj().useNativeKeyboard) { - NativeKeyboardComponent nativeKeyboard = nativeKeyboardOpt.get(); - - this.shiftChatAmt = nativeKeyboard.getKeyboardHeight(); - nativeKeyboard.open(); - } else { - this.shiftChatAmt = 0.5f; - int keyboardHeight = (int) (this.height * this.shiftChatAmt); - this.addRenderableWidget(keyboard = new ChatKeyboardWidget((ChatScreen) (Object) this, 0, this.height - keyboardHeight, this.width, keyboardHeight, KeyPressConsumer.of( - (keycode, scancode, modifiers) -> { - input.keyPressed(keycode, scancode, modifiers); - this.keyPressed(keycode, scancode, modifiers); - }, - (codePoint, modifiers) -> { - this.charTyped(codePoint, modifiers); - input.charTyped(codePoint, modifiers); - } - ))); - } - }); - } - - @ModifyArg(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/ChatScreen$1;(Lnet/minecraft/client/gui/screens/ChatScreen;Lnet/minecraft/client/gui/Font;IIIILnet/minecraft/network/chat/Component;)V"), index = 3) - private int modifyInputBoxY(int y) { - return (int) (y - this.height * this.shiftChatAmt); - } - - @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;fill(IIIII)V"), index = 1) - private int modifyInputBoxBackgroundY(int y) { - return (int) (y - this.height * this.shiftChatAmt); - } - - @ModifyExpressionValue(method = "init", at = @At(value = "CONSTANT", args = "intValue=10")) - private int modifyMaxSuggestionCount(int count) { - return shiftChatAmt > 0 ? 8 : count; - } - - @Override - public float controlify$keyboardShiftAmount() { - return this.shiftChatAmt; - } -} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/CommandSuggestionsMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/CommandSuggestionsMixin.java deleted file mode 100644 index bd8c40aea..000000000 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/CommandSuggestionsMixin.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.isxander.controlify.mixins.feature.screenkeyboard; - -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import dev.isxander.controlify.screenkeyboard.ChatKeyboardDucky; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.CommandSuggestions; -import net.minecraft.client.gui.screens.ChatScreen; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; - -@Mixin(CommandSuggestions.class) -public class CommandSuggestionsMixin { - @Shadow - @Final - Minecraft minecraft; - - @ModifyExpressionValue(method = {"renderUsage", "showSuggestions"}, at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screens/Screen;height:I")) - private int modifyUsageHeight(int height) { - if (minecraft.screen instanceof ChatScreen chat) - return (int) (height * (1 - ChatKeyboardDucky.getKeyboardShiftAmount(chat))); - return height; - } -} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/MinecraftMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/MinecraftMixin.java index 6b7723861..212023607 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/MinecraftMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/MinecraftMixin.java @@ -1,7 +1,10 @@ package dev.isxander.controlify.mixins.feature.screenop; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; import dev.isxander.controlify.screenop.ComponentProcessorProvider; import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.keyboard.KeyboardOverlayScreen; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import org.spongepowered.asm.mixin.Mixin; @@ -15,4 +18,9 @@ public class MinecraftMixin { private void changeScreen(Screen screen, CallbackInfo ci) { ComponentProcessorProvider.REGISTRY.clearCache(); } + + @WrapWithCondition(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;removed()V")) + private boolean preventRemovingOldScreen(Screen oldScreen, @Local(argsOnly = true) Screen newScreen) { + return !(newScreen instanceof KeyboardOverlayScreen); + } } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/bundle/BundleMouseActionsMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/bundle/BundleMouseActionsMixin.java similarity index 87% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/bundle/BundleMouseActionsMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/bundle/BundleMouseActionsMixin.java index 4d673817e..07bb8861c 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/bundle/BundleMouseActionsMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/bundle/BundleMouseActionsMixin.java @@ -1,6 +1,5 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla.bundle; +package dev.isxander.controlify.mixins.feature.screenop.impl.bundle; -import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; //? if >=1.21.2 { @@ -22,6 +21,8 @@ public abstract class BundleMouseActionsMixin implements ItemSlotControllerActio } } //?} else { -/*@Mixin(Minecraft.class) +/*import net.minecraft.client.Minecraft; + +@Mixin(Minecraft.class) public class BundleMouseActionsMixin {} *///?} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatComponentMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatComponentMixin.java similarity index 94% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatComponentMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatComponentMixin.java index e279dc7aa..adc389f2f 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenkeyboard/ChatComponentMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatComponentMixin.java @@ -1,9 +1,9 @@ -package dev.isxander.controlify.mixins.feature.screenkeyboard; +package dev.isxander.controlify.mixins.feature.screenop.impl.chat; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import dev.isxander.controlify.screenkeyboard.ChatKeyboardDucky; +import dev.isxander.controlify.screenop.keyboard.ChatKeyboardDucky; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.ChatComponent; import net.minecraft.client.gui.screens.ChatScreen; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatScreenMixin.java new file mode 100644 index 000000000..2d8ebfadc --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/ChatScreenMixin.java @@ -0,0 +1,163 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.chat; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.controlify.api.ControlifyApi; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.ChatScreenProcessor; +import dev.isxander.controlify.screenop.keyboard.*; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(ChatScreen.class) +public abstract class ChatScreenMixin extends Screen implements ScreenProcessorProvider, MixinInputTarget, ChatKeyboardDucky { + + @Shadow protected EditBox input; + @Shadow CommandSuggestions commandSuggestions; + @Shadow public abstract boolean keyPressed(int keyCode, int scanCode, int modifiers); + + @Unique private KeyboardWidget keyboard; + @Unique private float shiftChatAmt = 0f; + @Unique private final ChatScreenProcessor screenProcessor = new ChatScreenProcessor( + (ChatScreen) (Object) this, + () -> this.input, + () -> this.keyboard, + () -> (ChatScreenProcessor.CmdSuggestionsController) this.commandSuggestions + ); + + protected ChatScreenMixin(Component title) { + super(title); + } + + @Inject(method = "init", at = @At("HEAD")) + private void addKeyboard(CallbackInfo ci) { + this.shiftChatAmt = 0f; + + ControlifyApi.get().getCurrentController().ifPresent(c -> { + // if the keyboard is already present, re-add it even if we're in kb/m mode since + // setting fullscreen will turn it to that mode + if (!ControlifyApi.get().currentInputMode().isController() && this.keyboard == null) return; + if (!c.genericConfig().config().showOnScreenKeyboard) return; + + this.shiftChatAmt = 0.5f; + int keyboardHeight = (int) (this.height * this.shiftChatAmt); + this.addRenderableWidget(this.keyboard = new KeyboardWidget(0, this.height - keyboardHeight, this.width, keyboardHeight, KeyboardLayouts.full(), this)); + }); + } + + @ModifyArg(method = "setInitialFocus", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/ChatScreen;setInitialFocus(Lnet/minecraft/client/gui/components/events/GuiEventListener;)V")) + private GuiEventListener modifyInitialFocus(GuiEventListener editBox) { + return this.keyboard != null ? this.keyboard : editBox; + } + + @Definition(id = "height", field = "Lnet/minecraft/client/gui/screens/ChatScreen;height:I") + @Definition(id = "width", field = "Lnet/minecraft/client/gui/screens/ChatScreen;width:I") + // EditBox can't be referenced here because it's an anonymous subclass that doesn't have a concrete Class type + @Expression("new ?(?, ?, 4, @(this.height - 12), this.width - 4, 12, ?)") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private int modifyInputBoxY(int y) { + return this.height - (int) (this.height * this.shiftChatAmt) - 12; + } + + @Definition(id = "fill", method = "Lnet/minecraft/client/gui/GuiGraphics;fill(IIIII)V") + @Definition(id = "height", field = "Lnet/minecraft/client/gui/screens/ChatScreen;height:I") + @Definition(id = "width", field = "Lnet/minecraft/client/gui/screens/ChatScreen;width:I") + @Expression("?.fill(2, @(this.height - 14), this.width - 2, this.height - 2, ?)") + @ModifyExpressionValue(method = "render", at = @At("MIXINEXTRAS:EXPRESSION")) + private int modifyInputBoxBackgroundTop(int y) { + return this.input.getY() - 2; + } + + @Definition(id = "fill", method = "Lnet/minecraft/client/gui/GuiGraphics;fill(IIIII)V") + @Definition(id = "height", field = "Lnet/minecraft/client/gui/screens/ChatScreen;height:I") + @Definition(id = "width", field = "Lnet/minecraft/client/gui/screens/ChatScreen;width:I") + @Expression("?.fill(2, this.height - 14, this.width - 2, @(this.height - 2), ?)") + @ModifyExpressionValue(method = "render", at = @At("MIXINEXTRAS:EXPRESSION")) + private int modifyInputBoxBackgroundBottom(int y) { + return this.input.getBottom() - 2; + } + + @Definition(id = "CommandSuggestions", type = CommandSuggestions.class) + @Expression("new CommandSuggestions(?, ?, ?, ?, ?, ?, ?, @(10), ?, ?)") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private int modifyMaxSuggestionCount(int count) { + return shiftChatAmt > 0 ? 8 : count; + } + + @Override + public float controlify$keyboardShiftAmount() { + return this.shiftChatAmt; + } + + @Override + public boolean controlify$supportsCharInput() { + return true; + } + + @Override + public boolean controlify$acceptChar(char ch, int modifiers) { + this.input.charTyped(ch, modifiers); + return true; + } + + @Override + public boolean controlify$supportsKeyCodeInput() { + return true; + } + + @Override + public boolean controlify$acceptKeyCode(int keycode, int scancode, int modifiers) { + boolean bypassInput = List.of( + InputConstants.KEY_RETURN, + InputConstants.KEY_ESCAPE + ).contains(keycode); + + if (bypassInput) { + return ((ChatScreen) (Object) this).keyPressed(keycode, scancode, modifiers); + } + return this.input.keyPressed(keycode, scancode, modifiers); + } + + @Override + public boolean controlify$supportsCopying() { + return true; + } + + @Override + public boolean controlify$copy() { + minecraft.keyboardHandler.setClipboard(this.input.getValue()); + return true; + } + + @Override + public boolean controlify$supportsCursorMovement() { + return true; + } + + @Override + public boolean controlify$moveCursor(int amount) { + this.input.moveCursor(amount, false); + return true; + } + + @Override + public ChatScreenProcessor screenProcessor() { + return this.screenProcessor; + } + +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/CommandSuggestionsMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/CommandSuggestionsMixin.java new file mode 100644 index 000000000..198991e22 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/chat/CommandSuggestionsMixin.java @@ -0,0 +1,52 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.chat; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.isxander.controlify.screenop.keyboard.ChatKeyboardDucky; +import dev.isxander.controlify.screenop.compat.vanilla.ChatScreenProcessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.screens.ChatScreen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(CommandSuggestions.class) +public class CommandSuggestionsMixin implements ChatScreenProcessor.CmdSuggestionsController { + @Shadow + @Final + Minecraft minecraft; + + @Shadow + @Nullable + private CommandSuggestions.@Nullable SuggestionsList suggestions; + + @ModifyExpressionValue(method = {"renderUsage", "showSuggestions"}, at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screens/Screen;height:I")) + private int modifyUsageHeight(int height) { + if (minecraft.screen instanceof ChatScreen chat) + return (int) (height * (1 - ChatKeyboardDucky.getKeyboardShiftAmount(chat))); + return height; + } + + @Override + public boolean controlify$cycle(int amount) { + if (this.suggestions == null) return false; + + this.suggestions.cycle(amount); + return true; + } + + @Override + public boolean controlify$useSuggestion() { + if (this.suggestions == null) return false; + + this.suggestions.useSuggestion(); + return true; + } + + @Override + public boolean controlify$hasAvailableSuggestions() { + return this.suggestions != null; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractContainerScreenMixin.java similarity index 98% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractContainerScreenMixin.java index b98836d9d..b1ac707c7 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractContainerScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.container; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Share; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractRecipeBookScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractRecipeBookScreenMixin.java similarity index 96% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractRecipeBookScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractRecipeBookScreenMixin.java index 77098b6ed..0a8ef7749 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractRecipeBookScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/AbstractRecipeBookScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.container; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.screenop.ScreenProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreativeModeInventoryScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/CreativeModeInventoryScreenMixin.java similarity index 80% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreativeModeInventoryScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/CreativeModeInventoryScreenMixin.java index 422d1c93f..8e91dbc8d 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreativeModeInventoryScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/CreativeModeInventoryScreenMixin.java @@ -1,12 +1,9 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.container; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.compat.vanilla.CreativeModeInventoryScreenProcessor; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Inventory; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/MerchantScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/MerchantScreenMixin.java similarity index 97% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/MerchantScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/MerchantScreenMixin.java index 0754e0ee7..bb721e046 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/MerchantScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/container/MerchantScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.container; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractButtonMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractButtonMixin.java similarity index 91% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractButtonMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractButtonMixin.java index 9e509c7fc..6d4e1e6ff 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractButtonMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractButtonMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ComponentProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerEventHandlerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractContainerEventHandlerMixin.java similarity index 89% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerEventHandlerMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractContainerEventHandlerMixin.java index ccc9634a9..77a793ecd 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractContainerEventHandlerMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractContainerEventHandlerMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; import dev.isxander.controlify.screenop.CustomFocus; import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSelectionListMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSelectionListMixin.java similarity index 85% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSelectionListMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSelectionListMixin.java index 9225c3b3a..f5aef8d36 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSelectionListMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSelectionListMixin.java @@ -1,8 +1,7 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import dev.isxander.controlify.Controlify; -import dev.isxander.controlify.InputMode; import net.minecraft.client.gui.components.AbstractSelectionList; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSliderButtonMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSliderButtonMixin.java similarity index 95% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSliderButtonMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSliderButtonMixin.java index 3621f7063..9db212b6a 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSliderButtonMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/AbstractSliderButtonMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import dev.isxander.controlify.Controlify; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ContainerObjectSelectionListEntryMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/ContainerObjectSelectionListEntryMixin.java similarity index 89% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ContainerObjectSelectionListEntryMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/ContainerObjectSelectionListEntryMixin.java index e85d437ce..10804a013 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ContainerObjectSelectionListEntryMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/ContainerObjectSelectionListEntryMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; import dev.isxander.controlify.screenop.CustomFocus; import net.minecraft.client.gui.components.ContainerObjectSelectionList; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/EditBoxMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/EditBoxMixin.java new file mode 100644 index 000000000..238e57a1c --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/elements/EditBoxMixin.java @@ -0,0 +1,103 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.elements; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; +import dev.isxander.controlify.api.ControlifyApi; +import dev.isxander.controlify.bindings.ControlifyBindings; +import dev.isxander.controlify.font.BindingFontHelper; +import dev.isxander.controlify.screenop.ComponentProcessor; +import dev.isxander.controlify.screenop.ComponentProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.EditBoxComponentProcessor; +import dev.isxander.controlify.screenop.keyboard.ComponentKeyboardBehaviour; +import dev.isxander.controlify.screenop.keyboard.CommonKeyboardHints; +import dev.isxander.controlify.screenop.keyboard.KeyboardOverlayScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(EditBox.class) +public abstract class EditBoxMixin extends AbstractWidget implements ComponentProcessorProvider { + + @Shadow private @Nullable Component hint; + @Shadow private @Nullable String suggestion; + @Shadow private int textX; + @Shadow private int textY; + @Shadow @Final private Font font; + + @Unique private final EditBoxComponentProcessor processor = new EditBoxComponentProcessor( + (EditBox) (Object) this, + Minecraft.getInstance().getWindow().getGuiScaledWidth(), + Minecraft.getInstance().getWindow().getGuiScaledHeight() + ); + + public EditBoxMixin(int x, int y, int width, int height, Component message) { + super(x, y, width, height, message); + } + + /** + * Renders some hint text when the edit box is focused to indicate + * that pressing GUI_PRESS will open the on-screen keyboard. + * If the edit box has some text, the hint will be minimally rendered + */ + @ModifyExpressionValue(method = "renderWidget", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Font;plainSubstrByWidth(Ljava/lang/String;I)Ljava/lang/String;")) + private String renderHintText(String renderedValue, @Local(argsOnly = true) GuiGraphics graphics, @Share("renderHint") LocalBooleanRef renderHint) { + renderHint.set(false); + + ControlifyApi.get().getCurrentController().ifPresent(controller -> { + if (this.isFocused() + && controller.genericConfig().config().showOnScreenKeyboard + && controller.genericConfig().config().showScreenGuides + && ControlifyApi.get().currentInputMode().isController() + && !(Minecraft.getInstance().screen instanceof KeyboardOverlayScreen) + && processor.getKeyboardBehaviour() instanceof ComponentKeyboardBehaviour.Handled + ) { + if (renderedValue.isEmpty() + && this.hint == null + && this.suggestion == null + ) { + renderHint.set(true); + graphics.drawString(font, CommonKeyboardHints.OPEN_KEYBOARD.getComponent(), this.textX, this.textY, 0xFFAAAAAA); + } else { + var component = BindingFontHelper.binding(ControlifyBindings.GUI_PRESS); + int width = font.width(component); + + graphics.drawString( + font, component, + this.getX() - 2 - width, + this.textY, + -1 + ); + } + } + }); + + return renderedValue; + } + + @Definition(id = "isFocused", method = "Lnet/minecraft/client/gui/components/EditBox;isFocused()Z") + @Definition(id = "getMillis", method = "Lnet/minecraft/Util;getMillis()J") + @Definition(id = "focusedTime", field = "Lnet/minecraft/client/gui/components/EditBox;focusedTime:J") + @Expression("(getMillis() - this.focusedTime) / 300 % 2 == 0") + @ModifyExpressionValue(method = "renderWidget", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean preventShowingCursor(boolean showCursor, @Share("renderHint") LocalBooleanRef renderHint) { + return showCursor && !renderHint.get(); + } + + @Override + public ComponentProcessor componentProcessor() { + return processor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/CreateWorldScreenMixin.java similarity index 97% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/CreateWorldScreenMixin.java index 7edcbac69..7e8e83f98 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/CreateWorldScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import dev.isxander.controlify.api.buttonguide.ButtonGuideApi; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/DirectJoinServerScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/DirectJoinServerScreenMixin.java new file mode 100644 index 000000000..95c66261b --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/DirectJoinServerScreenMixin.java @@ -0,0 +1,48 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.AddServerLikeScreenProcessor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.DirectJoinServerScreen; +import net.minecraft.client.gui.screens.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(DirectJoinServerScreen.class) +public class DirectJoinServerScreenMixin implements ScreenProcessorProvider { + @Shadow + private EditBox ipEdit; + @Shadow + private Button selectButton; + @Unique + private Button cancelButton; + + @Unique + private final AddServerLikeScreenProcessor screenProcessor = new AddServerLikeScreenProcessor( + (Screen) (Object) this, + () -> this.ipEdit, + () -> this.selectButton, + () -> this.cancelButton + ); + + @Definition(id = "builder", method = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;") + @Definition(id = "GUI_CANCEL", field = "Lnet/minecraft/network/chat/CommonComponents;GUI_CANCEL:Lnet/minecraft/network/chat/Component;") + @Definition(id = "build", method = "Lnet/minecraft/client/gui/components/Button$Builder;build()Lnet/minecraft/client/gui/components/Button;") + @Expression("builder(GUI_CANCEL, ?).?(?, ?, ?, ?).build()") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private Button captureCancelButton(Button button) { + return this.cancelButton = button; + } + + @Override + public ScreenProcessor screenProcessor() { + return this.screenProcessor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/EditServerScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/EditServerScreenMixin.java new file mode 100644 index 000000000..1ea2012a7 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/EditServerScreenMixin.java @@ -0,0 +1,48 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.AddServerLikeScreenProcessor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.EditServerScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(EditServerScreen.class) +public class EditServerScreenMixin implements ScreenProcessorProvider { + + @Shadow + private EditBox ipEdit; + @Shadow + private Button addButton; + @Unique + private Button cancelButton; + + @Unique + private final AddServerLikeScreenProcessor screenProcessor = new AddServerLikeScreenProcessor( + (EditServerScreen) (Object) this, + () -> this.ipEdit, + () -> this.addButton, + () -> this.cancelButton + ); + + @Definition(id = "builder", method = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;") + @Definition(id = "GUI_CANCEL", field = "Lnet/minecraft/network/chat/CommonComponents;GUI_CANCEL:Lnet/minecraft/network/chat/Component;") + @Definition(id = "build", method = "Lnet/minecraft/client/gui/components/Button$Builder;build()Lnet/minecraft/client/gui/components/Button;") + @Expression("builder(GUI_CANCEL, ?).?(?, ?, ?, ?).build()") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private Button captureCancelButton(Button button) { + return this.cancelButton = button; + } + + @Override + public ScreenProcessor screenProcessor() { + return screenProcessor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenAccessor.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenAccessor.java similarity index 82% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenAccessor.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenAccessor.java index 08f9b0f52..6f02c53c1 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenAccessor.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenAccessor.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenMixin.java new file mode 100644 index 000000000..d56d03624 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/JoinMultiplayerScreenMixin.java @@ -0,0 +1,68 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.JoinMultiplayerScreenProcessor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(JoinMultiplayerScreen.class) +public class JoinMultiplayerScreenMixin implements ScreenProcessorProvider { + @Shadow protected ServerSelectionList serverSelectionList; + + @Unique + private Button backButton; + @Unique + private Button directConnectButton; + @Unique + private Button addServerButton; + + @Unique + private final JoinMultiplayerScreenProcessor processor = new JoinMultiplayerScreenProcessor( + (JoinMultiplayerScreen) (Object) this, + () -> this.serverSelectionList, + () -> this.backButton, + () -> this.directConnectButton, + () -> this.addServerButton + ); + + @Definition(id = "builder", method = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;") + @Definition(id = "build", method = "Lnet/minecraft/client/gui/components/Button$Builder;build()Lnet/minecraft/client/gui/components/Button;") + @Definition(id = "GUI_BACK", field = "Lnet/minecraft/network/chat/CommonComponents;GUI_BACK:Lnet/minecraft/network/chat/Component;") + @Expression("builder(GUI_BACK, ?).?(?).build()") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private Button captureBackButton(Button button) { + return this.backButton = button; + } + + @Definition(id = "builder", method = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;") + @Definition(id = "translatable", method = "Lnet/minecraft/network/chat/Component;translatable(Ljava/lang/String;)Lnet/minecraft/network/chat/MutableComponent;") + @Definition(id = "build", method = "Lnet/minecraft/client/gui/components/Button$Builder;build()Lnet/minecraft/client/gui/components/Button;") + @Expression("builder(translatable('selectServer.direct'), ?).?(?).build()") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private Button captureDirectConnectButton(Button button) { + return this.directConnectButton = button; + } + + @Definition(id = "builder", method = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;") + @Definition(id = "translatable", method = "Lnet/minecraft/network/chat/Component;translatable(Ljava/lang/String;)Lnet/minecraft/network/chat/MutableComponent;") + @Definition(id = "build", method = "Lnet/minecraft/client/gui/components/Button$Builder;build()Lnet/minecraft/client/gui/components/Button;") + @Expression("builder(translatable('selectServer.add'), ?).?(?).build()") + @ModifyExpressionValue(method = "init", at = @At("MIXINEXTRAS:EXPRESSION")) + private Button captureAddServerButton(Button button) { + return this.addServerButton = button; + } + + @Override + public ScreenProcessor screenProcessor() { + return processor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/LanguageSelectionListEntryMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/LanguageSelectionListEntryMixin.java similarity index 93% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/LanguageSelectionListEntryMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/LanguageSelectionListEntryMixin.java index 47fae08da..8375ac033 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/LanguageSelectionListEntryMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/LanguageSelectionListEntryMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ComponentProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/OptionsSubScreenAccessor.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/OptionsSubScreenAccessor.java similarity index 81% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/OptionsSubScreenAccessor.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/OptionsSubScreenAccessor.java index 7a912a0c6..78359c700 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/OptionsSubScreenAccessor.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/OptionsSubScreenAccessor.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import net.minecraft.client.gui.screens.options.OptionsSubScreen; import net.minecraft.client.gui.screens.Screen; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenAccessor.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenAccessor.java similarity index 78% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenAccessor.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenAccessor.java index 7b44d078d..1dabd05e1 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenAccessor.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenAccessor.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import net.minecraft.client.gui.screens.PauseScreen; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenMixin.java similarity index 89% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenMixin.java index e733cba09..29e1a1a1d 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/PauseScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/PauseScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.screenop.ScreenProcessorProvider; @@ -6,7 +6,6 @@ import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.PauseScreen; import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenAccessor.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenAccessor.java similarity index 86% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenAccessor.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenAccessor.java index a1ffe563a..9e934e443 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenAccessor.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenAccessor.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenMixin.java similarity index 97% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenMixin.java index bec3e8774..8d1060e3d 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/SelectWorldScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ServerSelectionListEntryMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/ServerSelectionListEntryMixin.java similarity index 92% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ServerSelectionListEntryMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/ServerSelectionListEntryMixin.java index 2b22986a0..22faa197d 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/ServerSelectionListEntryMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/ServerSelectionListEntryMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ComponentProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TabNavigationBarAccessor.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TabNavigationBarAccessor.java similarity index 87% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TabNavigationBarAccessor.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TabNavigationBarAccessor.java index e877c1b87..efd7c6199 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TabNavigationBarAccessor.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TabNavigationBarAccessor.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import com.google.common.collect.ImmutableList; import net.minecraft.client.gui.components.tabs.Tab; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TitleScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TitleScreenMixin.java similarity index 90% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TitleScreenMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TitleScreenMixin.java index fa2fea230..4b5c32ab5 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/TitleScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/TitleScreenMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.screenop.ScreenProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/WorldSelectionListEntryMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/WorldSelectionListEntryMixin.java similarity index 91% rename from src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/WorldSelectionListEntryMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/WorldSelectionListEntryMixin.java index 5cbdb27e5..c7e4784ae 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/WorldSelectionListEntryMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/outofgame/WorldSelectionListEntryMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; +package dev.isxander.controlify.mixins.feature.screenop.impl.outofgame; import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ComponentProcessorProvider; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/AbstractSignEditScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/AbstractSignEditScreenMixin.java new file mode 100644 index 000000000..673fe30ab --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/AbstractSignEditScreenMixin.java @@ -0,0 +1,133 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.sign; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.controlify.api.ControlifyApi; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.AbstractSignEditScreenProcessor; +import dev.isxander.controlify.screenop.keyboard.KeyboardLayouts; +import dev.isxander.controlify.screenop.keyboard.KeyboardWidget; +import dev.isxander.controlify.screenop.keyboard.MixinInputTarget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.font.TextFieldHelper; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(AbstractSignEditScreen.class) +public abstract class AbstractSignEditScreenMixin extends Screen implements ScreenProcessorProvider, MixinInputTarget { + + @Shadow private @Nullable TextFieldHelper signField; + @Shadow @Final private String[] messages; + @Shadow private int line; + @Shadow protected abstract void onDone(); + @Shadow @Final protected SignBlockEntity sign; + + @Unique + private KeyboardWidget keyboard; + @Unique + private final AbstractSignEditScreenProcessor screenProcessor = new AbstractSignEditScreenProcessor( + (AbstractSignEditScreen) (Object) this, + direction -> { + this.line = this.line + direction & 3; + this.signField.setCursorToEnd(); + }, + () -> this.sign, + () -> keyboard + ); + + + protected AbstractSignEditScreenMixin(Component title) { + super(title); + } + + @Inject(method = "init", at = @At("HEAD")) + private void addKeyboard(CallbackInfo ci) { + ControlifyApi.get().getCurrentController().ifPresent(c -> { + // if the keyboard is already present, re-add it even if we're in kb/m mode since + // setting fullscreen will turn it to that mode + if (!ControlifyApi.get().currentInputMode().isController() && this.keyboard == null) return; + if (!c.genericConfig().config().showOnScreenKeyboard) return; + + int keyboardHeight = (int) (this.height * 0.5f); + this.addRenderableWidget(this.keyboard = new KeyboardWidget(0, this.height - keyboardHeight, this.width, keyboardHeight, KeyboardLayouts.simple(), this)); + }); + } + + @WrapWithCondition( + method = "init", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/screens/inventory/AbstractSignEditScreen;addRenderableWidget(Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;" + ) + ) + private boolean shouldAddDoneButton(AbstractSignEditScreen instance, GuiEventListener guiEventListener) { + return this.keyboard == null; + } + + @Override + public boolean controlify$supportsCharInput() { + return true; + } + + @Override + public boolean controlify$acceptChar(char ch, int modifiers) { + return this.signField != null && this.signField.charTyped(ch); + } + + @Override + public boolean controlify$supportsKeyCodeInput() { + return true; + } + + @Override + public boolean controlify$acceptKeyCode(int keycode, int scancode, int modifiers) { + if (keycode == InputConstants.KEY_RETURN) { + this.onDone(); + return true; + } + + return this.signField != null && this.signField.keyPressed(keycode); + } + + @Override + public boolean controlify$supportsCopying() { + return true; + } + + @Override + public boolean controlify$copy() { + if (this.signField == null) return false; + + minecraft.keyboardHandler.setClipboard(this.messages[this.line]); + return true; + } + + @Override + public boolean controlify$supportsCursorMovement() { + return true; + } + + @Override + public boolean controlify$moveCursor(int amount) { + if (this.signField == null) return false; + + this.signField.moveByChars(amount); + return true; + } + + @Override + public ScreenProcessor screenProcessor() { + return screenProcessor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/SignEditScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/SignEditScreenMixin.java new file mode 100644 index 000000000..d498c6e2b --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/impl/sign/SignEditScreenMixin.java @@ -0,0 +1,42 @@ +package dev.isxander.controlify.mixins.feature.screenop.impl.sign; + +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; +import net.minecraft.client.gui.screens.inventory.SignEditScreen; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(SignEditScreen.class) +public abstract class SignEditScreenMixin extends AbstractSignEditScreen { + + public SignEditScreenMixin(SignBlockEntity sign, boolean isFrontText, boolean isFiltered) { + super(sign, isFrontText, isFiltered); + } + + @ModifyReturnValue(method = "getSignYOffset", at = @At("RETURN")) + private float modifySignY(float original) { + return original - calculateOverlap(); + } + + @Definition(id = "submitSignRenderState", method = "Lnet/minecraft/client/gui/GuiGraphics;submitSignRenderState(Lnet/minecraft/client/model/Model$Simple;FLnet/minecraft/world/level/block/state/properties/WoodType;IIII)V") + @Expression("?.submitSignRenderState(?, ?, ?, ?, @(66), ?, @(168))") + @ModifyExpressionValue(method = "renderSignBackground", at = @At("MIXINEXTRAS:EXPRESSION")) + private int modifySignRenderY(int original) { + return (int) (original - calculateOverlap()); + } + + @Unique + private float calculateOverlap() { + float original = 90f; + + float keyboardStart = this.height / 2f; + float signEnd = original + 90; + return Math.max(0, signEnd - keyboardStart); + } + +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSignEditScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSignEditScreenMixin.java deleted file mode 100644 index 6bd78966e..000000000 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/AbstractSignEditScreenMixin.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; - -import dev.isxander.controlify.screenop.ScreenProcessor; -import dev.isxander.controlify.screenop.ScreenProcessorProvider; -import dev.isxander.controlify.screenop.compat.vanilla.AbstractSignEditScreenProcessor; -import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; - -@Mixin(AbstractSignEditScreen.class) -public class AbstractSignEditScreenMixin implements ScreenProcessorProvider { - @Unique private final AbstractSignEditScreenProcessor screenProcessor = - new AbstractSignEditScreenProcessor((AbstractSignEditScreen) (Object) this); - - @Override - public ScreenProcessor screenProcessor() { - return screenProcessor; - } -} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/EditBoxMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/EditBoxMixin.java deleted file mode 100644 index ccd9b9339..000000000 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/EditBoxMixin.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; - -import dev.isxander.controlify.screenop.ComponentProcessor; -import dev.isxander.controlify.screenop.ComponentProcessorProvider; -import dev.isxander.controlify.screenop.compat.vanilla.EditBoxComponentProcessor; -import net.minecraft.client.gui.components.EditBox; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; - -@Mixin(EditBox.class) -public class EditBoxMixin implements ComponentProcessorProvider { - @Unique private final ComponentProcessor processor = new EditBoxComponentProcessor(); - - @Override - public ComponentProcessor componentProcessor() { - return processor; - } -} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenMixin.java deleted file mode 100644 index e437f6dea..000000000 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/JoinMultiplayerScreenMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.isxander.controlify.mixins.feature.screenop.vanilla; - -import dev.isxander.controlify.screenop.ScreenProcessor; -import dev.isxander.controlify.screenop.ScreenProcessorProvider; -import dev.isxander.controlify.screenop.compat.vanilla.JoinMultiplayerScreenProcessor; -import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; -import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; - -@Mixin(JoinMultiplayerScreen.class) -public class JoinMultiplayerScreenMixin implements ScreenProcessorProvider { - @Shadow protected ServerSelectionList serverSelectionList; - - @Unique private final JoinMultiplayerScreenProcessor controlify$processor - = new JoinMultiplayerScreenProcessor((JoinMultiplayerScreen) (Object) this, () -> serverSelectionList); - - @Override - public ScreenProcessor screenProcessor() { - return controlify$processor; - } -} diff --git a/src/main/java/dev/isxander/controlify/screenkeyboard/ChatKeyboardWidget.java b/src/main/java/dev/isxander/controlify/screenkeyboard/ChatKeyboardWidget.java deleted file mode 100644 index 3311b8ab1..000000000 --- a/src/main/java/dev/isxander/controlify/screenkeyboard/ChatKeyboardWidget.java +++ /dev/null @@ -1,90 +0,0 @@ -package dev.isxander.controlify.screenkeyboard; - -import com.mojang.blaze3d.platform.InputConstants; -import dev.isxander.controlify.bindings.ControlifyBindings; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.lwjgl.glfw.GLFW; - -public class ChatKeyboardWidget extends KeyboardWidget { - - public ChatKeyboardWidget(Screen screen, int x, int y, int width, int height, KeyPressConsumer keyPressConsumer) { - super(screen, x, y, width, height, keyPressConsumer); - } - - @Override - protected void arrangeKeys() { - var builder = new KeyLayoutBuilder<>(14, 5, this); - - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_ESCAPE, "Esc"), null), 1f); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_1, '1'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_2, '2'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_3, '3'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_4, '4'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_5, '5'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_6, '6'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_7, '7'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_8, '8'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_9, '9'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_0, '0'), null), 1); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_BACKSPACE, "Backspace"), ControlifyBindings.GUI_ABSTRACT_ACTION_1), 3f); - - builder.nextRow(); - - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_TAB, "Tab"), null), 2f); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_Q, 'q'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_W, 'w'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_E, 'e'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_R, 'r'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_T, 't'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_Y, 'y'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_U, 'u'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_I, 'i'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_O, 'o'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_P, 'p'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_BACKSLASH, '\\'), null), 2f); - - builder.nextRow(); - - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_CAPSLOCK, "Caps"), null), 2f); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_A, 'a'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_S, 's'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_D, 'd'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_F, 'f'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_G, 'g'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_H, 'h'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_J, 'j'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_K, 'k'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_L, 'l'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_APOSTROPHE, '\'', 0, InputConstants.KEY_APOSTROPHE, '"', GLFW.GLFW_MOD_SHIFT), null), 1); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_RETURN, "Enter"), ControlifyBindings.GUI_ABSTRACT_ACTION_2), 2f); - - builder.nextRow(); - - builder.key(Key.builder(new KeyFunction((screen, key) -> { - shiftMode = !shiftMode; - key.setHighlighted(shiftMode); - }, Key.ForegroundRenderer.text(Component.literal("Shift"))).copyShifted(), ControlifyBindings.GUI_ABSTRACT_ACTION_3), 2f); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_Z, 'z'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_X, 'x'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_C,'c'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_V, 'v'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_B, 'b'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_N, 'n'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_M, 'm'), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_COMMA, ',', 0, InputConstants.KEY_PERIOD, '.', 0), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_1, '!', GLFW.GLFW_MOD_SHIFT, InputConstants.KEY_SLASH, '?', GLFW.GLFW_MOD_SHIFT), null), 1); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_SLASH, '/', 0, InputConstants.KEY_BACKSLASH, '\\', 0), null), 1); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_UP, "\u2191"), null), 1f); - - builder.nextRow(); - - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_LCONTROL, "Ctrl"), null), 2f); - builder.key(Key.builder(KeyFunction.ofChar(InputConstants.KEY_SPACE, ' '), null), 9f); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_LEFT, "\u2190"), null), 1f); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_DOWN, "\u2193"), null), 1f); - builder.key(Key.builder(KeyFunction.ofRegularKey(InputConstants.KEY_RIGHT, "\u2192"), null), 1f); - - builder.build(keys::add); - } -} diff --git a/src/main/java/dev/isxander/controlify/screenkeyboard/KeyPressConsumer.java b/src/main/java/dev/isxander/controlify/screenkeyboard/KeyPressConsumer.java deleted file mode 100644 index 2cdd1f6f9..000000000 --- a/src/main/java/dev/isxander/controlify/screenkeyboard/KeyPressConsumer.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.isxander.controlify.screenkeyboard; - -public interface KeyPressConsumer { - void acceptKeyCode(int keycode, int scancode, int modifiers); - - void acceptChar(char codePoint, int modifiers); - - static KeyPressConsumer of(KeyCodeConsumer keyCodeConsumer, CharConsumer charConsumer) { - return new KeyPressConsumer() { - @Override - public void acceptKeyCode(int keycode, int scancode, int modifiers) { - keyCodeConsumer.accept(keycode, scancode, modifiers); - } - - @Override - public void acceptChar(char codePoint, int modifiers) { - charConsumer.accept(codePoint, modifiers); - } - }; - } - - interface KeyCodeConsumer { - void accept(int keycode, int scancode, int modifiers); - } - - interface CharConsumer { - void accept(char codePoint, int modifiers); - } -} diff --git a/src/main/java/dev/isxander/controlify/screenkeyboard/KeyboardWidget.java b/src/main/java/dev/isxander/controlify/screenkeyboard/KeyboardWidget.java deleted file mode 100644 index beeb5410b..000000000 --- a/src/main/java/dev/isxander/controlify/screenkeyboard/KeyboardWidget.java +++ /dev/null @@ -1,401 +0,0 @@ -package dev.isxander.controlify.screenkeyboard; - -import com.mojang.datafixers.util.Pair; -import dev.isxander.controlify.api.ControlifyApi; -import dev.isxander.controlify.api.bind.InputBinding; -import dev.isxander.controlify.api.bind.InputBindingSupplier; -import dev.isxander.controlify.bindings.ControlifyBindings; -import dev.isxander.controlify.controller.ControllerEntity; -import dev.isxander.controlify.screenop.ComponentProcessor; -import dev.isxander.controlify.screenop.ScreenControllerEventListener; -import dev.isxander.controlify.screenop.ScreenProcessor; -import dev.isxander.controlify.screenop.ScreenProcessorProvider; -import dev.isxander.controlify.utils.render.*; -import dev.isxander.controlify.utils.CUtil; -import dev.isxander.controlify.utils.HoldRepeatHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.events.ContainerEventHandler; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.lwjgl.glfw.GLFW; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public abstract class KeyboardWidget extends AbstractWidget implements ContainerEventHandler { - protected final List keys; - protected final KeyPressConsumer keyPressConsumer; - - protected boolean shiftMode; - - private @Nullable GuiEventListener focused; - private boolean isDragging; - - private final Screen containingScreen; - - public KeyboardWidget(Screen screen, int x, int y, int width, int height, KeyPressConsumer keyPressConsumer) { - super(x, y, width, height, Component.literal("On-screen keyboard")); - this.containingScreen = screen; - this.keyPressConsumer = keyPressConsumer; - this.keys = new ArrayList<>(); - arrangeKeys(); - } - - protected abstract void arrangeKeys(); - - @Override - protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { - for (T key : keys) { - // vanilla widget render does other stuff like mouse hover update etc - // render method of keys are empty - this doesn't actually do any rendering - key.render(guiGraphics, mouseX, mouseY, partialTick); - } - - // draw in a managed context so we can batch render calls - // everything within here is rendered in a single draw call - Blit.batchDraw(guiGraphics, () -> { - guiGraphics.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), 0x80000000); - guiGraphics./*? if >=1.21.9 {*/submitOutline/*?} else {*//*renderOutline*//*?}*/(getX(), getY(), getWidth(), getHeight(), 0xFFAAAAAA); - - for (T key : keys) { - // every key background is rendered into the same vertex buffer to upload at once - key.renderKeyBackground(guiGraphics, mouseX, mouseY, partialTick); - } - - // renders all foreground after background to prevent context switching - for (T key : keys) { - // text rendering is batched by default in managed mode - key.renderKeyForeground(guiGraphics, mouseX, mouseY, partialTick); - } - }); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { - - } - - public static class Key extends AbstractWidget implements ComponentProcessor, ScreenControllerEventListener { - public static final ResourceLocation SPRITE = CUtil.rl("keyboard/key"); - - private final KeyboardWidget keyboard; - - private final KeyFunction normalFunction; - private final KeyFunction shiftedFunction; - - private boolean highlighted; - - private final HoldRepeatHelper holdRepeatHelper; - - private final InputBindingSupplier shortcutPressBind; - private boolean shortcutPressed; - - public Key(Screen screen, int x, int y, int width, int height, KeyFunction normalFunction, @Nullable KeyFunction shiftedFunction, KeyboardWidget keyboard, @Nullable InputBindingSupplier shortcutPressBind) { - super(x, y, width, height, Component.literal("Key")); - this.keyboard = keyboard; - this.normalFunction = normalFunction; - if (shiftedFunction != null) - this.shiftedFunction = shiftedFunction; - else - this.shiftedFunction = normalFunction; - this.holdRepeatHelper = new HoldRepeatHelper(10, 2); - this.shortcutPressBind = shortcutPressBind; - ScreenProcessorProvider.provide(screen).addEventListener(this); - } - - public Key(Screen screen, int x, int y, int width, int height, Pair functions, KeyboardWidget keyboard, @Nullable InputBindingSupplier shortcutPressBind) { - this(screen, x, y, width, height, functions.getFirst(), functions.getSecond(), keyboard, shortcutPressBind); - } - - protected void renderKeyBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - Blit.sprite(graphics, SPRITE, getX() + 1, getY() + 1, getWidth() - 2, getHeight() - 2); - } - - protected void renderKeyForeground(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - if (keyboard.shiftMode) { - shiftedFunction.renderer.render(graphics, mouseX, mouseY, partialTick, this); - } else { - normalFunction.renderer.render(graphics, mouseX, mouseY, partialTick, this); - } - - if (isHoveredOrFocused() || shortcutPressed) { - graphics./*? if >=1.21.9 {*/submitOutline/*?} else {*//*renderOutline*//*?}*/(getX(), getY(), getWidth(), getHeight(), -1); - } else { - holdRepeatHelper.reset(); - } - } - - @Override - protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - // custom rendered above - } - - @Override - public boolean overrideControllerButtons(ScreenProcessor screen, ControllerEntity controller) { - if (holdRepeatHelper.shouldAction(ControlifyBindings.GUI_PRESS.on(controller))) { - onPress(); - holdRepeatHelper.onNavigate(); - } - - // do not allow any other input on keys to be processed - return true; - } - - @Override - public void onControllerInput(ControllerEntity controller) { - if (shortcutPressBind == null) return; - - InputBinding shortcutBind = shortcutPressBind.on(controller); - - shortcutPressed = shortcutBind.digitalNow(); - - if (holdRepeatHelper.shouldAction(shortcutBind)) { - onPress(); - holdRepeatHelper.onNavigate(); - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button /*? if >=1.21.9 {*/ ,boolean doubleClick /*?}*/) { - if (isMouseOver(mouseX, mouseY)) { - onPress(); - return true; - } - return false; - } - - protected void onPress() { - if (keyboard.shiftMode) { - shiftedFunction.consumer.accept(keyboard.keyPressConsumer, this); - } else { - normalFunction.consumer.accept(keyboard.keyPressConsumer, this); - } - } - - public Component modifyKeyName(Component name) { - Optional controller = ControlifyApi.get().getCurrentController() - .filter(c -> ControlifyApi.get().currentInputMode().isController()); - if (shortcutPressBind != null && controller.isPresent()) { - InputBinding binding = shortcutPressBind.on(controller.get()); - - return Component.empty() - .append(binding.inputGlyph()) - .append(name); - } - - return name; - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { - - } - - public void setHighlighted(boolean highlighted) { - this.highlighted = highlighted; - } - - public boolean isHighlighted() { - return highlighted; - } - - public static KeyBuilder builder(Pair functions, @Nullable InputBindingSupplier shortcutPressBind) { - return (screen, x, y, w, h, kb) -> new Key(screen, x, y, w, h, functions.getFirst(), functions.getSecond(), kb, shortcutPressBind); - } - - public static KeyBuilder builder(KeyFunction normalFunction, KeyFunction shiftedFunction, @Nullable InputBindingSupplier shortcutPressBind) { - return (screen, x, y, w, h, kb) -> new Key(screen, x, y, w, h, normalFunction, shiftedFunction, kb, shortcutPressBind); - } - - public interface ForegroundRenderer { - void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick, Key key); - - static ForegroundRenderer text(Component text) { - return (guiGraphics, mouseX, mouseY, partialTick, key) -> { - guiGraphics.drawCenteredString(Minecraft.getInstance().font, key.modifyKeyName(text), key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2 - 4, 0xFFFFFFFF); - }; - } - } - } - - public record KeyFunction(BiConsumer consumer, Key.ForegroundRenderer renderer) { - public static Pair ofChar(int normalKeyCode, char normalChar, int normalModifier, int shiftedKeyCode, char shiftedChar, int shiftedModifier) { - return Pair.of( - new KeyFunction((screen, key) -> { - screen.acceptKeyCode(normalKeyCode, 0, normalModifier); - screen.acceptChar(normalChar, normalModifier); - }, Key.ForegroundRenderer.text(Component.literal(String.valueOf(normalChar)))), - new KeyFunction((screen, key) -> { - screen.acceptKeyCode(shiftedKeyCode, 0, shiftedModifier); - screen.acceptChar(shiftedChar, shiftedModifier); - }, Key.ForegroundRenderer.text(Component.literal(String.valueOf(shiftedChar))) - ) - ); - } - - public static Pair ofChar(int keyCode, char ch) { - return ofChar(keyCode, Character.toLowerCase(ch), 0, keyCode, Character.toUpperCase(ch), GLFW.GLFW_MOD_SHIFT); - } - - public static Pair ofRegularKey(int keycode, String normal) { - KeyFunction function = new KeyFunction((screen, key) -> screen.acceptKeyCode(keycode, 0, 0), Key.ForegroundRenderer.text(Component.literal(normal))); - return Pair.of(function, function); - } - - public static Pair ofShiftableKey(int normalKeyCode, int normalModifier, String normalName, int shiftKeyCode, int shiftModifier, String shiftName) { - return Pair.of( - new KeyFunction((screen, key) -> screen.acceptKeyCode(normalKeyCode, 0, normalModifier), Key.ForegroundRenderer.text(Component.literal(normalName))), - new KeyFunction((screen, key) -> screen.acceptKeyCode(shiftKeyCode, 0, shiftModifier), Key.ForegroundRenderer.text(Component.literal(shiftName))) - ); - } - - public static Pair ofShiftableKey(int keyCode, String normal, String shift) { - return ofShiftableKey(keyCode, 0, normal, keyCode, GLFW.GLFW_MOD_SHIFT, shift); - } - - public Pair copyShifted() { - return Pair.of(this, this); - } - } - - public static class KeyLayoutBuilder { - private final List>> layout; - private final float maxUnitWidth; - private final int rowCount; - - private final KeyboardWidget keyboard; - - private float currentWidth; - private int currentRow; - - public KeyLayoutBuilder(float maxUnitWidth, int rowCount, KeyboardWidget keyboard) { - this.maxUnitWidth = maxUnitWidth; - this.rowCount = rowCount; - this.keyboard = keyboard; - this.layout = new ArrayList<>(rowCount); - for (int i = 0; i < rowCount; i++) { - layout.add(new ArrayList<>()); - } - } - - public void key(KeyBuilder key, float width) { - Validate.isTrue(currentWidth + width <= maxUnitWidth, "Key width exceeds row width"); - - layout.get(currentRow).add(new KeyLayout<>(key, width)); - - currentWidth += width; - } - - public void nextRow() { - Validate.isTrue(currentRow < rowCount, "Row index out of bounds"); - - currentWidth = 0; - currentRow++; - } - - public void build(Consumer keyConsumer) { - int trueWidth = keyboard.getWidth(); - int trueHeight = keyboard.getHeight(); - - float unitWidth = (float) trueWidth / maxUnitWidth; - float keyHeight = (float) trueHeight / rowCount; - - float y = keyboard.getY(); - for (List> row : layout) { - float x = keyboard.getX(); - for (KeyLayout keyLayout : row) { - float keyWidth = unitWidth * keyLayout.unitWidth; - T key = keyLayout.keyBuilder.build(keyboard.containingScreen, (int) x, (int) y, (int) keyWidth, (int) keyHeight, keyboard); - keyConsumer.accept(key); - - x += keyWidth; - } - - y += keyHeight; - } - } - - private record KeyLayout(KeyBuilder keyBuilder, float unitWidth) {} - } - - @FunctionalInterface - public interface KeyBuilder { - T build(Screen screen, int x, int y, int width, int height, KeyboardWidget keyboard); - } - - @Override - public @NotNull List children() { - return keys; - } - - @Override - public boolean isDragging() { - return isDragging; - } - - @Override - public void setDragging(boolean dragging) { - isDragging = dragging; - } - - @Nullable - @Override - public GuiEventListener getFocused() { - return focused; - } - - @Override - public void setFocused(@Nullable GuiEventListener focused) { - if (this.focused != null) { - this.focused.setFocused(false); - } - - if (focused != null) { - focused.setFocused(true); - } - - this.focused = focused; - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent event) { - return ContainerEventHandler.super.nextFocusPath(event); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button /*? if >=1.21.9 {*/ ,boolean doubleClick /*?}*/) { - return ContainerEventHandler.super.mouseClicked(mouseX, mouseY, button /*? if >=1.21.9 >>*/ ,doubleClick ); - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - return ContainerEventHandler.super.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - return ContainerEventHandler.super.mouseDragged(mouseX, mouseY, button, dragX, dragY); - } - - @Override - public boolean isFocused() { - return ContainerEventHandler.super.isFocused(); - } - - @Override - public void setFocused(boolean focused) { - ContainerEventHandler.super.setFocused(focused); - } -} diff --git a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java index efa8db1d8..707ee54f9 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java @@ -1,6 +1,7 @@ package dev.isxander.controlify.screenop; import dev.isxander.controlify.controller.ControllerEntity; +import dev.isxander.controlify.screenop.keyboard.ComponentKeyboardBehaviour; public interface ComponentProcessor extends ComponentProcessorProvider { ComponentProcessor EMPTY = new ComponentProcessor(){}; @@ -20,6 +21,10 @@ default boolean shouldKeepFocusOnKeyboardMode(ScreenProcessor screen) { return false; } + default ComponentKeyboardBehaviour getKeyboardBehaviour(ScreenProcessor screen, ControllerEntity controller) { + return ComponentKeyboardBehaviour.UNDEFINED; + } + @Override default ComponentProcessor componentProcessor() { return this; diff --git a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessorProvider.java b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessorProvider.java index 3035ef4bd..a4d8ac4f0 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessorProvider.java +++ b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessorProvider.java @@ -6,6 +6,10 @@ public interface ComponentProcessorProvider { ComponentProcessor componentProcessor(); static ComponentProcessor provide(GuiEventListener component) { + if (component == null) { + return ComponentProcessor.EMPTY; + } + if (component instanceof ComponentProcessorProvider provider) return provider.componentProcessor(); diff --git a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java index a020ec9bc..24d0788ff 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java @@ -10,7 +10,8 @@ import dev.isxander.controlify.controller.input.GamepadInputs; import dev.isxander.controlify.controller.input.InputComponent; import dev.isxander.controlify.mixins.feature.screenop.ScreenAccessor; -import dev.isxander.controlify.mixins.feature.screenop.vanilla.TabNavigationBarAccessor; +import dev.isxander.controlify.mixins.feature.screenop.impl.outofgame.TabNavigationBarAccessor; +import dev.isxander.controlify.screenop.keyboard.*; import dev.isxander.controlify.sound.ControlifyClientSounds; import dev.isxander.controlify.utils.HoldRepeatHelper; import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour; @@ -19,6 +20,7 @@ import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.components.tabs.Tab; import net.minecraft.client.gui.components.tabs.TabNavigationBar; @@ -28,13 +30,14 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvents; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import java.util.*; public class ScreenProcessor { public final T screen; - protected final HoldRepeatHelper holdRepeatHelper = new HoldRepeatHelper(10, 3); + protected final HoldRepeatHelper holdRepeatHelper; protected static final Minecraft minecraft = Minecraft.getInstance(); private final List eventListeners = new ArrayList<>(); @@ -44,6 +47,7 @@ public ScreenProcessor(T screen) { if (screen instanceof ScreenControllerEventListener eventListener) { eventListeners.add(eventListener); } + this.holdRepeatHelper = createHoldRepeatHelper(); } public void onControllerUpdate(ControllerEntity controller) { @@ -158,8 +162,9 @@ protected void handleComponentNavigation(ControllerEntity controller) { controller.input().ifPresent(InputComponent::notifyGuiPressOutputsOfNavigate); - if (Controlify.instance().config().globalSettings().uiSounds) - minecraft.getSoundManager().play(SimpleSoundInstance.forUI(ControlifyClientSounds.SCREEN_FOCUS_CHANGE.get(), 1.0F)); + if (Controlify.instance().config().globalSettings().extraUiSounds) { + playFocusChangeSound(); + } controller.hdHaptics().ifPresent(haptics -> haptics.playHaptic(HapticEffects.NAVIGATE)); var newFocusTree = getFocusTree(); @@ -177,7 +182,9 @@ protected void handleButtons(ControllerEntity controller) { boolean prevTouchpadPressed = input.stateThen().isButtonDown(GamepadInputs.TOUCHPAD_1_BUTTON); if (ControlifyBindings.GUI_PRESS.on(controller).guiPressed().get() || (vmouseEnabled && touchpadPressed && !prevTouchpadPressed)) { - screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); + if (!this.tryOpenKeyboard(controller)) { + screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); + } } if (screen.shouldCloseOnEsc() && ControlifyBindings.GUI_BACK.on(controller).guiPressed().get()) { playClackSound(); @@ -273,6 +280,34 @@ public void addEventListener(ScreenControllerEventListener listener) { eventListeners.add(listener); } + protected boolean tryOpenKeyboard(ControllerEntity controller) { + @Nullable GuiEventListener focused = screen.getFocused(); + var componentProcessor = ComponentProcessorProvider.provide(focused); + ComponentKeyboardBehaviour behaviour = componentProcessor.getKeyboardBehaviour(this, controller); + + switch (behaviour) { + case ComponentKeyboardBehaviour.DoNothing() -> { + // prevent pressing enter on select when handled + return true; + } + case ComponentKeyboardBehaviour.Undefined() -> { + return false; + } + case ComponentKeyboardBehaviour.Handled( + KeyboardLayoutWithId layout, + InputTarget inputTarget, + KeyboardOverlayScreen.KeyboardPositioner positioner + ) -> { + minecraft.setScreen(new KeyboardOverlayScreen(screen, layout, inputTarget, positioner)); + return true; + } + } + } + + protected HoldRepeatHelper createHoldRepeatHelper() { + return new HoldRepeatHelper(10, 3); + } + protected Queue getFocusTree() { if (screen.getFocused() == null) return new ArrayDeque<>(); @@ -310,4 +345,8 @@ protected final Optional getWidget(String translationKey) { public static void playClackSound() { minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); } + + public static void playFocusChangeSound() { + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(ControlifyClientSounds.SCREEN_FOCUS_CHANGE.get(), 1.0F)); + } } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/AbstractSliderComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/AbstractSliderComponentProcessor.java index 0127967bc..b409e3a91 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/AbstractSliderComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/AbstractSliderComponentProcessor.java @@ -15,10 +15,10 @@ public abstract class AbstractSliderComponentProcessor implements ComponentProce @Override public boolean overrideControllerNavigation(ScreenProcessor screen, ControllerEntity controller) { - var left = ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).digitalNow(); - var leftPrev = ControlifyBindings.CYCLE_OPT_BACKWARD.on(controller).digitalPrev(); - var right = ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).digitalNow(); - var rightPrev = ControlifyBindings.CYCLE_OPT_FORWARD.on(controller).digitalPrev(); + var left = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).digitalNow(); + var leftPrev = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller).digitalPrev(); + var right = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).digitalNow(); + var rightPrev = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller).digitalPrev(); boolean repeatEventAvailable = holdRepeatHelper.canNavigate(); diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractSignEditScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractSignEditScreenProcessor.java index 16d9fe5ad..e7e5cf946 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractSignEditScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractSignEditScreenProcessor.java @@ -5,22 +5,118 @@ import dev.isxander.controlify.api.buttonguide.ButtonGuideApi; import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate; import dev.isxander.controlify.bindings.ControlifyBindings; +import dev.isxander.controlify.controller.ControllerEntity; +import dev.isxander.controlify.font.BindingFontHelper; import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.keyboard.CommonKeyboardHints; +import dev.isxander.controlify.screenop.keyboard.KeyboardWidget; +import dev.isxander.controlify.utils.LazyComponentDims; +import dev.isxander.controlify.utils.PrecomputedComponentDims; +import dev.isxander.controlify.virtualmouse.VirtualMouseHandler; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractButton; import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.world.level.block.entity.SignBlockEntity; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; public class AbstractSignEditScreenProcessor extends ScreenProcessor { - public AbstractSignEditScreenProcessor(AbstractSignEditScreen screen) { + + private static final Component signLineHint = Component.translatable("controlify.hint.sign_line_change", + BindingFontHelper.binding(ControlifyBindings.GUI_SECONDARY_NAVI_UP), + BindingFontHelper.binding(ControlifyBindings.GUI_SECONDARY_NAVI_DOWN)); + + private final Consumer moveCursorFunc; + private final Supplier signSupplier; + private final Supplier keyboardWidgetSupplier; + + private List> signLineHintLines; + + public AbstractSignEditScreenProcessor( + AbstractSignEditScreen screen, + Consumer moveCursorFunc, + Supplier signSupplier, + Supplier keyboardWidgetSupplier + ) { super(screen); + this.moveCursorFunc = moveCursorFunc; + this.signSupplier = signSupplier; + this.keyboardWidgetSupplier = keyboardWidgetSupplier; + } + + @Override + protected void handleButtons(ControllerEntity controller) { + super.handleButtons(controller); + + var config = controller.genericConfig().config(); + + // move cursor down a line + if (ControlifyBindings.GUI_SECONDARY_NAVI_DOWN.on(controller).justPressed()) { + this.moveCursorFunc.accept(1); + + if (config.hintKeyboardSignLine && config.showScreenGuides) { + config.hintKeyboardSignLine = false; + Controlify.instance().config().save(); + } + + playFocusChangeSound(); + } + + // move cursor up a line + if (ControlifyBindings.GUI_SECONDARY_NAVI_UP.on(controller).justPressed()) { + this.moveCursorFunc.accept(-1); + + if (config.hintKeyboardSignLine && config.showScreenGuides) { + config.hintKeyboardSignLine = false; + Controlify.instance().config().save(); + } + + playFocusChangeSound(); + } + } + + @Override + protected void render(ControllerEntity controller, GuiGraphics graphics, float tickDelta, Optional vmouse) { + var config = controller.genericConfig().config(); + KeyboardWidget keyboardWidget = this.keyboardWidgetSupplier.get(); + if (keyboardWidget != null && config.showScreenGuides) { + if (config.hintKeyboardCursor) { + LazyComponentDims hint = CommonKeyboardHints.TEXT_CURSOR; + + int x = keyboardWidget.getRight() - hint.getWidth() - 2; + int y = keyboardWidget.getY() - hint.getHeight(); + + graphics.drawString(minecraft.font, hint.getComponent(), x, y, 0xFFFFFFFF, true); + } + + if (config.hintKeyboardSignLine && this.signLineHintLines != null) { + int y = 4; + for (PrecomputedComponentDims line : this.signLineHintLines) { + int lineWidth = line.width(); + int lineHeight = line.height(); + FormattedCharSequence lineText = line.component(); + + graphics.drawString(minecraft.font, lineText, this.screen.width - 1 - lineWidth, y, 0xFFFFFFFF, true); + + y += minecraft.font.lineHeight; + } + } + } } @Override protected void setInitialFocus() { - if (Controlify.instance().currentInputMode() == InputMode.MIXED) + if (Controlify.instance().currentInputMode() == InputMode.MIXED) { holdRepeatHelper.clearDelay(); - else + } else { super.setInitialFocus(); + } } @Override @@ -33,5 +129,13 @@ public void onWidgetRebuild() { ControlifyBindings.GUI_BACK, ButtonGuidePredicate.always() )); + + // compute hint wrapping + SignBlockEntity sign = this.signSupplier.get(); + int signRight = (screen.width / 2) + (sign.getMaxTextLineWidth() / 2); + int maxLineWidth = screen.width - signRight; + this.signLineHintLines = minecraft.font.split(signLineHint, maxLineWidth).stream() + .map(cs -> PrecomputedComponentDims.of(cs, minecraft.font)) + .toList(); } } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AddServerLikeScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AddServerLikeScreenProcessor.java new file mode 100644 index 000000000..00a74e144 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AddServerLikeScreenProcessor.java @@ -0,0 +1,78 @@ +package dev.isxander.controlify.screenop.compat.vanilla; + +import dev.isxander.controlify.api.buttonguide.ButtonGuideApi; +import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate; +import dev.isxander.controlify.bindings.ControlifyBindings; +import dev.isxander.controlify.controller.ControllerEntity; +import dev.isxander.controlify.screenop.ComponentProcessorProvider; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.keyboard.KeyboardLayouts; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; + +import java.util.function.Supplier; + +public class AddServerLikeScreenProcessor extends ScreenProcessor { + + private final Supplier ipEditBoxSupplier; + private final Supplier