notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit bf4b34f2d409a0735aeb3d4dbba8903e1a377085
parent 1b300428e729f70e8270821b01bc2ce89065faf9
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 27 Jan 2026 11:06:43 -0800

dave: integrate keybind hints into Allow/Deny buttons

Add ActionButton widget with integrated keybind hints inside the pill,
replacing the separate keybind_hint() calls next to the buttons. This
matches the StatusBadge pattern for showing keyboard shortcuts inline.

Also rename "Will Accept" to "Will Allow" for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mcrates/notedeck_dave/src/ui/badge.rs | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_dave/src/ui/dave.rs | 44+++++++++++++++++++++-----------------------
2 files changed, 122 insertions(+), 23 deletions(-)

diff --git a/crates/notedeck_dave/src/ui/badge.rs b/crates/notedeck_dave/src/ui/badge.rs @@ -205,3 +205,104 @@ impl<'a> StatusBadge<'a> { } } +/// A pill-shaped action button with integrated keybind hint +pub struct ActionButton<'a> { + text: &'a str, + bg_color: Color32, + text_color: Color32, + keybind: Option<&'a str>, +} + +impl<'a> ActionButton<'a> { + /// Create a new action button with the given text and colors + pub fn new(text: &'a str, bg_color: Color32, text_color: Color32) -> Self { + Self { + text, + bg_color, + text_color, + keybind: None, + } + } + + /// Add a keybind hint inside the button (e.g., "1" for key 1) + pub fn keybind(mut self, key: &'a str) -> Self { + self.keybind = Some(key); + self + } + + /// Show the button and return the response + pub fn show(self, ui: &mut Ui) -> Response { + // Calculate text size for proper allocation + let font_id = egui::FontId::proportional(13.0); + let galley = ui.painter().layout_no_wrap( + self.text.to_string(), + font_id.clone(), + self.text_color, + ); + + // Calculate keybind box size if present + let keybind_box_size = 16.0; + let keybind_spacing = 6.0; + let keybind_extra = if self.keybind.is_some() { + keybind_box_size + keybind_spacing + } else { + 0.0 + }; + + // Padding: horizontal 10px, vertical 4px + let padding = Vec2::new(10.0, 4.0); + let desired_size = + Vec2::new(galley.size().x + keybind_extra, galley.size().y) + padding * 2.0; + + let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); + + if ui.is_rect_visible(rect) { + let painter = ui.painter(); + + // Adjust color based on hover/click state + let bg_color = if response.is_pointer_button_down_on() { + self.bg_color.gamma_multiply(0.8) + } else if response.hovered() { + self.bg_color.gamma_multiply(1.15) + } else { + self.bg_color + }; + + // Full pill rounding (half of height) + let rounding = rect.height() / 2.0; + + // Background + painter.rect_filled(rect, rounding, bg_color); + + // Text (offset left if keybind present) + let text_offset_x = if self.keybind.is_some() { + -keybind_extra / 2.0 + } else { + 0.0 + }; + let text_pos = + rect.center() + Vec2::new(text_offset_x, 0.0) - galley.size() / 2.0; + painter.galley(text_pos, galley, self.text_color); + + // Draw keybind hint if present (no background, just text) + if let Some(key) = self.keybind { + let key_center = egui::pos2( + rect.right() - padding.x - keybind_box_size / 2.0, + rect.center().y, + ); + + // Keybind text using the button's text color + painter.text( + key_center, + egui::Align2::CENTER_CENTER, + key, + egui::FontId::monospace(keybind_box_size * 0.7), + self.text_color, + ); + } + } + + response + } +} + diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs @@ -429,17 +429,17 @@ impl<'a> DaveUi<'a> { let shift_held = ui.input(|i| i.modifiers.shift); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // Deny button (red) with keybind hint on left - let deny_response = ui - .add( - egui::Button::new( - egui::RichText::new("Deny") - .color(ui.visuals().widgets.active.fg_stroke.color), - ) - .fill(egui::Color32::from_rgb(178, 34, 34)), - ) - .on_hover_text("Press 2 to deny, Shift+2 to deny with message"); - super::keybind_hint(ui, "2"); + let button_text_color = ui.visuals().widgets.active.fg_stroke.color; + + // Deny button (red) with integrated keybind hint + let deny_response = super::badge::ActionButton::new( + "Deny", + egui::Color32::from_rgb(178, 34, 34), + button_text_color, + ) + .keybind("2") + .show(ui) + .on_hover_text("Press 2 to deny, Shift+2 to deny with message"); if deny_response.clicked() { if shift_held { @@ -456,17 +456,15 @@ impl<'a> DaveUi<'a> { } } - // Allow button (green) with keybind hint on left - let allow_response = ui - .add( - egui::Button::new( - egui::RichText::new("Allow") - .color(ui.visuals().widgets.active.fg_stroke.color), - ) - .fill(egui::Color32::from_rgb(34, 139, 34)), - ) - .on_hover_text("Press 1 to allow, Shift+1 to allow with message"); - super::keybind_hint(ui, "1"); + // Allow button (green) with integrated keybind hint + let allow_response = super::badge::ActionButton::new( + "Allow", + egui::Color32::from_rgb(34, 139, 34), + button_text_color, + ) + .keybind("1") + .show(ui) + .on_hover_text("Press 1 to allow, Shift+1 to allow with message"); if allow_response.clicked() { if shift_held { @@ -485,7 +483,7 @@ impl<'a> DaveUi<'a> { match self.permission_message_state { PermissionMessageState::TentativeAccept => { ui.label( - egui::RichText::new("✓ Will Accept") + egui::RichText::new("✓ Will Allow") .color(egui::Color32::from_rgb(100, 180, 100)) .strong(), );