Skip to content

20.14.0 and gamepad-plugin 1.2.0: versions, and a visible press for skinned buttons - #1841

Merged
obiot merged 95 commits into
masterfrom
claude/modest-johnson-ao5snz
Nov 7, 2026
Merged

obiot merged 95 commits into
masterfrom
claude/modest-johnson-ao5snz

Conversation

@obiot

@obiot obiot commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Description

Sets the versions for the next releases, and fixes one gap left by the gamepad skins (#1840).

  • melonjs 20.14.0: packages/melonjs/package.json and .claude-plugin/plugin.json, as the 20.13.0 bump did.
  • @melonjs/gamepad-plugin 1.2.0:
    • package.json is set to 1.2.0.
    • The changelog heading is now ## [1.2.0] - _unreleased_ for the atlas skins. They are a new feature, so this is a minor version.
    • The entry is shortened and links gamepad plugin: add custom skin #1802.
  • gamepad-plugin, visible press for a skinned button with no pressed region:
    • The problem: with only a resting region, a press doubled the opacity and nothing else. At an opacity of 0.5 or more, the usual setting for skinned art, the button showed no change when held, though the docs said it was "drawn brighter".
    • The fix: it is now drawn at 90% of its size while held, the same idea as the engine's UI pressScale. The hit area is unchanged, and a pressed region is still drawn at full size.
    • Docs: TSDoc and README are updated.

Tests: two new skin tests, on Canvas and on WebGL:

All 97 plugin tests pass. ESLint shows 0 errors; its 8 warnings were there before.

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)

Checklist

  • I have read the Contributing Guide
  • My code follows the existing code style (pnpm lint passes)
  • I have tested my changes locally (pnpm test passes)
  • I have added tests that cover my changes (if applicable)
  • The build succeeds (pnpm build)

Related issues

#1802

🤖 Generated with Claude Code

https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru

claude added 30 commits October 31, 2026 03:09
- UICheckbox: a checkbox with its label, drawn or skinned, a radio button
  with a `group`; `onChange` only on user input
- UISlider: horizontal or vertical, drag, track click, wheel, step, caption
  and value text, drawn or skinned; `onChange` and `onChangeEnd`
- UITextButton: `onClick` setting on a completed click, atlas `skin` with
  lowered pressed art, `enabled`, `pressedColor`, CSS fonts
- UIPanel: nine-slice `skin`, `title`, `draggable`, `layout`
  (column/row/grid, padding, gap, align, autoSize) and `scroll` (wheel,
  drag with momentum, scrollbar, scrollTo/scrollToChild)
- UIBaseElement: cancelPress/onCancel, so a drag on a button in a
  scrolling panel scrolls instead of clicking
- Input: children cropped by a clipping container, or scrolled out of a
  panel, no longer get pointer events there
- The UI example uses the widgets, with volume sliders wired to audio and
  a scrolling level list; skill sections and changelog entries

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
- UIBaseElement: a draggable element pressed with no hover first, as a
  touch tap is, no longer throws, and drags by touch
- UIPanel: a draggable panel is moved by its title or frame, not through
  a widget on it, so dragging it by a button no longer clicks the button
- UIPanel: no fling while the pointer is still down; autoSize counts the
  title and frame from a zero height; a scrolling panel inside another
  scrolls alone; emptying or destroying a panel does not lay it out per
  child or call onScroll
- UISlider: the wheel to the right raises it; a drag also ends on a
  mouse or touch release, or a release over the slider
- UICheckbox: as wide as its longer label, whichever it is
- UITextButton: a font given as fontData, an image, or with only its
  image loaded is a bitmap font as before; pressedColor defaults to
  hoverOnColor; bitmapText can be assigned
- 10 regression tests

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
API, from a consistency review against the existing UI widgets:
- UITextButton: width/height, fontSize (pixels, bitmap fonts too),
  labelFillStyle and borderColor, the names the other widgets use; the old
  names still work. Settings are no longer written back. Colours take any
  Paint. pressedColor left undefined follows hoverOnColor. onClickCallback
  (was clickCallback). bitmapText keeps its BitmapText type, deprecated
- UICheckbox: boxSize, boxHoverColor; labelOn/labelOff settable and
  refitting; group changes apply at once; skin hoverOn/hoverOff and
  disabledOn/disabledOff
