Skip to content

Fade disabled button icon when resource is a bitmap (#6217)#6381

Open
mohsenm4 wants to merge 3 commits into
fyne-io:developfrom
mohsenm4:fix/6217-disabled-bitmap-icon
Open

Fade disabled button icon when resource is a bitmap (#6217)#6381
mohsenm4 wants to merge 3 commits into
fyne-io:developfrom
mohsenm4:fix/6217-disabled-bitmap-icon

Conversation

@mohsenm4

@mohsenm4 mohsenm4 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Buttons with bitmap (PNG/JPEG) icons stayed at full opacity when Disable()d, even though SVG-icon buttons were correctly grayed out via NewDisabledResource. There was no visual cue that the button was disabled.
  • Bitmap resources cannot be recolored the way themed SVGs can. When the button is disabled and the icon resource is not an SVG, we now apply a Translucency fade to the canvas.Image to give a visual disabled cue. SVG behaviour is unchanged.
  • Resolves the existing // TODO support disabling bitmap resource not just SVG in widget/button.go.

Scope / not closing #6217

The original report (#6217) mixes two related problems:

  1. Bitmap icon resource (Button.Icon) not faded on Disable()fixed here.
  2. Bitmap-emoji text content (e.g. 🔝) not faded on Disable()not addressed here, the fix would live in the text/painter pipeline rather than the button renderer. Tracked as #6383.

So this PR intentionally uses Refs #6217 rather than Fixes #6217, to avoid auto-closing the original report before the emoji part is solved.

Note on the translucency constant

disabledIconTranslucency = 0.5 is a fixed value because we can't recolor a raster image with theme.ColorNameDisabled the way SVGs are recolored. The choice is documented inline — happy to switch to a theme-derived value (e.g. derived from the alpha ratio between ColorNameDisabled and ColorNameForeground) if you prefer.

Test plan

  • go build ./...
  • go test ./widget/ -run TestButton -count=1 — all existing button tests pass plus the new TestButton_DisabledBitmapIcon (covers enable → disable → re-enable for a PNG resource).
  • Reviewer: visually confirm with a button that has a PNG Icon set that the icon now fades on Disable() and restores on Enable().

Refs #6217
Refs #6383

@mohsenm4 mohsenm4 force-pushed the fix/6217-disabled-bitmap-icon branch from f4c155b to 92ef4b5 Compare June 28, 2026 07:32
@coveralls

coveralls commented Jun 28, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 59.835% (-0.06%) from 59.89% — mohsenm4:fix/6217-disabled-bitmap-icon into fyne-io:develop

Buttons with SVG icons were already grayed out on Disable() via
NewDisabledResource, but bitmap resources (PNG, JPEG, ...) cannot be
recolored that way and stayed at full opacity, leaving no visual cue
that the button is disabled.

Apply a translucency to the icon image when the button is disabled and
the resource is not an SVG; reset it when the button is enabled again.
The chosen translucency is a fixed value and documented inline — the
emoji-text rendering aspect from the original report is tracked
separately and is not addressed here.

Refs fyne-io#6217 (partial — Button.Icon only)
Refs fyne-io#6383 (follow-up for emoji text)
@mohsenm4 mohsenm4 force-pushed the fix/6217-disabled-bitmap-icon branch from 92ef4b5 to 008f894 Compare June 28, 2026 08:02

@andydotxyz andydotxyz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for looking at this. However translucency feels like a workaround. bitmap images absolutely can be recoloured. In this case it should be sufficient to reduce saturation to make it appear greyscale.

Comment thread widget/button.go Outdated
ButtonIconTrailingText
)

// disabledIconTranslucency is the alpha-fade applied to bitmap icons (PNG, JPEG, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please don't commit walls of comment. A comment should be added when its public API or where the code is not clear without it.

Comment thread widget/button.go Outdated
if svg.IsResourceSVG(icon) {
icon = theme.NewDisabledResource(icon)
} else {
// Bitmap resources cannot be recolored, so fade them instead

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, bitmaps can be recoloured. Not in the same way but it is not impossible.
Remove the colour saturation to be greyscale and then apply colour (if the desired outcome is not grey)

Per review feedback on fyne-io#6381: translucency was a workaround. Bitmap
resources can be recoloured by reducing saturation so the icon appears
greyscale, matching how SVGs are recoloured to ColorNameDisabled.

Extend theme.DisabledResource.Content() to decode non-SVG resources,
convert each pixel to its ITU-R BT.601 luminance (alpha preserved), and
return the result as PNG. SVG content keeps its existing colorize path.

Drop the IsResourceSVG branching in widget/button.go — NewDisabledResource
now handles both kinds. Removes the disabledIconTranslucency constant and
the Translucency field manipulation introduced earlier on this branch.
@mohsenm4

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback — you're right, translucency was hiding the real fix.

In cf066e59b I moved the work into theme.DisabledResource.Content(): when the wrapped resource is a bitmap, it now decodes the image, converts each pixel to its ITU-R BT.601 luminance (alpha preserved), and returns the result as PNG. SVG content keeps its existing colorize path. The button renderer no longer needs the IsResourceSVG branch or the Translucency workaround — it just calls NewDisabledResource for all icons.

Side effect: every other call site of NewDisabledResource (entry_password.go, select.go, menu_item.go if its own SVG guard is later dropped) gets the bitmap support for free.

Pushed as a follow-up commit, no force-push, so the diff against your previous review should be just cf066e59b.

@andydotxyz andydotxyz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple of notes inline. Plus the static check errors would need to be addressed

Comment thread theme/icons.go Outdated
// Content returns the disabled style content of the correct resource for the current theme
// Content returns the disabled style content of the correct resource for the current theme.
// SVG resources are recolored with the theme's disabled color; bitmap resources (PNG, JPEG, ...)
// are desaturated to greyscale since they cannot be recolored.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think that "since they cannot be recolored" in the docs is required - focus on behaviour in public docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e558fb3 — dropped the implementation reason, kept only the behaviour.

Comment thread theme/icons.go Outdated
// desaturateLogError returns a PNG-encoded greyscale copy of the given image bytes,
// preserving the alpha channel. If decoding or encoding fails, the original bytes are
// returned so the caller can still render something.
func desaturateLogError(src []byte) []byte {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As an internal API I would think that returning the error instead of logging it makes more sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in e558fb3desaturate now returns ([]byte, error); Content() logs and falls back to the original bytes.

Comment thread theme/icons.go Outdated
// Convert via NRGBA so the luminance is computed on unpremultiplied
// channels — otherwise partially-transparent pixels go too dark.
n := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
lum := uint8((299*uint32(n.R) + 587*uint32(n.G) + 114*uint32(n.B)) / 1000)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What are these magic numbers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ITU-R BT.601 luma coefficients. Replaced with named constants (lumaWeightR/G/B, lumaScale) in e558fb3. Same commit also resolves the gosec G115 lint by clamping before the uint8 cast.

@andydotxyz andydotxyz added the after-release For when an ongoing release is finished. label Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

after-release For when an ongoing release is finished.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants