Skip to content

feat(plugins): expose radius and padding on button nodes - #3355

Closed
gegnep wants to merge 1 commit into
noctalia-dev:mainfrom
gegnep:plugin-button-shape
Closed

feat(plugins): expose radius and padding on button nodes#3355
gegnep wants to merge 1 commit into
noctalia-dev:mainfrom
gegnep:plugin-button-shape

Conversation

@gegnep

@gegnep gegnep commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Exposes radius, padding, paddingH, and paddingV on declarative
button nodes in the plugin UI tree. Button already inherits these
setters from Flex; this change adds the four keys to the button prop
allowlist and applies them mirroring the existing row/column
handling. Application happens after the compact-host chrome block, so
explicit props win over the bar-widget padding/min-height reset. No
behavior change for trees that do not pass the new props.

Motivation

Plugin bar widgets can't shape their buttons: button is the only
clickable text-bearing node, but its corner radius and padding are fixed
by the control's chrome. A widget that wants capsule chips visually
consistent with the built-in workspaces widget's pills (fully rounded,
active pill wider via a larger horizontal inset) has no way to express
that — the workaround is padding the label with literal spaces. With
this change a plugin passes radius/paddingH per node with the same
semantics and scaling as the flex containers.

Type of Change

  • New feature

Testing

  • Built from source on NixOS (patch on main @ 3137323), full shell run.
  • Exercised live by a niri workspace/taskbar bar-widget plugin: capsule
    chips (height + radius), wider active chip via paddingH, props
    re-applied correctly across re-renders, and confirmed the compact-host
    ordering: explicit padding survives the bar-widget chrome reset.
  • Trees omitting the new props render identically to before.
  • just format (clang-format 22) — no diff.

Manual Coverage

  • Tested on Niri
  • Tested with different bar positions and density settings
  • Tested with multiple monitors

Screenshots / Videos

niri-2026-07-10-01-59-06

before

image

after

Checklist

  • This PR is ready for review, or it is marked as Draft.
  • I read and followed the relevant guidance in CONTRIBUTING.md.
  • I ran just format with clang-format v22+ installed, or this PR has no code changes.
  • I ran the relevant build or test commands, or explained why they were not run.
  • I self-reviewed the changes.
  • I checked for new warnings or errors.
  • I will update end-user documentation after merge, or this PR does not change user-facing configuration or behavior.
  • I added or updated assets/translations/en.json, or this PR adds no new user-facing strings.
  • I did not edit non-English translation files unless this PR is explicitly for translation tooling, an import/export sync, or a maintainer-requested locale change.
  • I used the existing canonical names for config keys, IPC names, paths, and identifiers.

Additional Notes

Prop names and application semantics are copied verbatim from the
row/column branch (padding fallback for paddingV/paddingH,
values through scaled()) — one consistent vocabulary across node
types. Docs follow-up after merge: add the props to the declarative-UI
button section.

Disclaimer: AI-assisted tooling was used.

Button already inherits Flex's setRadius/setPadding; allowlist the
props and apply them the same way the row/column branch does, so
plugin chips can render as capsules without giving up click handling.

Written with AI assistance (Claude Fable 5).
@gegnep
gegnep force-pushed the plugin-button-shape branch from c5c2737 to 452cc58 Compare July 12, 2026 02:58
@gegnep

gegnep commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

rebased onto main

@ItsLemmy

Copy link
Copy Markdown
Collaborator

Thanks for the PR, but I'm gonna pass on this one, we wanna keep our button cohesive with proper padding, we already support different button "controlSize".

The main issue here as per your screenshot: you are trying to use buttons to create pills and that's not the right control for the job.

To achieve the look you want you can simply use ui.box + ui.row. ui.box supports onClick.