- UISlider: valueFormat(value, ratio) (was format), ratio, settable
  min/max/step, thumbHoverColor, thumbBorderColor, thumbBorderWidth
- UIBaseElement: enabled shared by every UI element; keepsDrag public
- ProgressBar: onChange also gets the bar
- Paint exported; @category UI on the UI settings types

Fixes:
- align "stretch" only resizes buttons and panels, not a BitmapText
  (whose resize is a scale) or a checkbox or slider
- a drag ends only on its own pointer's release, in the event family the
  engine listens to, not on another finger or button
- a non-blocking scrolling panel inside another scrolls alone
- a draggable panel is not moved through a widget at any depth, and the
  check no longer mixes coordinate spaces in the world
- a draggable element in the world no longer jumps by the camera position
- a stale scroll press is dropped; skin insets of 0 match the art
- slider steps in exponent notation, and a range that is not a whole
  number of steps, snap correctly

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
shadowColor, shadowOffsetX, shadowOffsetY and shadowBlur. The soft edge is
a few stacked translucent rounded rectangles, so it looks the same on
Canvas, WebGL and WebGPU; it follows the corner radius, goes under a skin
too, and is drawn outside the bounds so it never takes the pointer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…game (#1805)

A touch fires pointerover and pointerenter before its pointerdown, and
pointerout and pointerleave after its pointerup. The plugin only kept the
down, move, up and cancel from the engine, so the widget under a button
got onOver as the button was pressed (the Alex 4 menu arrow jumped to the
line under B). The plugin now also captures the four hover events and
stops them for a touch that lands on, or lifted from, a control. A mouse
hovering over the controls is left alone, as its moves are.

Fixes #1805

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
UIFocus (off until UIFocus.enable()): one widget has the focus; the arrows,
the D-pad or the left stick move it to the nearest widget that way, edge to
edge; Enter, Space or A press it as a click does; left/right step a slider;
Escape or B cancel. The pad is read directly (input.readGamepads, real or
virtual), so the game's pad bindings are untouched. The ring shows after
keyboard or pad input and hides when the mouse moves; the first key after
the mouse only shows it. A UIPanel with trapFocus keeps the focus in and
restores it after, with onBack on cancel. Focused widgets in a scrolling
panel scroll into view, with room for the ring. Any UIBaseElement can take
part: focusable, focusNeighbors, onNavigate, onFocus/onBlur,
focusPress/focusRelease. Buttons, checkboxes and sliders do.

hoverScale and pressScale on UIBaseElement, and as settings on every
widget: an eased bounce applied to the drawing only, so hit areas and
layouts stay put.

The UI example turns on both, with a hint line for the controls. Skill
sections and changelog entries; 13 new tests (87 in ui-widgets).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Arrows or the D-pad move to the nearest widget, Enter, Space or A press
it, sliders step, a ring shows the focus, and a focused widget in a
scrolling panel scrolls into view. Removed: trapFocus/onBack, cancel,
focusNeighbors, onFocus/onBlur, hover moving the focus, the first-key
rule, the stick with repeat, configurable keys and ring sizes. Most games
use the mouse or touch; this covers a menu driven by keys or a pad.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
- a press held by Enter or A is called off when the focus moves, the
  widget is removed or navigation is disabled, instead of staying down
- a focused widget only bounces while the focus ring shows
- the bounce eases over about 0.2 s; it snapped in a frame
- Enter goes to the focused button: another bound to Enter waits while the
  ring shows elsewhere
- a pad's A held as navigation is enabled is not a press
- a widget made focusable after it was added takes part
- navigation skips widgets inside a hidden or transparent container
- an arrow from a focused widget disabled since moves on from where it is
- docs: the bounce is on UIBaseElement; Enter and bindKey; accept calls
  onClick() with no event

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
UIFocus is gone. A widget takes the keyboard and gamepad focus with
focus(); from then on the arrows or the D-pad move it, Enter, Space or A
press it, sliders step. Nothing listens until a widget has the focus, and
the listeners go when none does (blur(), or the focused widget leaving the
scene), so the keys and the pad are the game's until then. On
UIBaseElement: focus(), blur(), focused, focusable, onNavigate, the static
focusOwner and focusRingColor. The example focuses its first widget.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Three bugs the new tests caught:
- a slider's default value text kept the old decimals after `step` changed
- Enter on a focused button the pointer already held clicked it on release
- a virtual gamepad sharing a slot with a real one was not read

The stub-subclass tests now use the real button. New tests cover the
shadow under a faded parent, scrollbar geometry, vertical and skinned
sliders, cancelled and two-finger drags, skin regions and bitmap font
sizes, radio drawing and groups, sideways and line-mode wheel scrolling,
wheel pass-through at the end of a panel, fling limits, grid stretch,
row autoSize, clearChildren, the focus ring, and focus teardown. The
clipping test now clips through a real container.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Dates the 1.0.2 changelog section: a touch on the controls no longer
reaches the game as a hover (#1805).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
- a "which widget for what" table
- one complete options menu: skinned panel, column layout, checkbox,
  slider, button, drop shadow, bounce, keyboard / gamepad focus
- code for drop shadows, a scrolling level list, radio groups, skinned
  checkboxes and sliders, stepped and vertical sliders, and a custom
  focusable widget
- symptom rows for focus, bindKey vs focus, and shadow corners
- drops a reference to the UI example's old hand-made panel

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Class docs in sections (look, label, state and onChange, radio groups,
keyboard and gamepad; range, input, text, look), every setting with its
unit and default, and examples for the common uses: a settings toggle, a
label that follows the state, a radio group, skins, restoring saved
settings, a volume slider, stepped and vertical sliders, percent text, a
slider in a scrolling panel. Three docs that contradicted the code now
match it (trackThickness with a skin, the checkbox fontSize minimum,
onChangeEnd on arrow steps).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
UIPanel's class doc is rewritten in sections (background, title,
dragging, blocking, layout, scrolling, drop shadow, idle float, things to
know), with examples for a drawn dialog, a skinned options menu, a
scrolling level grid, a draggable window, a drop shadow and a HUD frame.
Every setting has its unit and default; clearChildren is now documented.
ProgressBar's doc is tidied, with examples for a loading bar, a health
bar, a colour that follows the value, a vertical bar, a drop shadow, and
reacting when the bar empties.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
UIBaseElement's class doc covers the pointer callbacks, enabled,
dragging, the bounce and keyboard / gamepad focus, with examples for a
widget of your own, a draggable token, a keyboard and pad menu, and an
opaque dialog. UITextButton documents every setting with its unit and
default (the old names as aliases), with examples for drawn, skinned,
bindKey, disabled and shared-style buttons. UISpriteElement gets a real
class doc and examples. Several docs that contradicted the code now match
it (onMove, onRelease, enabled on a custom element, bindKey while
disabled).

Fix: a held bindKey press that is called off (the window losing focus,
the button removed or disabled) now calls onCancel(), as a pointer press
does; it called neither onRelease nor onCancel, so work started on the
press never stopped. Test added; the skill's bindKey rules follow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Its onClick() and onRelease() returned false, stopping every press and
release at the sprite, while every other UI element lets the event carry
on to what is under it unless a handler returns false. They now return
true. A subclass that relied on the old default returns false itself; the
examples already do. onActivateEvent / onDeactivateEvent now call super.

Test with real pointer events; docs, skill and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
UIBaseElement's onClick() and onRelease() now return false, as
UISpriteElement's always did (the previous commit's flip of
UISpriteElement is undone). A click on a button, a checkbox, a slider or
a widget of your own no longer also reaches the game behind it; a handler
returns true to let the press through. Hover is unchanged, and a
non-blocking UIPanel still lets presses on its background through.

