use epaint::{ColorImage, MarginF32};
use crate::{
Key, OrderedViewportIdMap, Theme, ViewportId, ViewportIdMap,
emath::{Pos2, Rect, Vec2},
};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RawInput {
pub viewport_id: ViewportId,
pub viewports: ViewportIdMap<ViewportInfo>,
pub safe_area_insets: Option<SafeAreaInsets>,
pub screen_rect: Option<Rect>,
pub max_texture_side: Option<usize>,
pub time: Option<f64>,
pub predicted_dt: f32,
pub modifiers: Modifiers,
pub events: Vec<Event>,
pub hovered_files: Vec<HoveredFile>,
pub dropped_files: Vec<DroppedFile>,
pub focused: bool,
pub system_theme: Option<Theme>,
}
impl Default for RawInput {
fn default() -> Self {
Self {
viewport_id: ViewportId::ROOT,
viewports: std::iter::once((ViewportId::ROOT, Default::default())).collect(),
screen_rect: None,
max_texture_side: None,
time: None,
predicted_dt: 1.0 / 60.0,
modifiers: Modifiers::default(),
events: vec![],
hovered_files: Default::default(),
dropped_files: Default::default(),
focused: true, system_theme: None,
safe_area_insets: Default::default(),
}
}
}
impl RawInput {
#[inline]
pub fn viewport(&self) -> &ViewportInfo {
self.viewports.get(&self.viewport_id).expect("Failed to find current viewport in egui RawInput. This is the fault of the egui backend")
}
pub fn take(&mut self) -> Self {
Self {
viewport_id: self.viewport_id,
viewports: self
.viewports
.iter_mut()
.map(|(id, info)| (*id, info.take()))
.collect(),
screen_rect: self.screen_rect.take(),
safe_area_insets: self.safe_area_insets.take(),
max_texture_side: self.max_texture_side.take(),
time: self.time,
predicted_dt: self.predicted_dt,
modifiers: self.modifiers,
events: std::mem::take(&mut self.events),
hovered_files: self.hovered_files.clone(),
dropped_files: std::mem::take(&mut self.dropped_files),
focused: self.focused,
system_theme: self.system_theme,
}
}
pub fn append(&mut self, newer: Self) {
let Self {
viewport_id: viewport_ids,
viewports,
screen_rect,
max_texture_side,
time,
predicted_dt,
modifiers,
mut events,
mut hovered_files,
mut dropped_files,
focused,
system_theme,
safe_area_insets: safe_area,
} = newer;
self.viewport_id = viewport_ids;
self.viewports = viewports;
self.screen_rect = screen_rect.or(self.screen_rect);
self.max_texture_side = max_texture_side.or(self.max_texture_side);
self.time = time; self.predicted_dt = predicted_dt; self.modifiers = modifiers; self.events.append(&mut events);
self.hovered_files.append(&mut hovered_files);
self.dropped_files.append(&mut dropped_files);
self.focused = focused;
self.system_theme = system_theme;
self.safe_area_insets = safe_area;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ViewportEvent {
Close,
}
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ViewportInfo {
pub parent: Option<crate::ViewportId>,
pub title: Option<String>,
pub events: Vec<ViewportEvent>,
pub native_pixels_per_point: Option<f32>,
pub monitor_size: Option<Vec2>,
pub inner_rect: Option<Rect>,
pub outer_rect: Option<Rect>,
pub minimized: Option<bool>,
pub maximized: Option<bool>,
pub fullscreen: Option<bool>,
pub focused: Option<bool>,
}
impl ViewportInfo {
pub fn close_requested(&self) -> bool {
self.events.contains(&ViewportEvent::Close)
}
pub fn take(&mut self) -> Self {
Self {
parent: self.parent,
title: self.title.clone(),
events: std::mem::take(&mut self.events),
native_pixels_per_point: self.native_pixels_per_point,
monitor_size: self.monitor_size,
inner_rect: self.inner_rect,
outer_rect: self.outer_rect,
minimized: self.minimized,
maximized: self.maximized,
fullscreen: self.fullscreen,
focused: self.focused,
}
}
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
parent,
title,
events,
native_pixels_per_point,
monitor_size,
inner_rect,
outer_rect,
minimized,
maximized,
fullscreen,
focused,
} = self;
crate::Grid::new("viewport_info").show(ui, |ui| {
ui.label("Parent:");
ui.label(opt_as_str(parent));
ui.end_row();
ui.label("Title:");
ui.label(opt_as_str(title));
ui.end_row();
ui.label("Events:");
ui.label(format!("{events:?}"));
ui.end_row();
ui.label("Native pixels-per-point:");
ui.label(opt_as_str(native_pixels_per_point));
ui.end_row();
ui.label("Monitor size:");
ui.label(opt_as_str(monitor_size));
ui.end_row();
ui.label("Inner rect:");
ui.label(opt_rect_as_string(inner_rect));
ui.end_row();
ui.label("Outer rect:");
ui.label(opt_rect_as_string(outer_rect));
ui.end_row();
ui.label("Minimized:");
ui.label(opt_as_str(minimized));
ui.end_row();
ui.label("Maximized:");
ui.label(opt_as_str(maximized));
ui.end_row();
ui.label("Fullscreen:");
ui.label(opt_as_str(fullscreen));
ui.end_row();
ui.label("Focused:");
ui.label(opt_as_str(focused));
ui.end_row();
fn opt_rect_as_string(v: &Option<Rect>) -> String {
v.as_ref().map_or(String::new(), |r| {
format!("Pos: {:?}, size: {:?}", r.min, r.size())
})
}
fn opt_as_str<T: std::fmt::Debug>(v: &Option<T>) -> String {
v.as_ref().map_or(String::new(), |v| format!("{v:?}"))
}
});
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct HoveredFile {
pub path: Option<std::path::PathBuf>,
pub mime: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DroppedFile {
pub path: Option<std::path::PathBuf>,
pub name: String,
pub mime: String,
pub last_modified: Option<std::time::SystemTime>,
pub bytes: Option<std::sync::Arc<[u8]>>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Event {
Copy,
Cut,
Paste(String),
Text(String),
Key {
key: Key,
physical_key: Option<Key>,
pressed: bool,
repeat: bool,
modifiers: Modifiers,
},
PointerMoved(Pos2),
MouseMoved(Vec2),
PointerButton {
pos: Pos2,
button: PointerButton,
pressed: bool,
modifiers: Modifiers,
},
PointerGone,
Zoom(f32),
Rotate(f32),
Ime(ImeEvent),
Touch {
device_id: TouchDeviceId,
id: TouchId,
phase: TouchPhase,
pos: Pos2,
force: Option<f32>,
},
MouseWheel {
unit: MouseWheelUnit,
delta: Vec2,
modifiers: Modifiers,
},
WindowFocused(bool),
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest),
Screenshot {
viewport_id: crate::ViewportId,
user_data: crate::UserData,
image: std::sync::Arc<ColorImage>,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ImeEvent {
Enabled,
Preedit(String),
Commit(String),
Disabled,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum PointerButton {
Primary = 0,
Secondary = 1,
Middle = 2,
Extra1 = 3,
Extra2 = 4,
}
pub const NUM_POINTER_BUTTONS: usize = 5;
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Modifiers {
pub alt: bool,
pub ctrl: bool,
pub shift: bool,
pub mac_cmd: bool,
pub command: bool,
}
impl std::fmt::Debug for Modifiers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_none() {
return write!(f, "Modifiers::NONE");
}
let Self {
alt,
ctrl,
shift,
mac_cmd,
command,
} = *self;
let mut debug = f.debug_struct("Modifiers");
if alt {
debug.field("alt", &true);
}
if ctrl {
debug.field("ctrl", &true);
}
if shift {
debug.field("shift", &true);
}
if mac_cmd {
debug.field("mac_cmd", &true);
}
if command {
debug.field("command", &true);
}
debug.finish()
}
}
impl Modifiers {
pub const NONE: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: false,
command: false,
};
pub const ALT: Self = Self {
alt: true,
ctrl: false,
shift: false,
mac_cmd: false,
command: false,
};
pub const CTRL: Self = Self {
alt: false,
ctrl: true,
shift: false,
mac_cmd: false,
command: false,
};
pub const SHIFT: Self = Self {
alt: false,
ctrl: false,
shift: true,
mac_cmd: false,
command: false,
};
pub const MAC_CMD: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: true,
command: false,
};
pub const COMMAND: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: false,
command: true,
};
#[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]
pub fn any(&self) -> bool {
!self.is_none()
}
#[inline]
pub fn all(&self) -> bool {
self.alt && self.ctrl && self.shift && self.command
}
#[inline]
pub fn shift_only(&self) -> bool {
self.shift && !(self.alt || self.command)
}
#[inline]
pub fn command_only(&self) -> bool {
!self.alt && !self.shift && self.command
}
pub fn matches_logically(&self, pattern: Self) -> bool {
if pattern.alt && !self.alt {
return false;
}
if pattern.shift && !self.shift {
return false;
}
self.cmd_ctrl_matches(pattern)
}
pub fn matches_exact(&self, pattern: Self) -> bool {
if pattern.alt != self.alt || pattern.shift != self.shift {
return false;
}
self.cmd_ctrl_matches(pattern)
}
pub fn matches_any(&self, pattern: Self) -> bool {
if self.alt && pattern.alt {
return true;
}
if self.shift && pattern.shift {
return true;
}
if self.ctrl && pattern.ctrl {
return true;
}
if self.mac_cmd && pattern.mac_cmd {
return true;
}
if (self.mac_cmd || self.command || self.ctrl) && pattern.command {
return true;
}
false
}
pub fn cmd_ctrl_matches(&self, pattern: Self) -> bool {
if pattern.mac_cmd {
if !self.mac_cmd {
return false;
}
if pattern.ctrl != self.ctrl {
return false;
}
return true;
}
if !pattern.ctrl && !pattern.command {
return !self.ctrl && !self.command;
}
if pattern.ctrl && !self.ctrl {
return false;
}
if pattern.command && !self.command {
return false;
}
true
}
pub fn contains(&self, query: Self) -> bool {
if query == Self::default() {
return true;
}
let Self {
alt,
ctrl,
shift,
mac_cmd,
command,
} = *self;
if alt && query.alt {
return self.contains(Self {
alt: false,
..query
});
}
if shift && query.shift {
return self.contains(Self {
shift: false,
..query
});
}
if (ctrl || command) && (query.ctrl || query.command) {
return self.contains(Self {
command: false,
ctrl: false,
..query
});
}
if (mac_cmd || command) && (query.mac_cmd || query.command) {
return self.contains(Self {
mac_cmd: false,
command: false,
..query
});
}
false
}
}
impl std::ops::BitOr for Modifiers {
type Output = Self;
#[inline]
fn bitor(self, rhs: Self) -> Self {
self.plus(rhs)
}
}
impl std::ops::BitOrAssign for Modifiers {
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
impl Modifiers {
pub fn ui(&self, ui: &mut crate::Ui) {
ui.label(ModifierNames::NAMES.format(self, ui.ctx().os().is_mac()));
}
}
#[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,
pub mac_alt: &'a str,
pub concat: &'a str,
}
impl ModifierNames<'static> {
pub const SYMBOLS: Self = Self {
is_short: true,
alt: "⌥",
ctrl: "⌃",
shift: "⇧",
mac_cmd: "⌘",
mac_alt: "⌥",
concat: "",
};
pub const NAMES: Self = Self {
is_short: false,
alt: "Alt",
ctrl: "Ctrl",
shift: "Shift",
mac_cmd: "Cmd",
mac_alt: "Option",
concat: "+",
};
}
impl ModifierNames<'_> {
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.mac_alt);
append_if(modifiers.mac_cmd || modifiers.command, self.mac_cmd);
} else {
append_if(modifiers.ctrl || modifiers.command, self.ctrl);
append_if(modifiers.alt, self.alt);
append_if(modifiers.shift, self.shift);
}
s
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct KeyboardShortcut {
pub modifiers: Modifiers,
pub logical_key: Key,
}
impl KeyboardShortcut {
pub const fn new(modifiers: Modifiers, logical_key: Key) -> Self {
Self {
modifiers,
logical_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.logical_key.symbol_or_name();
} else {
s += self.logical_key.name();
}
s
}
}
#[test]
fn format_kb_shortcut() {
let cmd_shift_f = KeyboardShortcut::new(Modifiers::COMMAND | Modifiers::SHIFT, Key::F);
assert_eq!(
cmd_shift_f.format(&ModifierNames::NAMES, false),
"Ctrl+Shift+F"
);
assert_eq!(
cmd_shift_f.format(&ModifierNames::NAMES, true),
"Shift+Cmd+F"
);
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "⌃⇧F");
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F");
}
impl RawInput {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
viewport_id,
viewports,
screen_rect,
max_texture_side,
time,
predicted_dt,
modifiers,
events,
hovered_files,
dropped_files,
focused,
system_theme,
safe_area_insets: safe_area,
} = self;
ui.label(format!("Active viewport: {viewport_id:?}"));
let ordered_viewports = viewports
.iter()
.map(|(id, value)| (*id, value))
.collect::<OrderedViewportIdMap<_>>();
for (id, viewport) in ordered_viewports {
ui.group(|ui| {
ui.label(format!("Viewport {id:?}"));
ui.push_id(id, |ui| {
viewport.ui(ui);
});
});
}
ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!("max_texture_side: {max_texture_side:?}"));
if let Some(time) = time {
ui.label(format!("time: {time:.3} s"));
} else {
ui.label("time: None");
}
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("modifiers: {modifiers:#?}"));
ui.label(format!("hovered_files: {}", hovered_files.len()));
ui.label(format!("dropped_files: {}", dropped_files.len()));
ui.label(format!("focused: {focused}"));
ui.label(format!("system_theme: {system_theme:?}"));
ui.label(format!("safe_area: {safe_area:?}"));
ui.scope(|ui| {
ui.set_min_height(150.0);
ui.label(format!("events: {events:#?}"))
.on_hover_text("key presses etc");
});
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TouchDeviceId(pub u64);
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TouchId(pub u64);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TouchPhase {
Start,
Move,
End,
Cancel,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum MouseWheelUnit {
Point,
Line,
Page,
}
impl From<u64> for TouchId {
fn from(id: u64) -> Self {
Self(id)
}
}
impl From<i32> for TouchId {
fn from(id: i32) -> Self {
Self(id as u64)
}
}
impl From<u32> for TouchId {
fn from(id: u32) -> Self {
Self(id as u64)
}
}
#[derive(Clone, Copy, Debug)]
pub struct EventFilter {
pub tab: bool,
pub horizontal_arrows: bool,
pub vertical_arrows: bool,
pub escape: bool,
}
#[expect(clippy::derivable_impls)] impl Default for EventFilter {
fn default() -> Self {
Self {
tab: false,
horizontal_arrows: false,
vertical_arrows: false,
escape: false,
}
}
}
impl EventFilter {
pub fn matches(&self, event: &Event) -> bool {
if let Event::Key { key, .. } = event {
match key {
crate::Key::Tab => self.tab,
crate::Key::ArrowUp | crate::Key::ArrowDown => self.vertical_arrows,
crate::Key::ArrowRight | crate::Key::ArrowLeft => self.horizontal_arrows,
crate::Key::Escape => self.escape,
_ => true,
}
} else {
true
}
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SafeAreaInsets(pub MarginF32);
impl std::ops::Sub<SafeAreaInsets> for Rect {
type Output = Self;
fn sub(self, rhs: SafeAreaInsets) -> Self::Output {
self - rhs.0
}
}