@ItsLemmy ItsLemmy closed this Jul 14, 2026
gegnep added a commit to gegnep/noctalia that referenced this pull request Jul 20, 2026
Plugin pills need a clickable, hoverable container with a text child:
box supports onClick but is a leaf, and buttons are the wrong control
for pill shapes (noctalia-dev#3355 review). Rows and columns already carry fill/
radius/padding, so let them take the same onClick/onHover props as
box and image.

Wrapped containers use a new ClickWrap InputArea that forwards
measure/arrange to its single child: a plain InputArea never
self-sizes, and the explicit size mirroring box/image rely on cannot
work for a content-sized flex.

Clickable wrappers also gain keyboard activation to keep parity with
Button: focusable, and the Validate keybind fires onClick. The same
activation applies to existing clickable box/image wrappers, which now
use ClickWrap and enter the tab order. No visual focus state is drawn
or exposed for containers yet.

Wrapper input handlers deviate from the reconciler's
retain-absent-props default: an empty callback name counts as unset,
and removing onClick/onHover while the other callback keeps the
wrapper alive clears the stale handler, button mask, and focusability
— a retained one would leave an invisible node that swallows clicks
and sits in tab order as a keyboard-activatable ghost.

Container onHover fires only while the container itself is the
innermost hovered input area — interactive descendants receive
enter/leave instead, matching existing dispatcher behavior.

Assisted with AI tooling.
ItsLemmy added a commit that referenced this pull request Jul 22, 2026
…3470)

* feat(plugins): expose onHover callback on button, box, and image nodes

Delivers pointer enter/leave to plugin UI trees as an onHover callback
with a "true"/"false" argument, mirroring the toggle onChange
convention. Buttons use their existing enter/leave hooks; box/image
reuse the click wrapper, which now carries an empty button mask when
hover-only so it never swallows clicks meant for ancestors.

Assisted with AI tooling.

* feat(plugins): expose onClick and onHover on row and column nodes

Plugin pills need a clickable, hoverable container with a text child:
box supports onClick but is a leaf, and buttons are the wrong control
for pill shapes (#3355 review). Rows and columns already carry fill/
radius/padding, so let them take the same onClick/onHover props as
box and image.

Wrapped containers use a new ClickWrap InputArea that forwards
measure/arrange to its single child: a plain InputArea never
self-sizes, and the explicit size mirroring box/image rely on cannot
work for a content-sized flex.

Clickable wrappers also gain keyboard activation to keep parity with
Button: focusable, and the Validate keybind fires onClick. The same
activation applies to existing clickable box/image wrappers, which now
use ClickWrap and enter the tab order. No visual focus state is drawn
or exposed for containers yet.

Wrapper input handlers deviate from the reconciler's
retain-absent-props default: an empty callback name counts as unset,
and removing onClick/onHover while the other callback keeps the
wrapper alive clears the stale handler, button mask, and focusability
— a retained one would leave an invisible node that swallows clicks
and sits in tab order as a keyboard-activatable ghost.

Container onHover fires only while the container itself is the
innermost hovered input area — interactive descendants receive
enter/leave instead, matching existing dispatcher behavior.

Assisted with AI tooling.

* fix(plugins): balance hover callbacks and dedupe wrapper callback wiring

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
bennypowers pushed a commit to bennypowers/noctalia-shell that referenced this pull request Jul 22, 2026
…octalia-dev#3470)

* feat(plugins): expose onHover callback on button, box, and image nodes

Delivers pointer enter/leave to plugin UI trees as an onHover callback
with a "true"/"false" argument, mirroring the toggle onChange
convention. Buttons use their existing enter/leave hooks; box/image
reuse the click wrapper, which now carries an empty button mask when
hover-only so it never swallows clicks meant for ancestors.

Assisted with AI tooling.

* feat(plugins): expose onClick and onHover on row and column nodes

Plugin pills need a clickable, hoverable container with a text child:
box supports onClick but is a leaf, and buttons are the wrong control
for pill shapes (noctalia-dev#3355 review). Rows and columns already carry fill/
radius/padding, so let them take the same onClick/onHover props as
box and image.

Wrapped containers use a new ClickWrap InputArea that forwards
measure/arrange to its single child: a plain InputArea never
self-sizes, and the explicit size mirroring box/image rely on cannot
work for a content-sized flex.

Clickable wrappers also gain keyboard activation to keep parity with
Button: focusable, and the Validate keybind fires onClick. The same
activation applies to existing clickable box/image wrappers, which now
use ClickWrap and enter the tab order. No visual focus state is drawn
or exposed for containers yet.

Wrapper input handlers deviate from the reconciler's
retain-absent-props default: an empty callback name counts as unset,
and removing onClick/onHover while the other callback keeps the
wrapper alive clears the stale handler, button mask, and focusability
— a retained one would leave an invisible node that swallows clicks
and sits in tab order as a keyboard-activatable ghost.

Container onHover fires only while the container itself is the
innermost hovered input area — interactive descendants receive
enter/leave instead, matching existing dispatcher behavior.

Assisted with AI tooling.

* fix(plugins): balance hover callbacks and dedupe wrapper callback wiring

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
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.

2 participants