Tests with real pointer events for buttons, checkboxes, sprite elements,
a handler letting the press through, and a non-blocking panel. Docs, both
skills and the changelog follow; two example buttons that returned true
now return false.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
It was a Sprite carrying its own copy of the pointer handling, which had
drifted from UIBaseElement's. It now extends UIBaseElement and holds its
image as `sprite`, so it shares the one implementation and gains what the
other widgets have: enabled (drawn at half opacity when off),
cancelPress / onCancel, hoverScale / pressScale, dragging and keyboard /
gamepad focus.

Kept for existing code: (x, y) is still where the image's anchor point
goes, and setRegion (which also resizes the element), addAnimation,
setCurrentAnimation and isCurrentAnimation stay on the element. Other
Sprite members move to `sprite`; UISpriteElementSettings is exported.

The matter platformer's touch controls follow: the button passes its
anchorPoint in the settings, and the joypad's knob is a child instead of
being drawn by hand. Tests cover placement, clicks, disabled drawing,
setRegion, the bounce, cancel and focus; docs, skill and changelog
updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
17 tests of what a game relies on: Sprite settings and size, placement
(centred by default, or by anchorPoint in the settings), presses,
releases, hover, a press ended by leaving, isClickable, removal, the
press stopping at it or let through, a subclass overriding the callbacks
as the demos' buttons do, and the setRegion / animation / opacity
shortcuts. They only use what the Sprite-based element and the
UIBaseElement-based one both offer, and pass on both, and on master's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…es the canvas

