feat: clipboard support #61

Merged
abart27 merged 12 commits from clipboard-support into main 2026-03-15 11:43:32 +00:00
abart27 commented 2026-03-15 09:41:27 +00:00 (Migrated from github.com)

Summary

Adds the concept of STATIC_ENV, which houses platform interfaces (only a clipboard API for now) that are provided by the user.

Providing these is optional; default stub implementations are provided.

This new concept is leveraged to provide copy/paste functionality for certain controls:

textbox - Supports copy and paste
numberbox - Supports copy and paste (even pasting multiple numbers)
spinner - Supports copy and paste (inherits from textbox)
listbox - Supports copying (selected item)

Adoption

If you're running under a Mupen64 Lua environment, update your code like this:

 ---@module "ugui-amalgamated"
 ugui = dofile('ugui-amalgamated.lua')

+ugui.STATIC_ENV = {
+    clipboard = {
+        get = function()
+            return clipboard.get('text')
+        end,
+        set = function(text)
+            clipboard.set('text', text)
+        end,
+    },
+}
# Summary Adds the concept of `STATIC_ENV`, which houses platform interfaces (only a `clipboard` API for now) that are provided by the user. Providing these is optional; default stub implementations are provided. This new concept is leveraged to provide copy/paste functionality for certain controls: **textbox** - Supports copy and paste **numberbox** - Supports copy and paste (even pasting multiple numbers) **spinner** - Supports copy and paste (inherits from textbox) **listbox** - Supports copying (selected item) # Adoption If you're running under a Mupen64 Lua environment, update your code like this: ```diff ---@module "ugui-amalgamated" ugui = dofile('ugui-amalgamated.lua') +ugui.STATIC_ENV = { + clipboard = { + get = function() + return clipboard.get('text') + end, + set = function(text) + clipboard.set('text', text) + end, + }, +} ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-03-15 10:10:31 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

Introduces a STATIC_ENV concept to let host scripts optionally provide platform integrations (starting with clipboard), and wires that into input processing to enable copy/paste behaviors in several controls.

Changes:

  • Add ugui.STATIC_ENV and new environment-related type definitions (Environment, UguiStaticEnvironment, UguiClipboard).
  • Implement Ctrl+V handling by translating paste key events into synthetic text events during begin_frame.
  • Add Ctrl+C copy support for textbox, numberbox, and listbox; add multi-digit insertion support for numberbox.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/ugui/ugui.lua Adds STATIC_ENV to the public ugui table.
src/ugui/types.lua Removes old (commented) Environment type block.
src/ugui/environment/static_env.lua Adds UguiStaticEnvironment type definition.
src/ugui/environment/env.lua Adds Environment type definition.
src/ugui/environment/clipboard.lua Adds clipboard provider type definitions.
src/ugui/core.lua Implements paste translation in begin_frame using the clipboard provider.
src/ugui/helpers.lua Adds helper to set a range of digits for multi-digit insertion.
src/ugui/controls/textbox.lua Adds Ctrl+C copy and supports multi-char e.text insertion.
src/ugui/controls/numberbox.lua Adds Ctrl+C copy and multi-digit paste handling.
src/ugui/controls/listbox.lua Adds Ctrl+C copy of selected item.
demos/base.lua Demonstrates wiring ugui.STATIC_ENV.clipboard to Mupen clipboard APIs.
build.py Adds new environment files to the amalgamation order.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

## Pull request overview Introduces a `STATIC_ENV` concept to let host scripts optionally provide platform integrations (starting with clipboard), and wires that into input processing to enable copy/paste behaviors in several controls. **Changes:** - Add `ugui.STATIC_ENV` and new environment-related type definitions (`Environment`, `UguiStaticEnvironment`, `UguiClipboard`). - Implement Ctrl+V handling by translating paste key events into synthetic `text` events during `begin_frame`. - Add Ctrl+C copy support for textbox, numberbox, and listbox; add multi-digit insertion support for numberbox. ### Reviewed changes Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | src/ugui/ugui.lua | Adds `STATIC_ENV` to the public ugui table. | | src/ugui/types.lua | Removes old (commented) `Environment` type block. | | src/ugui/environment/static_env.lua | Adds `UguiStaticEnvironment` type definition. | | src/ugui/environment/env.lua | Adds `Environment` type definition. | | src/ugui/environment/clipboard.lua | Adds clipboard provider type definitions. | | src/ugui/core.lua | Implements paste translation in `begin_frame` using the clipboard provider. | | src/ugui/helpers.lua | Adds helper to set a range of digits for multi-digit insertion. | | src/ugui/controls/textbox.lua | Adds Ctrl+C copy and supports multi-char `e.text` insertion. | | src/ugui/controls/numberbox.lua | Adds Ctrl+C copy and multi-digit paste handling. | | src/ugui/controls/listbox.lua | Adds Ctrl+C copy of selected item. | | demos/base.lua | Demonstrates wiring `ugui.STATIC_ENV.clipboard` to Mupen clipboard APIs. | | build.py | Adds new environment files to the amalgamation order. | </details> --- 💡 <a href="/mupen64/ugui/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.<br><br>You can also share your feedback on Copilot code review. [Take the survey](https://www.surveymonkey.com/r/XP6L3XJ).
@ -64,1 +64,4 @@
end
if e.keycode == ugui.keycodes.VK_C and e.ctrl and data.selected_index ~= nil then
local item = control.items[data.selected_index]
ugui.STATIC_ENV.clipboard.set(item)
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:31 +00:00

