egui/egui_glium/src/lib.rs

339 lines
12 KiB
Rust
Raw Normal View History

//! [`egui`] bindings for [`glium`](https://github.com/glium/glium).
//!
//! This library is an [`epi`] backend.
//!
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
#![forbid(unsafe_code)]
2020-12-18 21:19:56 +00:00
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
2021-01-02 11:02:26 +00:00
#![warn(clippy::all, rust_2018_idioms)]
2021-01-02 15:31:45 +00:00
#![allow(clippy::manual_range_contains, clippy::single_match)]
2020-07-22 16:01:27 +00:00
2020-07-23 16:54:16 +00:00
mod backend;
#[cfg(feature = "http")]
pub mod http;
2019-04-21 08:13:05 +00:00
mod painter;
#[cfg(feature = "persistence")]
pub mod persistence;
pub mod screen_reader;
pub mod window_settings;
2019-04-21 08:13:05 +00:00
2020-07-23 16:54:16 +00:00
pub use backend::*;
2019-04-21 08:13:05 +00:00
pub use painter::Painter;
use {
clipboard::ClipboardProvider,
egui::*,
glium::glutin::{self, event::VirtualKeyCode, event_loop::ControlFlow},
};
pub use clipboard::ClipboardContext; // TODO: remove
2020-11-13 10:04:45 +00:00
pub struct GliumInputState {
pub pointer_pos_in_points: Option<Pos2>,
pub raw: egui::RawInput,
2020-11-13 10:04:45 +00:00
}
impl GliumInputState {
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
Self {
pointer_pos_in_points: Default::default(),
2020-11-13 10:04:45 +00:00
raw: egui::RawInput {
pixels_per_point: Some(pixels_per_point),
..Default::default()
},
}
}
}
pub fn input_to_egui(
pixels_per_point: f32,
2021-01-02 11:02:26 +00:00
event: glutin::event::WindowEvent<'_>,
clipboard: Option<&mut ClipboardContext>,
2020-11-13 10:04:45 +00:00
input_state: &mut GliumInputState,
control_flow: &mut ControlFlow,
) {
2021-01-17 01:31:37 +00:00
use glutin::event::WindowEvent;
match event {
2021-01-17 01:31:37 +00:00
WindowEvent::CloseRequested | WindowEvent::Destroyed => *control_flow = ControlFlow::Exit,
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
input_state.raw.pixels_per_point = Some(scale_factor as f32);
}
WindowEvent::MouseInput { state, button, .. } => {
if let Some(pos_in_points) = input_state.pointer_pos_in_points {
if let Some(button) = translate_mouse_button(button) {
input_state.raw.events.push(egui::Event::PointerButton {
pos: pos_in_points,
button,
pressed: state == glutin::event::ElementState::Pressed,
modifiers: input_state.raw.modifiers,
});
}
}
}
2021-01-17 01:31:37 +00:00
WindowEvent::CursorMoved {
position: pos_in_pixels,
..
} => {
let pos_in_points = pos2(
pos_in_pixels.x as f32 / pixels_per_point,
pos_in_pixels.y as f32 / pixels_per_point,
);
input_state.pointer_pos_in_points = Some(pos_in_points);
input_state
.raw
.events
.push(egui::Event::PointerMoved(pos_in_points));
}
2021-01-17 01:31:37 +00:00
WindowEvent::CursorLeft { .. } => {
input_state.pointer_pos_in_points = None;
input_state.raw.events.push(egui::Event::PointerGone);
}
2021-01-17 01:31:37 +00:00
WindowEvent::ReceivedCharacter(ch) => {
if is_printable_char(ch)
2020-11-15 13:21:21 +00:00
&& !input_state.raw.modifiers.ctrl
&& !input_state.raw.modifiers.mac_cmd
{
2020-11-13 10:04:45 +00:00
input_state.raw.events.push(Event::Text(ch.to_string()));
}
}
2021-01-17 01:31:37 +00:00
WindowEvent::KeyboardInput { input, .. } => {
if let Some(keycode) = input.virtual_keycode {
let pressed = input.state == glutin::event::ElementState::Pressed;
2020-11-13 10:04:45 +00:00
// We could also use `WindowEvent::ModifiersChanged` instead, I guess.
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
2020-11-15 13:21:21 +00:00
input_state.raw.modifiers.alt = pressed;
}
if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) {
2020-11-15 13:21:21 +00:00
input_state.raw.modifiers.ctrl = pressed;
if !cfg!(target_os = "macos") {
2020-11-15 13:21:21 +00:00
input_state.raw.modifiers.command = pressed;
}
}
if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) {
2020-11-15 13:21:21 +00:00
input_state.raw.modifiers.shift = pressed;
}
if cfg!(target_os = "macos")
&& matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin)
{
2020-11-15 13:21:21 +00:00
input_state.raw.modifiers.mac_cmd = pressed;
input_state.raw.modifiers.command = pressed;
}
if pressed {
2020-11-13 10:04:45 +00:00
if cfg!(target_os = "macos")
2020-11-15 13:21:21 +00:00
&& input_state.raw.modifiers.mac_cmd
&& keycode == VirtualKeyCode::Q
2020-11-13 10:04:45 +00:00
{
*control_flow = ControlFlow::Exit;
}
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
// so we detect these things manually:
2020-11-15 13:21:21 +00:00
if input_state.raw.modifiers.command && keycode == VirtualKeyCode::X {
2020-11-13 10:04:45 +00:00
input_state.raw.events.push(Event::Cut);
2020-11-15 13:21:21 +00:00
} else if input_state.raw.modifiers.command && keycode == VirtualKeyCode::C {
2020-11-13 10:04:45 +00:00
input_state.raw.events.push(Event::Copy);
2020-11-15 13:21:21 +00:00
} else if input_state.raw.modifiers.command && keycode == VirtualKeyCode::V {
if let Some(clipboard) = clipboard {
match clipboard.get_contents() {
Ok(contents) => {
2020-11-13 10:04:45 +00:00
input_state.raw.events.push(Event::Text(contents));
}
Err(err) => {
eprintln!("Paste error: {}", err);
}
}
}
}
}
if let Some(key) = translate_virtual_key_code(keycode) {
input_state.raw.events.push(Event::Key {
key,
pressed,
modifiers: input_state.raw.modifiers,
});
}
}
}
2021-01-17 01:31:37 +00:00
WindowEvent::MouseWheel { delta, .. } => {
match delta {
glutin::event::MouseScrollDelta::LineDelta(x, y) => {
let line_height = 24.0; // TODO
2020-11-13 10:04:45 +00:00
input_state.raw.scroll_delta = vec2(x, y) * line_height;
}
glutin::event::MouseScrollDelta::PixelDelta(delta) => {
// Actually point delta
2020-11-13 10:04:45 +00:00
input_state.raw.scroll_delta = vec2(delta.x as f32, delta.y as f32);
}
}
}
_ => {
// dbg!(event);
}
}
}
/// Glium sends special keys (backspace, delete, F1, ...) as characters.
/// Ignore those.
/// We also ignore '\r', '\n', '\t'.
/// Newlines are handled by the `Key::Enter` event.
fn is_printable_char(chr: char) -> bool {
let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
|| '\u{f0000}' <= chr && chr <= '\u{ffffd}'
|| '\u{100000}' <= chr && chr <= '\u{10fffd}';
!is_in_private_use_area && !chr.is_ascii_control()
}
pub fn translate_mouse_button(button: glutin::event::MouseButton) -> Option<egui::PointerButton> {
match button {
glutin::event::MouseButton::Left => Some(egui::PointerButton::Primary),
glutin::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
glutin::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
_ => None,
}
}
pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
use VirtualKeyCode::*;
Some(match key {
Down => Key::ArrowDown,
Left => Key::ArrowLeft,
Right => Key::ArrowRight,
Up => Key::ArrowUp,
Escape => Key::Escape,
Tab => Key::Tab,
Back => Key::Backspace,
Return => Key::Enter,
2020-12-13 09:00:20 +00:00
Space => Key::Space,
2020-11-15 13:21:21 +00:00
Insert => Key::Insert,
Delete => Key::Delete,
Home => Key::Home,
End => Key::End,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Key0 | Numpad0 => Key::Num0,
Key1 | Numpad1 => Key::Num1,
Key2 | Numpad2 => Key::Num2,
Key3 | Numpad3 => Key::Num3,
Key4 | Numpad4 => Key::Num4,
Key5 | Numpad5 => Key::Num5,
Key6 | Numpad6 => Key::Num6,
Key7 | Numpad7 => Key::Num7,
Key8 | Numpad8 => Key::Num8,
Key9 | Numpad9 => Key::Num9,
2020-11-15 13:21:21 +00:00
A => Key::A,
B => Key::B,
C => Key::C,
D => Key::D,
E => Key::E,
F => Key::F,
G => Key::G,
H => Key::H,
I => Key::I,
J => Key::J,
2020-11-15 13:21:21 +00:00
K => Key::K,
L => Key::L,
M => Key::M,
N => Key::N,
O => Key::O,
P => Key::P,
Q => Key::Q,
R => Key::R,
S => Key::S,
T => Key::T,
2020-11-15 13:21:21 +00:00
U => Key::U,
V => Key::V,
2020-11-15 13:21:21 +00:00
W => Key::W,
X => Key::X,
Y => Key::Y,
2020-11-15 13:21:21 +00:00
Z => Key::Z,
_ => {
return None;
}
})
}
pub fn translate_cursor(cursor_icon: egui::CursorIcon) -> glutin::window::CursorIcon {
match cursor_icon {
CursorIcon::Default => glutin::window::CursorIcon::Default,
CursorIcon::PointingHand => glutin::window::CursorIcon::Hand,
CursorIcon::ResizeHorizontal => glutin::window::CursorIcon::EwResize,
CursorIcon::ResizeNeSw => glutin::window::CursorIcon::NeswResize,
CursorIcon::ResizeNwSe => glutin::window::CursorIcon::NwseResize,
CursorIcon::ResizeVertical => glutin::window::CursorIcon::NsResize,
CursorIcon::Text => glutin::window::CursorIcon::Text,
CursorIcon::Grab => glutin::window::CursorIcon::Grab,
CursorIcon::Grabbing => glutin::window::CursorIcon::Grabbing,
}
}
pub fn handle_output(
output: egui::Output,
display: &glium::backend::glutin::Display,
clipboard: Option<&mut ClipboardContext>,
) {
if let Some(open) = output.open_url {
if let Err(err) = webbrowser::open(&open.url) {
2020-10-01 20:40:49 +00:00
eprintln!("Failed to open url: {}", err);
}
}
if !output.copied_text.is_empty() {
if let Some(clipboard) = clipboard {
if let Err(err) = clipboard.set_contents(output.copied_text) {
eprintln!("Copy/Cut error: {}", err);
}
}
}
display
.gl_window()
.window()
.set_cursor_icon(translate_cursor(output.cursor_icon));
}
pub fn init_clipboard() -> Option<ClipboardContext> {
match ClipboardContext::new() {
Ok(clipboard) => Some(clipboard),
Err(err) => {
eprintln!("Failed to initialize clipboard: {}", err);
None
}
}
}
// ----------------------------------------------------------------------------
/// Time of day as seconds since midnight. Used for clock in demo app.
pub fn seconds_since_midnight() -> Option<f64> {
#[cfg(feature = "time")]
{
use chrono::Timelike;
let time = chrono::Local::now().time();
let seconds_since_midnight =
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64);
Some(seconds_since_midnight)
}
#[cfg(not(feature = "time"))]
None
}
pub fn screen_size_in_pixels(display: &glium::Display) -> Vec2 {
let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
vec2(width_in_pixels as f32, height_in_pixels as f32)
}
pub fn native_pixels_per_point(display: &glium::Display) -> f32 {
display.gl_window().window().scale_factor() as f32
}