A pointerup cleared the pointer a region was tracking, so the region
forgot it was under the pointer: leaving straight after a click never
called onOut (a button stayed lit until the pointer came back over it),
and the next move over it called onOver a second time. The release now
keeps the pointer; the press still ends as before.

The browser's pointerleave, sent when the pointer leaves the canvas or a
finger lifts, now releases every region holding that pointer, wherever
the exit is reported. It used to reach only a region at that point,
which for a mouse leaving the canvas is the edge, far from most widgets.
Touch is unchanged: a tap hovers while the finger is down, not after.

Tests for leaving after a click, moving on over it, repeated clicks, a
tap with new pointer ids, and leaving the canvas; the UISpriteElement
tests check the hover strictly again, and pass on the old and new
element.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
- UICheckbox#toggle() on a checked radio button leaves it checked, as a
  click does, instead of leaving its group with nothing checked
- UIPanel#scrollTo() (and scrollToChild, scrollX / scrollY) stops a
  fling still coasting; the panel's own fling, drag, wheel and relayout
  scroll through a private method that leaves it alone
- UISlider lays its texts out in one place, again whenever they change:
  a vertical slider widens for a longer value text (valueFormat, min, max
  or step set later), a horizontal one moves the value below the track
  when it would run into the caption, and the new labelText sets the
  caption later

Tests for each, failing before the fix, and one for the wheel over a
slider in a scrolling panel (it moves the slider, not the panel), which
had none. Docs, skill and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
claude added 15 commits November 5, 2026 10:16
…: flicker callback gets the sprite

Removing a container, resetting the world or changing state called
onDeactivateEvent on each child twice, or more deeper down. Each child is now
deactivated once for each activation.

enableChildBoundsUpdate did not re-measure when children moved, so the camera
culled the group by where its children started. The bounds are re-measured on
update, and a culled group is re-measured before it is skipped.

Sprite.flicker called its callback with the wrong this and cancelled a flicker
started from it. The flicker now ends first, then the callback runs on the sprite.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…k; UI: widget state, accept keys, blur, slider release, ProgressBar range

Text and BitmapText word wrap replaced explicit line breaks with spaces and
added a blank first line when the first word was wider than wordWrapWidth.
Each line is now wrapped on its own.

Multi-line Text with a middle, bottom or alphabetic baseline placed only its
first line by the baseline, so later lines fell outside its bounds. It now
places the whole block, as BitmapText does.

UI widgets removed and re-added kept a stale hover or press; Enter could click
both the focused widget and a bindKey button; a key held through a window blur
stayed pressed and locked out the pad; UISlider never ended a pointer press
with onRelease or onCancel; ProgressBar did not re-clamp when min or max
changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…ngles and lineWidth restore; Camera: minimap coordinates

WebGL folded a colour's alpha into the global alpha, so after a translucent
setColor the next colours, images and renderables were drawn faded and
getGlobalAlpha() returned the colour's alpha. Canvas leaked it through
globalAlpha() too. The global alpha now lives in the render state, saved and
restored, and fills use colour alpha times global alpha.

WebGL and WebGPU fillArc never normalised its angles, so an end before the
start filled the wrong part. save()/restore() did not keep lineWidth or
lineJoin.

A minimap camera's worldToLocal/localToWorld applied the world offset that its
draw cancels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…eat on restart, round/isPowerOfFour, observable vector *Self, Ellipse.scale rotation, pool purge count

- addAnimation() on the playing animation now refreshes it, instead of
  crashing the next update when the new strip is shorter
