GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
button: an icon face has a rect and an opacity a flat host can read
`Button::paint` computed its icon rect inline and drew it at a hardcoded
alpha 1.0. Neither was reachable from a flat-path host, which consumes
`all_quads` and a text list — an image is neither a quad nor a label, so a
host that wants to draw the icon itself had to re-derive where it goes.
`icon_rect(rect)` is that geometry, now public and read by `paint` as well,
so the glyph lands in the same place on both paths. `with_icon_alpha` gives
the icon the one state lever it can have: `PaintCtx::image` carries no color
and images ignore vertex color, so a disabled icon button dims rather than
graying its glyph the way a label would.
src/widget/input/button.rs | 57 ++++++++++++++++++++++++++++++++++------------
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 0b0bd71..c7cb64d 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -35,6 +35,10 @@ pub struct Button {
/// Icon face: an uploaded texture `(image id, pixel w, pixel h)` drawn
/// centered in place of the label (see [`crate::upload_icon`]).
icon: Option<(u32, f32, f32)>,
+ /// Opacity of the icon face — the ONLY state lever an icon has, since
+ /// `PaintCtx::image` carries no color and images ignore vertex color. A
+ /// disabled icon button dims instead of graying its glyph.
+ icon_alpha: f32,
hovered: bool,
/// Keyboard focus, tracked from `FocusIn`/`FocusOut` the way Checkbox does —
/// `Paint` never sees the `Widget` base, so the flag has to live here to be
@@ -73,6 +77,7 @@ impl Button {
justify: Justification::Center,
label: None,
icon: None,
+ icon_alpha: 1.0,
hovered: false,
focused: false,
raised: crate::layout::control_relief(),
@@ -112,6 +117,34 @@ impl Button {
self.icon.is_some()
}
+ /// Where the icon face draws inside `rect`: centered, inset one 4px margin
+ /// per side from the shorter extent, native aspect kept. `None` when this
+ /// button has no icon.
+ ///
+ /// Public because a flat-path host draws the icon itself — it consumes
+ /// `all_quads` and a text list, so `paint` never runs for it and an image
+ /// is neither a quad nor a label. Keeping the geometry here means the icon
+ /// lands in the same place on both paths.
+ pub fn icon_rect(&self, rect: Rect) -> Option<(u32, Rect, f32)> {
+ let (image, iw, ih) = self.icon?;
+ let s = (rect.width.min(rect.height) - 8.0).max(4.0);
+ let (dw, dh) = if iw >= ih {
+ (s, s * ih / iw.max(1.0))
+ } else {
+ (s * iw / ih.max(1.0), s)
+ };
+ Some((
+ image,
+ Rect {
+ x: rect.x + (rect.width - dw) / 2.0,
+ y: rect.y + (rect.height - dh) / 2.0,
+ width: dw,
+ height: dh,
+ },
+ self.icon_alpha,
+ ))
+ }
+
/// Hover state, also settable by immediate-mode hosts that hit-test themselves.
pub fn hovered(&self) -> bool {
self.hovered
@@ -170,6 +203,12 @@ impl Adapted<Button> {
self
}
+ /// Dim the icon face — see the `icon_alpha` field. 1.0 is fully opaque.
+ pub fn with_icon_alpha(mut self, alpha: f32) -> Self {
+ self.icon_alpha = alpha;
+ self
+ }
+
/// Raised style: see the `raised` field.
pub fn with_raised(mut self, raised: bool) -> Self {
self.raised = raised;
@@ -354,20 +393,10 @@ impl Paint for Button {
}
}
- // Icon face: centered, inset one 4px margin per side from the shorter
- // extent, native aspect kept. Replaces the label.
- if let Some((image, iw, ih)) = self.icon {
- let s = (w.min(h) - 8.0).max(4.0);
- let (dw, dh) = if iw >= ih {
- (s, s * ih / iw.max(1.0))
- } else {
- (s * iw / ih.max(1.0), s)
- };
- ctx.image(
- image,
- Rect { x: x + (w - dw) / 2.0, y: y + (h - dh) / 2.0, width: dw, height: dh },
- 1.0,
- );
+ // Icon face: replaces the label. Geometry from `icon_rect` — see there
+ // for why it is not inlined here.
+ if let Some((image, rect, alpha)) = self.icon_rect(rect) {
+ ctx.image(image, rect, alpha);
return;
}