control.items[data.selected_index] can be nil if selected_index is out of range (the validator only checks type). Pressing Ctrl+C would then call clipboard.set(nil) and likely error. Clamp data.selected_index to [1, #control.items] before indexing and/or guard against nil items before copying.

`control.items[data.selected_index]` can be nil if `selected_index` is out of range (the validator only checks type). Pressing Ctrl+C would then call `clipboard.set(nil)` and likely error. Clamp `data.selected_index` to `[1, #control.items]` before indexing and/or guard against nil items before copying.
@ -77,15 +77,31 @@ ugui.registry.numberbox = {
if e.keycode == ugui.keycodes.VK_DOWN then
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:30 +00:00

tonumber(e.text) is too permissive for digit insertion/paste: it accepts strings like "1.2", "1e3", or "-1", which will make set_digit_range behave incorrectly (non-digit chars change #e.text, can drive negative exponents, and produce non-integer results). Validate pasted input as digits-only (e.g. ^%d+$) and truncate/clamp the digit count so data.caret_index + #digits - 1 never exceeds control.places.

`tonumber(e.text)` is too permissive for digit insertion/paste: it accepts strings like "1.2", "1e3", or "-1", which will make `set_digit_range` behave incorrectly (non-digit chars change `#e.text`, can drive negative exponents, and produce non-integer results). Validate pasted input as digits-only (e.g. `^%d+$`) and truncate/clamp the digit count so `data.caret_index + #digits - 1` never exceeds `control.places`.
@ -95,0 +96,4 @@
if e.keycode == ugui.keycodes.VK_C and e.ctrl and has_selection then
local selected_text = data.text:sub(lower_selection, higher_selection - 1)
ugui.STATIC_ENV.clipboard.set(selected_text)
end
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:30 +00:00

TextBox selection indices are caret positions (end is exclusive), as shown by the selection rendering in styler.lua (text:sub(..., selection_end - 1)) and by remove_range’s implementation. Using data.text:sub(lower_selection, higher_selection) will copy one extra character; this should slice to higher_selection - 1.

TextBox selection indices are caret positions (end is exclusive), as shown by the selection rendering in `styler.lua` (`text:sub(..., selection_end - 1)`) and by `remove_range`’s implementation. Using `data.text:sub(lower_selection, higher_selection)` will copy one extra character; this should slice to `higher_selection - 1`.
@ -43,12 +43,34 @@ ugui.begin_frame = function(environment)
if not ugui.internal.environment then
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:30 +00:00

ugui.begin_frame unconditionally calls ugui.STATIC_ENV.clipboard.get(). If the clipboard provider is not configured (which the PR description says is optional), this will throw (attempt to index field 'clipboard' (a nil value) or attempt to call a nil value). Guard the call (check ugui.STATIC_ENV.clipboard and .get) and/or rely on a guaranteed default stub implementation.

`ugui.begin_frame` unconditionally calls `ugui.STATIC_ENV.clipboard.get()`. If the clipboard provider is not configured (which the PR description says is optional), this will throw (`attempt to index field 'clipboard' (a nil value)` or `attempt to call a nil value`). Guard the call (check `ugui.STATIC_ENV.clipboard` and `.get`) and/or rely on a guaranteed default stub implementation.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:30 +00:00

Typo in comment: "synthethic" should be "synthetic".

Typo in comment: "synthethic" should be "synthetic".
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:31 +00:00

New clipboard/paste behavior is introduced here (transforming Ctrl+V into a synthetic text event). The repo has a fairly extensive test/core.lua, but there are no tests covering this translation or the “clipboard provider optional” behavior; adding coverage would help prevent regressions (e.g., missing provider, multiple Ctrl+V events, empty clipboard).

New clipboard/paste behavior is introduced here (transforming Ctrl+V into a synthetic `text` event). The repo has a fairly extensive `test/core.lua`, but there are no tests covering this translation or the “clipboard provider optional” behavior; adding coverage would help prevent regressions (e.g., missing provider, multiple Ctrl+V events, empty clipboard).
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:31 +00:00

set_digit_range breaks if index + #digits - 1 > length (it computes insert_pow with a negative exponent, leading to fractional powers and incorrect digit extraction/replacement). Add bounds handling (truncate digits to remaining length or early-return) and consider rejecting non-digit digits explicitly so the function always returns an integer in [0, 10^length).

`set_digit_range` breaks if `index + #digits - 1 > length` (it computes `insert_pow` with a negative exponent, leading to fractional powers and incorrect digit extraction/replacement). Add bounds handling (truncate digits to remaining length or early-return) and consider rejecting non-digit `digits` explicitly so the function always returns an integer in `[0, 10^length)`.
@ -10,6 +10,7 @@ local ugui = {
_DESCRIPTION = 'Flexible immediate-mode GUI library for Mupen Lua',
_LICENSE = 'GPL-3',
DEBUG = false,
STATIC_ENV = {},
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:10:29 +00:00

STATIC_ENV is initialized as an empty table, but the rest of the changes assume ugui.STATIC_ENV.clipboard.get/set always exist. With the default {} this will crash on the first begin_frame() or on Ctrl+C in controls. Provide a default stub clipboard provider here (e.g., get returning nil and set as a no-op), or ensure STATIC_ENV.clipboard is always populated during initialization.

`STATIC_ENV` is initialized as an empty table, but the rest of the changes assume `ugui.STATIC_ENV.clipboard.get/set` always exist. With the default `{}` this will crash on the first `begin_frame()` or on Ctrl+C in controls. Provide a default stub clipboard provider here (e.g., `get` returning nil and `set` as a no-op), or ensure `STATIC_ENV.clipboard` is always populated during initialization.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-03-15 10:29:07 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

Adds a configurable “static environment” surface (currently clipboard) and wires it into input handling to enable copy/paste for text-like controls, while also fixing/adding helper routines to support multi-character insertion.

Changes:

  • Introduces ugui.STATIC_ENV (with default stub clipboard provider) and new environment type definitions under src/ugui/environment/.
  • Implements clipboard-driven paste by translating Ctrl+V key events into synthetic text events at begin_frame, and adds Ctrl+C copy support in textbox/numberbox/listbox.
  • Fixes insert_at semantics and adds set_digit_range to support multi-digit insertion/paste in numberbox.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/ugui/ugui.lua Adds STATIC_ENV field to the top-level ugui table.
src/ugui/types.lua Removes the inline Environment type doc (moved to dedicated file).
src/ugui/helpers.lua Fixes insert_at and adds set_digit_range for multi-digit number edits.
src/ugui/environment/static_env.lua Defines UguiStaticEnvironment and installs default stub clipboard provider.
src/ugui/environment/env.lua Reintroduces Environment type documentation in a dedicated file.
src/ugui/environment/clipboard.lua Adds clipboard type aliases/classes for the static environment contract.
src/ugui/core.lua Adds Ctrl+V → synthetic text event translation using clipboard provider.
src/ugui/controls/textbox.lua Adds Ctrl+C copy and adjusts insertion to new insert_at behavior.
src/ugui/controls/numberbox.lua Adds Ctrl+C digit copy and multi-digit paste support via set_digit_range.
src/ugui/controls/listbox.lua Adds Ctrl+C copy of selected item.
demos/base.lua Demonstrates wiring ugui.STATIC_ENV.clipboard to Mupen’s clipboard API.
build.py Ensures new environment files are included early in the ugui amalgamation order.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

## Pull request overview Adds a configurable “static environment” surface (currently clipboard) and wires it into input handling to enable copy/paste for text-like controls, while also fixing/adding helper routines to support multi-character insertion. **Changes:** - Introduces `ugui.STATIC_ENV` (with default stub clipboard provider) and new environment type definitions under `src/ugui/environment/`. - Implements clipboard-driven paste by translating Ctrl+V key events into synthetic `text` events at `begin_frame`, and adds Ctrl+C copy support in textbox/numberbox/listbox. - Fixes `insert_at` semantics and adds `set_digit_range` to support multi-digit insertion/paste in `numberbox`. ### Reviewed changes Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | src/ugui/ugui.lua | Adds `STATIC_ENV` field to the top-level `ugui` table. | | src/ugui/types.lua | Removes the inline `Environment` type doc (moved to dedicated file). | | src/ugui/helpers.lua | Fixes `insert_at` and adds `set_digit_range` for multi-digit number edits. | | src/ugui/environment/static_env.lua | Defines `UguiStaticEnvironment` and installs default stub clipboard provider. | | src/ugui/environment/env.lua | Reintroduces `Environment` type documentation in a dedicated file. | | src/ugui/environment/clipboard.lua | Adds clipboard type aliases/classes for the static environment contract. | | src/ugui/core.lua | Adds Ctrl+V → synthetic `text` event translation using clipboard provider. | | src/ugui/controls/textbox.lua | Adds Ctrl+C copy and adjusts insertion to new `insert_at` behavior. | | src/ugui/controls/numberbox.lua | Adds Ctrl+C digit copy and multi-digit paste support via `set_digit_range`. | | src/ugui/controls/listbox.lua | Adds Ctrl+C copy of selected item. | | demos/base.lua | Demonstrates wiring `ugui.STATIC_ENV.clipboard` to Mupen’s clipboard API. | | build.py | Ensures new environment files are included early in the ugui amalgamation order. | </details> --- 💡 <a href="/mupen64/ugui/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.<br><br>You can also share your feedback on Copilot code review. [Take the survey](https://www.surveymonkey.com/r/XP6L3XJ).
@ -95,0 +96,4 @@
if e.keycode == ugui.keycodes.VK_C and e.ctrl and has_selection then
local selected_text = data.text:sub(lower_selection, higher_selection - 1)
ugui.STATIC_ENV.clipboard.set(selected_text)
end
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:29:06 +00:00

Clipboard copy/paste is new behavior (Ctrl+C here, Ctrl+V via begin_frame synthetic text events), but there are no tests asserting that clipboard.set is called with the selected text and that a Ctrl+V key event results in inserting clipboard text. Adding unit tests (e.g., stubbing ugui.STATIC_ENV.clipboard in test/textbox.lua) would help prevent regressions.

Clipboard copy/paste is new behavior (Ctrl+C here, Ctrl+V via `begin_frame` synthetic text events), but there are no tests asserting that clipboard.set is called with the selected text and that a Ctrl+V key event results in inserting clipboard text. Adding unit tests (e.g., stubbing `ugui.STATIC_ENV.clipboard` in `test/textbox.lua`) would help prevent regressions.
@ -43,12 +43,34 @@ ugui.begin_frame = function(environment)
if not ugui.internal.environment then
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:29:06 +00:00

begin_frame calls ugui.STATIC_ENV.clipboard.get() every frame even when there is no Ctrl+V event. If clipboard access is expensive or has side effects on some platforms, this adds unnecessary overhead. Consider first scanning environment.key_events for a Ctrl+V press and only then fetching the clipboard text (or fetch lazily inside the loop).

`begin_frame` calls `ugui.STATIC_ENV.clipboard.get()` every frame even when there is no Ctrl+V event. If clipboard access is expensive or has side effects on some platforms, this adds unnecessary overhead. Consider first scanning `environment.key_events` for a Ctrl+V press and only then fetching the clipboard text (or fetch lazily inside the loop).
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:29:06 +00:00

This paste handling rewrites entries in the caller-provided environment.key_events table in-place. That mutation can be surprising if the host app reuses the same events table elsewhere (e.g., for non-ugui hotkeys or logging). Consider applying the transformation to a copy that is only used for ugui.internal.environment (leave the input environment untouched).

This paste handling rewrites entries in the caller-provided `environment.key_events` table in-place. That mutation can be surprising if the host app reuses the same events table elsewhere (e.g., for non-ugui hotkeys or logging). Consider applying the transformation to a copy that is only used for `ugui.internal.environment` (leave the input `environment` untouched).
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 10:29:06 +00:00

ugui.STATIC_ENV.clipboard is treated as always present here, but UguiStaticEnvironment.clipboard is annotated as optional and the PR description says providers are optional. If a consumer sets ugui.STATIC_ENV without a clipboard field (or sets it to nil), this will throw. Consider guarding the call (and treating missing clipboard as "no paste available").

`ugui.STATIC_ENV.clipboard` is treated as always present here, but `UguiStaticEnvironment.clipboard` is annotated as optional and the PR description says providers are optional. If a consumer sets `ugui.STATIC_ENV` without a `clipboard` field (or sets it to nil), this will throw. Consider guarding the call (and treating missing clipboard as "no paste available").
Sign in to join this conversation.
No description provided.