- start() restores a tween's configured repeat count
- math.round rounds negatives correctly and past 2^31; isPowerOfFour rejects
  fractions and odd powers of two
- ObservableVector2d/3d negateSelf, floorSelf, ceilSelf, clampSelf (and 3D
  normalize, perp) go through set(): one callback, return this; a pooled
  clone without a callback no longer keeps the previous owner's
- Ellipse.scale() keeps the rotation
- pool.purge() drops only idle instances and keeps the in-use count

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…sistent children keep their bodies on reset

addChild registered only direct children's bodies and removeChildNow removed
only the child's own, so a grandchild's bodyDef was never simulated, a group
removed with keepalive left its children simulated, and adding it back (as
reparenting does) threw 'already adapter-managed'. The whole subtree is now
registered and removed, once per stay in the world.

World.reset kept a body only if its own renderable was persistent, so the
children of a persistent container lost theirs on every state change or
level load. It now keeps the bodies of everything still in the tree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…after remove() persists; Text: setText compares lines, measureText leaves the label alone

The loader kept the last preload callback in module state, so a preload with
no callback called the previous one again (often a state change), and its
progress counters never reset, so a second preload started part-full and
progress never reached 1 after a failed asset. Each preload now has its own
callback and progress; reload() completes the preload the asset failed in and
works for custom asset types.

A save key set again after remove() was written but left out of the index
read at boot, so it was lost on reload.

Text and BitmapText setText compared toString(), so '1,000' after
['1','000'] did nothing; measureText(other) wrote into the label's own
metrics and shifted a centred label.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…aycast on every shape type, rotation about the shapes' centre

- respondToCollision compared the floor push-out against 1px, so a body that
  sank 1px or more per step (heavier gravity or mass, low frame rate, a rising
  floor) stayed falling and isGrounded() was false. The direction of the push
  now decides.
- Polygon edge normals pointed outward only for clockwise polygons, so a
  counter-clockwise one (Tiled, or mirrored) collided with far circles. Normals
  are flipped for counter-clockwise polygons; points keep their order.
- Raycast threw on Point, Sphere and Box3d shapes and ignored an ellipse's
  rotation. Each shape type is now handled, and unknown ones are skipped.
- A body rotated about its bounds centre in the anchor-less frame, so a
  centre-anchored renderable swung about its corner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
…dge getTile, view-edge drawing

- Infinite maps ignored the layer origin and wrapped chunk tiles past the
  layer width into other rows; tiles at negative coordinates were misplaced or
  dropped. The layer grid now covers every chunk, cells use Tiled's tile
  coordinates (TMXLayer.startx/starty), and drawing and getTile agree.
- The staggered renderer doubled its reference point before measuring the
  offset, so pixelToTileCoords/getTile returned a neighbour on every row from
  1 down, and a scrolled staggered map drew nothing.
- A tileset's tile count ignored its margin and rounded partial rows up, so it
  claimed the next tileset's gids; an image collection's range covered one.
- cellAt truncated negative fractions toward 0, so getTile returned an edge
  tile just outside isometric, staggered and oblique maps.
- Draw loops: stagger-axis-x maps left gaps at the top of views, oblique maps
  missed tiles at view edges, left/up render orders skipped a row or column,
  and a view outside a layer looped forever.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Dates the 20.13.0 changelog section (2026-11-06).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Copilot AI balanced review requested due to automatic review settings November 7, 2026 01:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 7, 2026 01:38
@obiot obiot changed the title 20.14.0: bump the version 20.14.0 and gamepad-plugin 1.2.0: bump the versions Nov 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

…le held

With only a resting region, a press only doubled the opacity, so at
opacity 0.5 or more a skinned button showed no change when pressed, though
the docs said it was drawn brighter. It is now drawn at 90% of its size while
held, like the engine's UI pressScale; the hit area is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JnzwA4xBNBw7cEZzG1bUru
Copilot AI balanced review requested due to automatic review settings November 7, 2026 01:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@obiot obiot changed the title 20.14.0 and gamepad-plugin 1.2.0: bump the versions 20.14.0 and gamepad-plugin 1.2.0: versions, and a visible press for skinned buttons Nov 7, 2026
@obiot
obiot merged commit b12892d into master Nov 7, 2026
6 checks passed
@obiot
obiot deleted the claude/modest-johnson-ao5snz branch November 7, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants