Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable shortcut text independently of widget type, or add support to Checkbox #4363

Open
djeedai opened this issue Apr 14, 2024 · 2 comments

Comments

@djeedai
Copy link

djeedai commented Apr 14, 2024

This is similar to #429 but without the complexity of actually implementing the shortcut functionality, only the text rendering part.

Is your feature request related to a problem? Please describe.

I need a togglable menu item to show or hide a panel. I'm using a Checkbox inside a Menu. I want also a keyboard shortcut to be displayed on the right side, like Button::shortcut_text, but this is not implemented for Checkbox, only for Button.

Describe the solution you'd like

Either of:

  • Add Checkbox::shortcut_text (but there might be other widgets which also need that); or
  • Make the "shortcut text" for menu items a separate feature from the menu item widget itself.

Describe alternatives you've considered

I use this workaround which works fine, is just verbose and not super intuitive (some other issue suggested to use Layout instead of Ui::horizontal() but this is wrong, as this will expand vertically the menu item to the entire screen; we need the Layout to expand inside the Horizontal so that it expands only horizontally (I think)):

ui.horizontal(|ui| {
    egui::Checkbox::new(&mut panel.visible, title);
    // Align shortcut to the right
    ui.with_layout(
        egui::Layout::right_to_left(egui::Align::Center),
        |ui| {
            ui.label(format!("{:?}", shortcut));
        },
    );
});

Note: shortcut here is Bevy's Keycode so will display the key name, like "F1" for example. But the solution works with any text I believe.

Additional praise

This crate is awesome, thanks a lot for all the hard work, this is so useful! ❤️

@lukexor
Copy link

lukexor commented Jul 4, 2024

Here's my version that is generic over the widget, but in 0.28.0 it seems to allocate horizontal too much space when used within a menu compared to the provided shortcut_text. I'm still trying to figure out how to layout the shortcut right-aligned similar to the Button implementation, but I'm not sure if it's possible while still using the provided ui implementation of the inner Widget

pub trait ShortcutText<'a>
where
    Self: Sized + 'a,
{
    fn shortcut_text(self, shortcut_text: impl Into<WidgetText>) -> ShortcutWidget<'a, Self> {
        ShortcutWidget {
            inner: self,
            shortcut_text: shortcut_text.into(),
            phantom: std::marker::PhantomData,
        }
    }
}

impl<'a> ShortcutText<'a> for Checkbox<'a> {}
impl<'a> ShortcutText<'a> for ToggleValue<'a> {}
impl<'a, T> ShortcutText<'a> for RadioValue<'a, T> {}

#[must_use]
pub struct ShortcutWidget<'a, T> {
    inner: T,
    shortcut_text: WidgetText,
    phantom: std::marker::PhantomData<&'a ()>,
}

impl<'a, T> Deref for ShortcutWidget<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<'a, T> DerefMut for ShortcutWidget<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl<'a, T> Widget for ShortcutWidget<'a, T>
where
    T: Widget,
{
    fn ui(self, ui: &mut Ui) -> Response {
        ui.horizontal(|ui| {
            let res = self.inner.ui(ui);

            if !self.shortcut_text.is_empty() {
                ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
                    // Ensure sense is set to hover so that screen readers don't try to read it,
                    // consistent with `shortcut_text` on `Button`
                    ui.add(egui::Label::new(self.shortcut_text.weak()).sense(egui::Sense::hover()));
                });
            }
            res
        })
        .inner
    }
}

#[must_use]
pub struct ToggleValue<'a> {
    selected: &'a mut bool,
    text: WidgetText,
}

impl<'a> ToggleValue<'a> {
    pub fn new(selected: &'a mut bool, text: impl Into<WidgetText>) -> Self {
        Self {
            selected,
            text: text.into(),
        }
    }
}

impl<'a> Widget for ToggleValue<'a> {
    fn ui(self, ui: &mut Ui) -> Response {
        let mut res = ui.selectable_label(*self.selected, self.text);
        if res.clicked() {
            *self.selected = !*self.selected;
            res.mark_changed();
        }
        res
    }
}

#[must_use]
pub struct RadioValue<'a, T> {
    current_value: &'a mut T,
    alternative: T,
    text: WidgetText,
}

impl<'a, T: PartialEq> RadioValue<'a, T> {
    pub fn new(current_value: &'a mut T, alternative: T, text: impl Into<WidgetText>) -> Self {
        Self {
            current_value,
            alternative,
            text: text.into(),
        }
    }
}

impl<'a, T: PartialEq> Widget for RadioValue<'a, T> {
    fn ui(self, ui: &mut Ui) -> Response {
        let mut res = ui.radio(*self.current_value == self.alternative, self.text);
        if res.clicked() && *self.current_value != self.alternative {
            *self.current_value = self.alternative;
            res.mark_changed();
        }
        res
    }
}

@lukexor
Copy link

lukexor commented Jul 4, 2024

This update seems to fix the width issue:

        ui.horizontal(|ui| {
            let res = self.inner.ui(ui);

            if !self.shortcut_text.is_empty() {
                let shortcut_galley = self.shortcut_text.into_galley(
                    ui,
                    Some(TextWrapMode::Extend),
                    f32::INFINITY,
                    TextStyle::Button,
                );

                let available_rect = ui.available_rect_before_wrap();

                let gap_before_shortcut_text = ui.spacing().item_spacing.x;
                let mut desired_size = shortcut_galley.size();
                desired_size.x += gap_before_shortcut_text;
                // Ensure sense is set to hover so that screen readers don't try to read it,
                // consistent with `shortcut_text` on `Button`
                let (rect, _) = ui.allocate_at_least(desired_size, Sense::hover());

                if ui.is_rect_visible(rect) {
                    let text_pos = Pos2::new(
                        available_rect.max.x - shortcut_galley.size().x,
                        rect.center().y - 0.5 * shortcut_galley.size().y,
                    );
                    ui.painter()
                        .galley(text_pos, shortcut_galley, ui.visuals().weak_text_color());
                }
            }
            res
        })

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

No branches or pull requests

2 participants