mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
Keyboard shortcut helpers (#2202)
* eframe web: Add WebInfo::user_agent * Deprecate `Modifier::ALT_SHIFT` * Add code for formatting Modifiers and Key * Add type KeyboardShortcut * Code cleanup * Add Context::os/set_os to query/set what OS egui believes it is on * Add Fonts::has_glyph(s) * Add helper function for formatting keyboard shortcuts * Faster code * Add way to set a shortcut text on menu buttons * Cleanup * format_keyboard_shortcut -> format_shortcut * Add TODO about supporting more keyboard sumbols * Modifiers::plus * Use the new keyboard shortcuts in emark editor demo * Explain why ALT+SHIFT is a bad modifier combo * Fix doctest
This commit is contained in:
@@ -3,7 +3,8 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
|
||||
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
|
||||
input_state::*, layers::GraphicLayers, memory::Options, os::OperatingSystem,
|
||||
output::FullOutput, TextureHandle, *,
|
||||
};
|
||||
use epaint::{mutex::*, stats::*, text::Fonts, textures::TextureFilter, TessellationOptions, *};
|
||||
|
||||
@@ -36,6 +37,8 @@ struct ContextImpl {
|
||||
animation_manager: AnimationManager,
|
||||
tex_manager: WrappedTextureManager,
|
||||
|
||||
os: OperatingSystem,
|
||||
|
||||
input: InputState,
|
||||
|
||||
/// State that is collected during a frame and then cleared
|
||||
@@ -563,6 +566,59 @@ impl Context {
|
||||
pub fn tessellation_options(&self) -> RwLockWriteGuard<'_, TessellationOptions> {
|
||||
RwLockWriteGuard::map(self.write(), |c| &mut c.memory.options.tessellation_options)
|
||||
}
|
||||
|
||||
/// What operating system are we running on?
|
||||
///
|
||||
/// When compiling natively, this is
|
||||
/// figured out from the `target_os`.
|
||||
///
|
||||
/// For web, this can be figured out from the user-agent,
|
||||
/// and is done so by [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
|
||||
pub fn os(&self) -> OperatingSystem {
|
||||
self.read().os
|
||||
}
|
||||
|
||||
/// Set the operating system we are running on.
|
||||
///
|
||||
/// If you are writing wasm-based integration for egui you
|
||||
/// may want to set this based on e.g. the user-agent.
|
||||
pub fn set_os(&self, os: OperatingSystem) {
|
||||
self.write().os = os;
|
||||
}
|
||||
|
||||
/// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`).
|
||||
///
|
||||
/// Can be used to get the text for [`Button::shortcut_text`].
|
||||
pub fn format_shortcut(&self, shortcut: &KeyboardShortcut) -> String {
|
||||
let os = self.os();
|
||||
|
||||
let is_mac = matches!(os, OperatingSystem::Mac | OperatingSystem::IOS);
|
||||
|
||||
let can_show_symbols = || {
|
||||
let ModifierNames {
|
||||
alt,
|
||||
ctrl,
|
||||
shift,
|
||||
mac_cmd,
|
||||
..
|
||||
} = ModifierNames::SYMBOLS;
|
||||
|
||||
let font_id = TextStyle::Body.resolve(&self.style());
|
||||
let fonts = self.fonts();
|
||||
let mut fonts = fonts.lock();
|
||||
let font = fonts.fonts.font(&font_id);
|
||||
font.has_glyphs(alt)
|
||||
&& font.has_glyphs(ctrl)
|
||||
&& font.has_glyphs(shift)
|
||||
&& font.has_glyphs(mac_cmd)
|
||||
};
|
||||
|
||||
if is_mac && can_show_symbols() {
|
||||
shortcut.format(&ModifierNames::SYMBOLS, is_mac)
|
||||
} else {
|
||||
shortcut.format(&ModifierNames::NAMES, is_mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
||||
@@ -297,6 +297,10 @@ pub const NUM_POINTER_BUTTONS: usize = 5;
|
||||
/// State of the modifier keys. These must be fed to egui.
|
||||
///
|
||||
/// The best way to compare [`Modifiers`] is by using [`Modifiers::matches`].
|
||||
///
|
||||
/// NOTE: For cross-platform uses, ALT+SHIFT is a bad combination of modifiers
|
||||
/// as on mac that is how you type special characters,
|
||||
/// so those key presses are usually not reported to egui.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Modifiers {
|
||||
@@ -321,10 +325,6 @@ pub struct Modifiers {
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub const NONE: Self = Self {
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
@@ -354,6 +354,8 @@ impl Modifiers {
|
||||
mac_cmd: false,
|
||||
command: false,
|
||||
};
|
||||
|
||||
#[deprecated = "Use `Modifiers::ALT | Modifiers::SHIFT` instead"]
|
||||
pub const ALT_SHIFT: Self = Self {
|
||||
alt: true,
|
||||
ctrl: false,
|
||||
@@ -380,24 +382,50 @@ impl Modifiers {
|
||||
command: true,
|
||||
};
|
||||
|
||||
#[inline(always)]
|
||||
/// ```
|
||||
/// # use egui::Modifiers;
|
||||
/// assert_eq!(
|
||||
/// Modifiers::CTRL | Modifiers::ALT,
|
||||
/// Modifiers { ctrl: true, alt: true, ..Default::default() }
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// Modifiers::ALT.plus(Modifiers::CTRL),
|
||||
/// Modifiers::CTRL.plus(Modifiers::ALT),
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// Modifiers::CTRL | Modifiers::ALT,
|
||||
/// Modifiers::CTRL.plus(Modifiers::ALT),
|
||||
/// );
|
||||
/// ```
|
||||
#[inline]
|
||||
pub const fn plus(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
alt: self.alt | rhs.alt,
|
||||
ctrl: self.ctrl | rhs.ctrl,
|
||||
shift: self.shift | rhs.shift,
|
||||
mac_cmd: self.mac_cmd | rhs.mac_cmd,
|
||||
command: self.command | rhs.command,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
self == &Self::default()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
pub fn any(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
/// Is shift the only pressed button?
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
pub fn shift_only(&self) -> bool {
|
||||
self.shift && !(self.alt || self.command)
|
||||
}
|
||||
|
||||
/// true if only [`Self::ctrl`] or only [`Self::mac_cmd`] is pressed.
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
pub fn command_only(&self) -> bool {
|
||||
!self.alt && !self.shift && self.command
|
||||
}
|
||||
@@ -453,17 +481,82 @@ impl Modifiers {
|
||||
impl std::ops::BitOr for Modifiers {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
alt: self.alt | rhs.alt,
|
||||
ctrl: self.ctrl | rhs.ctrl,
|
||||
shift: self.shift | rhs.shift,
|
||||
mac_cmd: self.mac_cmd | rhs.mac_cmd,
|
||||
command: self.command | rhs.command,
|
||||
}
|
||||
self.plus(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Names of different modifier keys.
|
||||
///
|
||||
/// Used to name modifiers.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ModifierNames<'a> {
|
||||
pub is_short: bool,
|
||||
|
||||
pub alt: &'a str,
|
||||
pub ctrl: &'a str,
|
||||
pub shift: &'a str,
|
||||
pub mac_cmd: &'a str,
|
||||
|
||||
/// What goes between the names
|
||||
pub concat: &'a str,
|
||||
}
|
||||
|
||||
impl ModifierNames<'static> {
|
||||
/// ⌥ ^ ⇧ ⌘ - NOTE: not supported by the default egui font.
|
||||
pub const SYMBOLS: Self = Self {
|
||||
is_short: true,
|
||||
alt: "⌥",
|
||||
ctrl: "^",
|
||||
shift: "⇧",
|
||||
mac_cmd: "⌘",
|
||||
concat: "",
|
||||
};
|
||||
|
||||
/// Alt, Ctrl, Shift, Command
|
||||
pub const NAMES: Self = Self {
|
||||
is_short: false,
|
||||
alt: "Alt",
|
||||
ctrl: "Ctrl",
|
||||
shift: "Shift",
|
||||
mac_cmd: "Command",
|
||||
concat: "+",
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a> ModifierNames<'a> {
|
||||
pub fn format(&self, modifiers: &Modifiers, is_mac: bool) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
let mut append_if = |modifier_is_active, modifier_name| {
|
||||
if modifier_is_active {
|
||||
if !s.is_empty() {
|
||||
s += self.concat;
|
||||
}
|
||||
s += modifier_name;
|
||||
}
|
||||
};
|
||||
|
||||
if is_mac {
|
||||
append_if(modifiers.ctrl, self.ctrl);
|
||||
append_if(modifiers.shift, self.shift);
|
||||
append_if(modifiers.alt, self.alt);
|
||||
append_if(modifiers.mac_cmd || modifiers.command, self.mac_cmd);
|
||||
} else {
|
||||
append_if(modifiers.ctrl, self.ctrl);
|
||||
append_if(modifiers.alt, self.alt);
|
||||
append_if(modifiers.shift, self.shift);
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Keyboard keys.
|
||||
///
|
||||
/// Includes all keys egui is interested in (such as `Home` and `End`)
|
||||
@@ -563,6 +656,132 @@ pub enum Key {
|
||||
F20,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
/// Emoji or name representing the key
|
||||
pub fn symbol_or_name(self) -> &'static str {
|
||||
// TODO(emilk): add support for more unicode symbols (see for instance https://wincent.com/wiki/Unicode_representations_of_modifier_keys).
|
||||
// Before we do we must first make sure they are supported in `Fonts` though,
|
||||
// so perhaps this functions needs to take a `supports_character: impl Fn(char) -> bool` or something.
|
||||
match self {
|
||||
Key::ArrowDown => "⏷",
|
||||
Key::ArrowLeft => "⏴",
|
||||
Key::ArrowRight => "⏵",
|
||||
Key::ArrowUp => "⏶",
|
||||
_ => self.name(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human-readable English name.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Key::ArrowDown => "Down",
|
||||
Key::ArrowLeft => "Left",
|
||||
Key::ArrowRight => "Right",
|
||||
Key::ArrowUp => "Up",
|
||||
Key::Escape => "Escape",
|
||||
Key::Tab => "Tab",
|
||||
Key::Backspace => "Backspace",
|
||||
Key::Enter => "Enter",
|
||||
Key::Space => "Space",
|
||||
Key::Insert => "Insert",
|
||||
Key::Delete => "Delete",
|
||||
Key::Home => "Home",
|
||||
Key::End => "End",
|
||||
Key::PageUp => "PageUp",
|
||||
Key::PageDown => "PageDown",
|
||||
Key::Num0 => "0",
|
||||
Key::Num1 => "1",
|
||||
Key::Num2 => "2",
|
||||
Key::Num3 => "3",
|
||||
Key::Num4 => "4",
|
||||
Key::Num5 => "5",
|
||||
Key::Num6 => "6",
|
||||
Key::Num7 => "7",
|
||||
Key::Num8 => "8",
|
||||
Key::Num9 => "9",
|
||||
Key::A => "A",
|
||||
Key::B => "B",
|
||||
Key::C => "C",
|
||||
Key::D => "D",
|
||||
Key::E => "E",
|
||||
Key::F => "F",
|
||||
Key::G => "G",
|
||||
Key::H => "H",
|
||||
Key::I => "I",
|
||||
Key::J => "J",
|
||||
Key::K => "K",
|
||||
Key::L => "L",
|
||||
Key::M => "M",
|
||||
Key::N => "N",
|
||||
Key::O => "O",
|
||||
Key::P => "P",
|
||||
Key::Q => "Q",
|
||||
Key::R => "R",
|
||||
Key::S => "S",
|
||||
Key::T => "T",
|
||||
Key::U => "U",
|
||||
Key::V => "V",
|
||||
Key::W => "W",
|
||||
Key::X => "X",
|
||||
Key::Y => "Y",
|
||||
Key::Z => "Z",
|
||||
Key::F1 => "F1",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F13 => "F13",
|
||||
Key::F14 => "F14",
|
||||
Key::F15 => "F15",
|
||||
Key::F16 => "F16",
|
||||
Key::F17 => "F17",
|
||||
Key::F18 => "F18",
|
||||
Key::F19 => "F19",
|
||||
Key::F20 => "F20",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A keyboard shortcut, e.g. `Ctrl+Alt+W`.
|
||||
///
|
||||
/// Can be used with [`crate::InputState::consume_shortcut`]
|
||||
/// and [`crate::Context::format_shortcut`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct KeyboardShortcut {
|
||||
pub modifiers: Modifiers,
|
||||
pub key: Key,
|
||||
}
|
||||
|
||||
impl KeyboardShortcut {
|
||||
pub const fn new(modifiers: Modifiers, key: Key) -> Self {
|
||||
Self { modifiers, key }
|
||||
}
|
||||
|
||||
pub fn format(&self, names: &ModifierNames<'_>, is_mac: bool) -> String {
|
||||
let mut s = names.format(&self.modifiers, is_mac);
|
||||
if !s.is_empty() {
|
||||
s += names.concat;
|
||||
}
|
||||
if names.is_short {
|
||||
s += self.key.symbol_or_name();
|
||||
} else {
|
||||
s += self.key.name();
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl RawInput {
|
||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
|
||||
@@ -267,6 +267,14 @@ impl InputState {
|
||||
match_found
|
||||
}
|
||||
|
||||
/// Check if the given shortcut has been pressed.
|
||||
///
|
||||
/// If so, `true` is returned and the key pressed is consumed, so that this will only return `true` once.
|
||||
pub fn consume_shortcut(&mut self, shortcut: &KeyboardShortcut) -> bool {
|
||||
let KeyboardShortcut { modifiers, key } = *shortcut;
|
||||
self.consume_key(modifiers, key)
|
||||
}
|
||||
|
||||
/// Was the given key pressed this frame?
|
||||
pub fn key_pressed(&self, desired_key: Key) -> bool {
|
||||
self.num_presses(desired_key) > 0
|
||||
|
||||
@@ -312,6 +312,7 @@ pub mod layers;
|
||||
mod layout;
|
||||
mod memory;
|
||||
pub mod menu;
|
||||
pub mod os;
|
||||
mod painter;
|
||||
pub(crate) mod placer;
|
||||
mod response;
|
||||
|
||||
71
crates/egui/src/os.rs
Normal file
71
crates/egui/src/os.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum OperatingSystem {
|
||||
/// Unknown OS - could be wasm
|
||||
Unknown,
|
||||
|
||||
/// Android OS.
|
||||
Android,
|
||||
|
||||
/// Apple iPhone OS.
|
||||
IOS,
|
||||
|
||||
/// Linux or Unix other than Android.
|
||||
Nix,
|
||||
|
||||
/// MacOS.
|
||||
Mac,
|
||||
|
||||
/// Windows.
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl Default for OperatingSystem {
|
||||
fn default() -> Self {
|
||||
Self::from_target_os()
|
||||
}
|
||||
}
|
||||
|
||||
impl OperatingSystem {
|
||||
pub const fn from_target_os() -> Self {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
Self::Unknown
|
||||
} else if cfg!(target_os = "android") {
|
||||
Self::Android
|
||||
} else if cfg!(target_os = "ios") {
|
||||
Self::IOS
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Self::Mac
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Self::Android
|
||||
} else if cfg!(target_os = "linux")
|
||||
|| cfg!(target_os = "dragonfly")
|
||||
|| cfg!(target_os = "freebsd")
|
||||
|| cfg!(target_os = "netbsd")
|
||||
|| cfg!(target_os = "openbsd")
|
||||
{
|
||||
Self::Nix
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: try to guess from the user-agent of a browser.
|
||||
pub fn from_user_agent(user_agent: &str) -> Self {
|
||||
if user_agent.contains("Android") {
|
||||
Self::Android
|
||||
} else if user_agent.contains("like Mac") {
|
||||
Self::IOS
|
||||
} else if user_agent.contains("Win") {
|
||||
Self::Windows
|
||||
} else if user_agent.contains("Mac") {
|
||||
Self::Mac
|
||||
} else if user_agent.contains("Linux")
|
||||
|| user_agent.contains("X11")
|
||||
|| user_agent.contains("Unix")
|
||||
{
|
||||
Self::Nix
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,12 @@ pub enum TextStyle {
|
||||
/// Normal labels. Easily readable, doesn't take up too much space.
|
||||
Body,
|
||||
|
||||
/// Same size as [`Self::Body`], but used when monospace is important (for aligning number, code snippets, etc).
|
||||
/// Same size as [`Self::Body`], but used when monospace is important (for code snippets, aligning numbers, etc).
|
||||
Monospace,
|
||||
|
||||
/// Buttons. Maybe slightly bigger than [`Self::Body`].
|
||||
/// Signifies that he item is interactive.
|
||||
///
|
||||
/// Signifies that he item can be interacted with.
|
||||
Button,
|
||||
|
||||
/// Heading. Probably larger than [`Self::Body`].
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::*;
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
pub struct Button {
|
||||
text: WidgetText,
|
||||
shortcut_text: WidgetText,
|
||||
wrap: Option<bool>,
|
||||
/// None means default for interact
|
||||
fill: Option<Color32>,
|
||||
@@ -36,6 +37,7 @@ impl Button {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
shortcut_text: Default::default(),
|
||||
wrap: None,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
@@ -47,23 +49,16 @@ impl Button {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the size Vec2 provided.
|
||||
/// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the provided size.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn image_and_text(
|
||||
texture_id: TextureId,
|
||||
size: impl Into<Vec2>,
|
||||
image_size: impl Into<Vec2>,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
fill: None,
|
||||
stroke: None,
|
||||
sense: Sense::click(),
|
||||
small: false,
|
||||
frame: None,
|
||||
wrap: None,
|
||||
min_size: Vec2::ZERO,
|
||||
image: Some(widgets::Image::new(texture_id, size)),
|
||||
image: Some(widgets::Image::new(texture_id, image_size)),
|
||||
..Self::new(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,16 +111,28 @@ impl Button {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the minimum size of the button.
|
||||
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
||||
self.min_size = min_size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show some text on the right side of the button, in weak color.
|
||||
///
|
||||
/// Designed for menu buttons, for setting a keyboard shortcut text (e.g. `Ctrl+S`).
|
||||
///
|
||||
/// The text can be created with [`Context::format_shortcut`].
|
||||
pub fn shortcut_text(mut self, shortcut_text: impl Into<WidgetText>) -> Self {
|
||||
self.shortcut_text = shortcut_text.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Button {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Button {
|
||||
text,
|
||||
shortcut_text,
|
||||
wrap,
|
||||
fill,
|
||||
stroke,
|
||||
@@ -142,38 +149,39 @@ impl Widget for Button {
|
||||
if small {
|
||||
button_padding.y = 0.0;
|
||||
}
|
||||
let total_extra = button_padding + button_padding;
|
||||
|
||||
let wrap_width = ui.available_width() - total_extra.x;
|
||||
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
||||
let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x;
|
||||
if let Some(image) = image {
|
||||
text_wrap_width -= image.size().x + ui.spacing().icon_spacing;
|
||||
}
|
||||
if !shortcut_text.is_empty() {
|
||||
text_wrap_width -= 60.0; // Some space for the shortcut text (which we never wrap).
|
||||
}
|
||||
|
||||
let mut desired_size = text.size() + 2.0 * button_padding;
|
||||
let text = text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button);
|
||||
let shortcut_text = (!shortcut_text.is_empty())
|
||||
.then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button));
|
||||
|
||||
let mut desired_size = text.size();
|
||||
if let Some(image) = image {
|
||||
desired_size.x += image.size().x + ui.spacing().icon_spacing;
|
||||
desired_size.y = desired_size.y.max(image.size().y);
|
||||
}
|
||||
if let Some(shortcut_text) = &shortcut_text {
|
||||
desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x;
|
||||
desired_size.y = desired_size.y.max(shortcut_text.size().y);
|
||||
}
|
||||
if !small {
|
||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
}
|
||||
desired_size += 2.0 * button_padding;
|
||||
desired_size = desired_size.at_least(min_size);
|
||||
|
||||
if let Some(image) = image {
|
||||
desired_size.x += image.size().x + ui.spacing().icon_spacing;
|
||||
desired_size.y = desired_size.y.max(image.size().y + 2.0 * button_padding.y);
|
||||
}
|
||||
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
let text_pos = if let Some(image) = image {
|
||||
let icon_spacing = ui.spacing().icon_spacing;
|
||||
pos2(
|
||||
rect.min.x + button_padding.x + image.size().x + icon_spacing,
|
||||
rect.center().y - 0.5 * text.size().y,
|
||||
)
|
||||
} else {
|
||||
ui.layout()
|
||||
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||
.min
|
||||
};
|
||||
|
||||
if frame {
|
||||
let fill = fill.unwrap_or(visuals.bg_fill);
|
||||
@@ -186,15 +194,38 @@ impl Widget for Button {
|
||||
);
|
||||
}
|
||||
|
||||
let text_pos = if let Some(image) = image {
|
||||
let icon_spacing = ui.spacing().icon_spacing;
|
||||
pos2(
|
||||
rect.min.x + button_padding.x + image.size().x + icon_spacing,
|
||||
rect.center().y - 0.5 * text.size().y,
|
||||
)
|
||||
} else {
|
||||
ui.layout()
|
||||
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||
.min
|
||||
};
|
||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||
}
|
||||
|
||||
if let Some(image) = image {
|
||||
let image_rect = Rect::from_min_size(
|
||||
pos2(rect.min.x, rect.center().y - 0.5 - (image.size().y / 2.0)),
|
||||
image.size(),
|
||||
);
|
||||
image.paint_at(ui, image_rect);
|
||||
if let Some(shortcut_text) = shortcut_text {
|
||||
let shortcut_text_pos = pos2(
|
||||
rect.max.x - button_padding.x - shortcut_text.size().x,
|
||||
rect.center().y - 0.5 * shortcut_text.size().y,
|
||||
);
|
||||
shortcut_text.paint_with_fallback_color(
|
||||
ui.painter(),
|
||||
shortcut_text_pos,
|
||||
ui.visuals().weak_text_color(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(image) = image {
|
||||
let image_rect = Rect::from_min_size(
|
||||
pos2(rect.min.x, rect.center().y - 0.5 - (image.size().y / 2.0)),
|
||||
image.size(),
|
||||
);
|
||||
image.paint_at(ui, image_rect);
|
||||
}
|
||||
}
|
||||
|
||||
response
|
||||
|
||||
Reference in New Issue
Block a user