Split out new crate egui-winit from egui_glium (#735)
This commit is contained in:
parent
ba0e3780a1
commit
1b36863248
27 changed files with 1003 additions and 616 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -132,7 +132,7 @@ jobs:
|
||||||
toolchain: 1.54.0
|
toolchain: 1.54.0
|
||||||
override: true
|
override: true
|
||||||
- run: sudo apt-get install libspeechd-dev
|
- run: sudo apt-get install libspeechd-dev
|
||||||
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui_glium --lib --no-deps --all-features
|
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_glium --lib --no-deps --all-features
|
||||||
|
|
||||||
doc_web:
|
doc_web:
|
||||||
name: cargo doc web
|
name: cargo doc web
|
||||||
|
|
|
@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUT
|
||||||
|
|
||||||
|
|
||||||
## Crate overview
|
## Crate overview
|
||||||
The crates in this repository are: `egui, emath, epaint, egui, epi, egui_web, egui_glium, egui_demo_lib, egui_demo_app`.
|
The crates in this repository are: `egui, emath, epaint, egui, epi, egui-winit, egui_web, egui_glium, egui_demo_lib, egui_demo_app`.
|
||||||
|
|
||||||
### `egui`: The main GUI library.
|
### `egui`: The main GUI library.
|
||||||
Example code: `if ui.button("Click me").clicked() { … }`
|
Example code: `if ui.button("Click me").clicked() { … }`
|
||||||
|
@ -25,6 +25,11 @@ Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_re
|
||||||
Depends only on `egui`.
|
Depends only on `egui`.
|
||||||
Adds a thin application level wrapper around `egui` for hosting an `egui` app inside of `eframe`.
|
Adds a thin application level wrapper around `egui` for hosting an `egui` app inside of `eframe`.
|
||||||
|
|
||||||
|
### `egui-winit`
|
||||||
|
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit).
|
||||||
|
|
||||||
|
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.
|
||||||
|
|
||||||
### `egui_web`
|
### `egui_web`
|
||||||
Puts an egui app inside the web browser by compiling to WASM and binding to the web browser with [`js-sys`](https://crates.io/crates/js-sys) and [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen). Paints the triangles that egui outputs using WebGL.
|
Puts an egui app inside the web browser by compiling to WASM and binding to the web browser with [`js-sys`](https://crates.io/crates/js-sys) and [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen). Paints the triangles that egui outputs using WebGL.
|
||||||
|
|
||||||
|
@ -40,7 +45,6 @@ The demo that you can see at <https://emilk.github.io/egui/index.html> is using
|
||||||
Depends on `egui` + `epi`.
|
Depends on `egui` + `epi`.
|
||||||
This contains a bunch of uses of `egui` and looks like the ui code you would write for an `egui` app.
|
This contains a bunch of uses of `egui` and looks like the ui code you would write for an `egui` app.
|
||||||
|
|
||||||
|
|
||||||
### `egui_demo_app`
|
### `egui_demo_app`
|
||||||
Thin wrapper around `egui_demo_lib` so we can compile it to a web site or a native app executable.
|
Thin wrapper around `egui_demo_lib` so we can compile it to a web site or a native app executable.
|
||||||
Depends on `egui_demo_lib` + `eframe`.
|
Depends on `egui_demo_lib` + `eframe`.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
All notable changes to the egui crate will be documented in this file.
|
All notable changes to the egui crate will be documented in this file.
|
||||||
|
|
||||||
NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [`egui_glium`](egui_glium/CHANGELOG.md) have their own changelogs!
|
NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.md) and [`egui_glium`](egui_glium/CHANGELOG.md) have their own changelogs!
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -799,6 +799,18 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui-winit"
|
||||||
|
version = "0.14.0"
|
||||||
|
dependencies = [
|
||||||
|
"copypasta",
|
||||||
|
"egui",
|
||||||
|
"epi",
|
||||||
|
"tts",
|
||||||
|
"webbrowser",
|
||||||
|
"winit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_demo_app"
|
name = "egui_demo_app"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -826,16 +838,14 @@ name = "egui_glium"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"copypasta",
|
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"egui",
|
"egui",
|
||||||
|
"egui-winit",
|
||||||
"epi",
|
"epi",
|
||||||
"glium",
|
"glium",
|
||||||
"image",
|
"image",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"tts",
|
|
||||||
"webbrowser",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -5,6 +5,7 @@ members = [
|
||||||
"egui_demo_lib",
|
"egui_demo_lib",
|
||||||
"egui_glium",
|
"egui_glium",
|
||||||
"egui_web",
|
"egui_web",
|
||||||
|
"egui-winit",
|
||||||
"egui",
|
"egui",
|
||||||
"emath",
|
"emath",
|
||||||
"epaint",
|
"epaint",
|
||||||
|
|
|
@ -167,6 +167,7 @@ I maintain two official egui integrations made for apps:
|
||||||
|
|
||||||
* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://emilk.github.io/egui/index.html).
|
* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://emilk.github.io/egui/index.html).
|
||||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
|
* [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
|
||||||
|
* [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [`winit`](https://github.com/rust-windowing/winit). `egui-winit` is used by `egui_glium`.
|
||||||
|
|
||||||
If you making an app, consider using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), a framework which allows you to write code that works on both the web (`egui_web`) and native (using `egui_glium`).
|
If you making an app, consider using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), a framework which allows you to write code that works on both the web (`egui_web`) and native (using `egui_glium`).
|
||||||
|
|
||||||
|
@ -180,7 +181,6 @@ If you making an app, consider using [`eframe`](https://github.com/emilk/egui/tr
|
||||||
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
|
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
|
||||||
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||||
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
||||||
* [`egui_winit_platform`](https://github.com/hasenbanck/egui_winit_platform) for [winit](https://crates.io/crates/winit) (requires separate painter).
|
|
||||||
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||||
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
|
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
|
||||||
* [`ggez-egui`](https://github.com/NemuiSen/ggez-egui) for the [ggez](https://ggez.rs/) game framework.
|
* [`ggez-egui`](https://github.com/NemuiSen/ggez-egui) for the [ggez](https://ggez.rs/) game framework.
|
||||||
|
|
|
@ -28,7 +28,7 @@ epi = { version = "0.14.0", path = "../epi" }
|
||||||
|
|
||||||
# For compiling natively:
|
# For compiling natively:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
egui_glium = { version = "0.14.0", path = "../egui_glium", default-features = false }
|
egui_glium = { version = "0.14.0", path = "../egui_glium", default-features = false, features = ["clipboard", "links"] }
|
||||||
|
|
||||||
# For compiling to web:
|
# For compiling to web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
@ -43,6 +43,12 @@ default = ["default_fonts"]
|
||||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
default_fonts = ["egui/default_fonts"]
|
default_fonts = ["egui/default_fonts"]
|
||||||
|
|
||||||
|
# Enable saving app state to disk.
|
||||||
persistence = ["epi/persistence", "egui_glium/persistence", "egui_web/persistence"]
|
persistence = ["epi/persistence", "egui_glium/persistence", "egui_web/persistence"]
|
||||||
screen_reader = ["egui_glium/screen_reader", "egui_web/screen_reader"] # experimental
|
|
||||||
time = ["egui_glium/time"] # for seconds_since_midnight
|
# experimental support for a screen reader
|
||||||
|
screen_reader = ["egui_glium/screen_reader", "egui_web/screen_reader"]
|
||||||
|
|
||||||
|
# for seconds_since_midnight (used in egui_demo_lib)
|
||||||
|
time = ["egui_glium/time"]
|
||||||
|
|
|
@ -62,5 +62,5 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
|
||||||
/// Call from `fn main` like this: `eframe::run_native(Box::new(MyEguiApp::default()))`
|
/// Call from `fn main` like this: `eframe::run_native(Box::new(MyEguiApp::default()))`
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
||||||
egui_glium::run(app, native_options)
|
egui_glium::run(app, &native_options)
|
||||||
}
|
}
|
||||||
|
|
7
egui-winit/CHANGELOG.md
Normal file
7
egui-winit/CHANGELOG.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Changelog for egui-winit
|
||||||
|
|
||||||
|
All notable changes to the `egui-winit` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
First stand-alone release. Previously part of `egui_glium`.
|
45
egui-winit/Cargo.toml
Normal file
45
egui-winit/Cargo.toml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
[package]
|
||||||
|
name = "egui-winit"
|
||||||
|
version = "0.14.0"
|
||||||
|
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
|
description = "Bindings for using egui with winit"
|
||||||
|
edition = "2018"
|
||||||
|
homepage = "https://github.com/emilk/egui/tree/master/egui-winit"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/emilk/egui/tree/master/egui-winit"
|
||||||
|
categories = ["gui", "game-development"]
|
||||||
|
keywords = ["winit", "egui", "gui", "gamedev"]
|
||||||
|
include = [
|
||||||
|
"../LICENSE-APACHE",
|
||||||
|
"../LICENSE-MIT",
|
||||||
|
"**/*.rs",
|
||||||
|
"Cargo.toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = { version = "0.14.0", path = "../egui", default-features = false }
|
||||||
|
epi = { version = "0.14.0", path = "../epi" }
|
||||||
|
winit = "0.25"
|
||||||
|
|
||||||
|
copypasta = { version = "0.7", optional = true }
|
||||||
|
webbrowser = { version = "0.5", optional = true }
|
||||||
|
|
||||||
|
# feature screen_reader
|
||||||
|
tts = { version = "0.17", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["clipboard", "links"]
|
||||||
|
|
||||||
|
# enable cut/copy/paste to OS clipboard.
|
||||||
|
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||||
|
clipboard = ["copypasta"]
|
||||||
|
|
||||||
|
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
|
links = ["webbrowser"]
|
||||||
|
|
||||||
|
# experimental support for a screen reader
|
||||||
|
screen_reader = ["tts"]
|
11
egui-winit/README.md
Normal file
11
egui-winit/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# egui-winit
|
||||||
|
|
||||||
|
[](https://crates.io/crates/egui-winit)
|
||||||
|
[](https://docs.rs/egui-winit)
|
||||||
|
[](https://github.com/rust-secure-code/safety-dance/)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit).
|
||||||
|
|
||||||
|
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.
|
69
egui-winit/src/clipboard.rs
Normal file
69
egui-winit/src/clipboard.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/// Handles interfacing either with the OS clipboard.
|
||||||
|
/// If the "clipboard" feature is off it will instead simulate the clipboard locally.
|
||||||
|
pub struct Clipboard {
|
||||||
|
#[cfg(feature = "copypasta")]
|
||||||
|
copypasta: Option<copypasta::ClipboardContext>,
|
||||||
|
|
||||||
|
/// Fallback manual clipboard.
|
||||||
|
#[cfg(not(feature = "copypasta"))]
|
||||||
|
clipboard: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Clipboard {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(feature = "copypasta")]
|
||||||
|
copypasta: init_copypasta(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "copypasta"))]
|
||||||
|
clipboard: String::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clipboard {
|
||||||
|
pub fn get(&mut self) -> Option<String> {
|
||||||
|
#[cfg(feature = "copypasta")]
|
||||||
|
if let Some(clipboard) = &mut self.copypasta {
|
||||||
|
use copypasta::ClipboardProvider as _;
|
||||||
|
match clipboard.get_contents() {
|
||||||
|
Ok(contents) => Some(contents),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Paste error: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "copypasta"))]
|
||||||
|
Some(self.clipboard.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, text: String) {
|
||||||
|
#[cfg(feature = "copypasta")]
|
||||||
|
if let Some(clipboard) = &mut self.copypasta {
|
||||||
|
use copypasta::ClipboardProvider as _;
|
||||||
|
if let Err(err) = clipboard.set_contents(text) {
|
||||||
|
eprintln!("Copy/Cut error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "copypasta"))]
|
||||||
|
{
|
||||||
|
self.clipboard = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "copypasta")]
|
||||||
|
fn init_copypasta() -> Option<copypasta::ClipboardContext> {
|
||||||
|
match copypasta::ClipboardContext::new() {
|
||||||
|
Ok(clipboard) => Some(clipboard),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to initialize clipboard: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
675
egui-winit/src/lib.rs
Normal file
675
egui-winit/src/lib.rs
Normal file
|
@ -0,0 +1,675 @@
|
||||||
|
//! [`egui`] bindings for [`winit`](https://github.com/rust-windowing/winit).
|
||||||
|
//!
|
||||||
|
//! The library translates winit events to egui, handled copy/paste,
|
||||||
|
//! updates the cursor, open links clicked in egui, etc.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
clippy::all,
|
||||||
|
clippy::await_holding_lock,
|
||||||
|
clippy::char_lit_as_u8,
|
||||||
|
clippy::checked_conversions,
|
||||||
|
clippy::dbg_macro,
|
||||||
|
clippy::debug_assert_with_mut_call,
|
||||||
|
clippy::doc_markdown,
|
||||||
|
clippy::empty_enum,
|
||||||
|
clippy::enum_glob_use,
|
||||||
|
clippy::exit,
|
||||||
|
clippy::expl_impl_clone_on_copy,
|
||||||
|
clippy::explicit_deref_methods,
|
||||||
|
clippy::explicit_into_iter_loop,
|
||||||
|
clippy::fallible_impl_from,
|
||||||
|
clippy::filter_map_next,
|
||||||
|
clippy::float_cmp_const,
|
||||||
|
clippy::fn_params_excessive_bools,
|
||||||
|
clippy::if_let_mutex,
|
||||||
|
clippy::imprecise_flops,
|
||||||
|
clippy::inefficient_to_string,
|
||||||
|
clippy::invalid_upcast_comparisons,
|
||||||
|
clippy::large_types_passed_by_value,
|
||||||
|
clippy::let_unit_value,
|
||||||
|
clippy::linkedlist,
|
||||||
|
clippy::lossy_float_literal,
|
||||||
|
clippy::macro_use_imports,
|
||||||
|
clippy::manual_ok_or,
|
||||||
|
clippy::map_err_ignore,
|
||||||
|
clippy::map_flatten,
|
||||||
|
clippy::match_on_vec_items,
|
||||||
|
clippy::match_same_arms,
|
||||||
|
clippy::match_wildcard_for_single_variants,
|
||||||
|
clippy::mem_forget,
|
||||||
|
clippy::mismatched_target_os,
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::missing_safety_doc,
|
||||||
|
clippy::mut_mut,
|
||||||
|
clippy::mutex_integer,
|
||||||
|
clippy::needless_borrow,
|
||||||
|
clippy::needless_continue,
|
||||||
|
clippy::needless_pass_by_value,
|
||||||
|
clippy::option_option,
|
||||||
|
clippy::path_buf_push_overwrite,
|
||||||
|
clippy::ptr_as_ptr,
|
||||||
|
clippy::ref_option_ref,
|
||||||
|
clippy::rest_pat_in_fully_bound_structs,
|
||||||
|
clippy::same_functions_in_if_condition,
|
||||||
|
clippy::string_add_assign,
|
||||||
|
clippy::string_add,
|
||||||
|
clippy::string_lit_as_bytes,
|
||||||
|
clippy::string_to_string,
|
||||||
|
clippy::todo,
|
||||||
|
clippy::trait_duplication_in_bounds,
|
||||||
|
clippy::unimplemented,
|
||||||
|
clippy::unnested_or_patterns,
|
||||||
|
clippy::unused_self,
|
||||||
|
clippy::useless_transmute,
|
||||||
|
clippy::verbose_file_reads,
|
||||||
|
clippy::zero_sized_map_values,
|
||||||
|
future_incompatible,
|
||||||
|
missing_crate_level_docs,
|
||||||
|
nonstandard_style,
|
||||||
|
rust_2018_idioms
|
||||||
|
)]
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
pub use winit;
|
||||||
|
|
||||||
|
pub mod clipboard;
|
||||||
|
pub mod screen_reader;
|
||||||
|
|
||||||
|
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
||||||
|
window.scale_factor() as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
|
||||||
|
// let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
|
||||||
|
// egui::vec2(width_in_pixels as f32, height_in_pixels as f32)
|
||||||
|
let size = window.inner_size();
|
||||||
|
egui::vec2(size.width as f32, size.height as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the integration between egui and winit.
|
||||||
|
pub struct State {
|
||||||
|
start_time: std::time::Instant,
|
||||||
|
egui_input: egui::RawInput,
|
||||||
|
pointer_pos_in_points: Option<egui::Pos2>,
|
||||||
|
any_pointer_button_down: bool,
|
||||||
|
current_cursor_icon: egui::CursorIcon,
|
||||||
|
/// What egui uses.
|
||||||
|
current_pixels_per_point: f32,
|
||||||
|
|
||||||
|
clipboard: clipboard::Clipboard,
|
||||||
|
screen_reader: screen_reader::ScreenReader,
|
||||||
|
|
||||||
|
/// If `true`, mouse inputs will be treated as touches.
|
||||||
|
/// Useful for debugging touch support in egui.
|
||||||
|
simulate_touch_screen: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Initialize with the native `pixels_per_point` (dpi scaling).
|
||||||
|
pub fn new(window: &winit::window::Window) -> Self {
|
||||||
|
Self::from_pixels_per_point(native_pixels_per_point(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize with a given dpi scaling.
|
||||||
|
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
start_time: std::time::Instant::now(),
|
||||||
|
egui_input: egui::RawInput {
|
||||||
|
pixels_per_point: Some(pixels_per_point),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
pointer_pos_in_points: None,
|
||||||
|
any_pointer_button_down: false,
|
||||||
|
current_cursor_icon: egui::CursorIcon::Default,
|
||||||
|
current_pixels_per_point: pixels_per_point,
|
||||||
|
|
||||||
|
clipboard: Default::default(),
|
||||||
|
screen_reader: screen_reader::ScreenReader::default(),
|
||||||
|
|
||||||
|
simulate_touch_screen: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of physical pixels per logical point,
|
||||||
|
/// as configured on the current egui context (see [`egui::Context::pixels_per_point`]).
|
||||||
|
#[inline]
|
||||||
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
|
self.current_pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current input state.
|
||||||
|
/// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`].
|
||||||
|
#[inline]
|
||||||
|
pub fn egui_input(&self) -> &egui::RawInput {
|
||||||
|
&self.egui_input
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare for a new frame by extracting the accumulated input,
|
||||||
|
/// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect).
|
||||||
|
pub fn take_egui_input(&mut self, display: &winit::window::Window) -> egui::RawInput {
|
||||||
|
let pixels_per_point = self.pixels_per_point();
|
||||||
|
|
||||||
|
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
|
||||||
|
|
||||||
|
// On Windows, a minimized window will have 0 width and height.
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
|
// This solves an issue where egui window positions would be changed when minimizing on Windows.
|
||||||
|
let screen_size_in_pixels = screen_size_in_pixels(display);
|
||||||
|
let screen_size_in_points = screen_size_in_pixels / pixels_per_point;
|
||||||
|
self.egui_input.screen_rect =
|
||||||
|
if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 {
|
||||||
|
Some(egui::Rect::from_min_size(
|
||||||
|
egui::Pos2::ZERO,
|
||||||
|
screen_size_in_points,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
self.egui_input.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this when there is a new event.
|
||||||
|
///
|
||||||
|
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
|
||||||
|
///
|
||||||
|
/// Returns `true` if egui wants exclusive use of this event
|
||||||
|
/// (e.g. a mouse click on an egui window, or entering text into a text field).
|
||||||
|
/// For instance, if you use egui for a game, you want to first call this
|
||||||
|
/// and only when this returns `false` pass on the events to your game.
|
||||||
|
///
|
||||||
|
/// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs.
|
||||||
|
pub fn on_event(
|
||||||
|
&mut self,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
event: &winit::event::WindowEvent<'_>,
|
||||||
|
) -> bool {
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
match event {
|
||||||
|
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||||
|
let pixels_per_point = *scale_factor as f32;
|
||||||
|
self.egui_input.pixels_per_point = Some(pixels_per_point);
|
||||||
|
self.current_pixels_per_point = pixels_per_point;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
|
self.on_mouse_button_input(*state, *button);
|
||||||
|
egui_ctx.wants_pointer_input()
|
||||||
|
}
|
||||||
|
WindowEvent::MouseWheel { delta, .. } => {
|
||||||
|
self.on_mouse_wheel(*delta);
|
||||||
|
egui_ctx.wants_pointer_input()
|
||||||
|
}
|
||||||
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
|
self.on_cursor_moved(*position);
|
||||||
|
egui_ctx.is_using_pointer()
|
||||||
|
}
|
||||||
|
WindowEvent::CursorLeft { .. } => {
|
||||||
|
self.pointer_pos_in_points = None;
|
||||||
|
self.egui_input.events.push(egui::Event::PointerGone);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
// WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO
|
||||||
|
WindowEvent::Touch(touch) => {
|
||||||
|
self.on_touch(touch);
|
||||||
|
match touch.phase {
|
||||||
|
winit::event::TouchPhase::Started
|
||||||
|
| winit::event::TouchPhase::Ended
|
||||||
|
| winit::event::TouchPhase::Cancelled => egui_ctx.wants_pointer_input(),
|
||||||
|
winit::event::TouchPhase::Moved => egui_ctx.is_using_pointer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::ReceivedCharacter(ch) => {
|
||||||
|
if is_printable_char(*ch)
|
||||||
|
&& !self.egui_input.modifiers.ctrl
|
||||||
|
&& !self.egui_input.modifiers.mac_cmd
|
||||||
|
{
|
||||||
|
self.egui_input
|
||||||
|
.events
|
||||||
|
.push(egui::Event::Text(ch.to_string()));
|
||||||
|
egui_ctx.wants_keyboard_input()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { input, .. } => {
|
||||||
|
self.on_keyboard_input(input);
|
||||||
|
egui_ctx.wants_keyboard_input()
|
||||||
|
|| input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab)
|
||||||
|
}
|
||||||
|
WindowEvent::Focused(_) => {
|
||||||
|
// We will not be given a KeyboardInput event when the modifiers are released while
|
||||||
|
// the window does not have focus. Unset all modifier state to be safe.
|
||||||
|
self.egui_input.modifiers = egui::Modifiers::default();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
WindowEvent::HoveredFile(path) => {
|
||||||
|
self.egui_input.hovered_files.push(egui::HoveredFile {
|
||||||
|
path: Some(path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
WindowEvent::HoveredFileCancelled => {
|
||||||
|
self.egui_input.hovered_files.clear();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
WindowEvent::DroppedFile(path) => {
|
||||||
|
self.egui_input.hovered_files.clear();
|
||||||
|
self.egui_input.dropped_files.push(egui::DroppedFile {
|
||||||
|
path: Some(path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// dbg!(event);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_button_input(
|
||||||
|
&mut self,
|
||||||
|
state: winit::event::ElementState,
|
||||||
|
button: winit::event::MouseButton,
|
||||||
|
) {
|
||||||
|
if let Some(pos) = self.pointer_pos_in_points {
|
||||||
|
if let Some(button) = translate_mouse_button(button) {
|
||||||
|
let pressed = state == winit::event::ElementState::Pressed;
|
||||||
|
|
||||||
|
self.egui_input.events.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed,
|
||||||
|
modifiers: self.egui_input.modifiers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.simulate_touch_screen {
|
||||||
|
if pressed {
|
||||||
|
self.any_pointer_button_down = true;
|
||||||
|
|
||||||
|
self.egui_input.events.push(egui::Event::Touch {
|
||||||
|
device_id: egui::TouchDeviceId(0),
|
||||||
|
id: egui::TouchId(0),
|
||||||
|
phase: egui::TouchPhase::Start,
|
||||||
|
pos,
|
||||||
|
force: 0.0,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.any_pointer_button_down = false;
|
||||||
|
|
||||||
|
self.egui_input.events.push(egui::Event::PointerGone);
|
||||||
|
|
||||||
|
self.egui_input.events.push(egui::Event::Touch {
|
||||||
|
device_id: egui::TouchDeviceId(0),
|
||||||
|
id: egui::TouchId(0),
|
||||||
|
phase: egui::TouchPhase::End,
|
||||||
|
pos,
|
||||||
|
force: 0.0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition<f64>) {
|
||||||
|
let pos_in_points = egui::pos2(
|
||||||
|
pos_in_pixels.x as f32 / self.pixels_per_point(),
|
||||||
|
pos_in_pixels.y as f32 / self.pixels_per_point(),
|
||||||
|
);
|
||||||
|
self.pointer_pos_in_points = Some(pos_in_points);
|
||||||
|
|
||||||
|
if self.simulate_touch_screen {
|
||||||
|
if self.any_pointer_button_down {
|
||||||
|
self.egui_input
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos_in_points));
|
||||||
|
|
||||||
|
self.egui_input.events.push(egui::Event::Touch {
|
||||||
|
device_id: egui::TouchDeviceId(0),
|
||||||
|
id: egui::TouchId(0),
|
||||||
|
phase: egui::TouchPhase::Move,
|
||||||
|
pos: pos_in_points,
|
||||||
|
force: 0.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.egui_input
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos_in_points));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_touch(&mut self, touch: &winit::event::Touch) {
|
||||||
|
self.egui_input.events.push(egui::Event::Touch {
|
||||||
|
device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
|
||||||
|
id: egui::TouchId::from(touch.id),
|
||||||
|
phase: match touch.phase {
|
||||||
|
winit::event::TouchPhase::Started => egui::TouchPhase::Start,
|
||||||
|
winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
|
||||||
|
winit::event::TouchPhase::Ended => egui::TouchPhase::End,
|
||||||
|
winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
|
||||||
|
},
|
||||||
|
pos: egui::pos2(
|
||||||
|
touch.location.x as f32 / self.pixels_per_point(),
|
||||||
|
touch.location.y as f32 / self.pixels_per_point(),
|
||||||
|
),
|
||||||
|
force: match touch.force {
|
||||||
|
Some(winit::event::Force::Normalized(force)) => force as f32,
|
||||||
|
Some(winit::event::Force::Calibrated {
|
||||||
|
force,
|
||||||
|
max_possible_force,
|
||||||
|
..
|
||||||
|
}) => (force / max_possible_force) as f32,
|
||||||
|
None => 0_f32,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
|
||||||
|
let mut delta = match delta {
|
||||||
|
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||||
|
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
||||||
|
egui::vec2(x, y) * points_per_scroll_line
|
||||||
|
}
|
||||||
|
winit::event::MouseScrollDelta::PixelDelta(delta) => {
|
||||||
|
egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
// This is still buggy in winit despite
|
||||||
|
// https://github.com/rust-windowing/winit/issues/1695 being closed
|
||||||
|
delta.x *= -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
||||||
|
// Treat as zoom instead:
|
||||||
|
self.egui_input.zoom_delta *= (delta.y / 200.0).exp();
|
||||||
|
} else {
|
||||||
|
self.egui_input.scroll_delta += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) {
|
||||||
|
if let Some(keycode) = input.virtual_keycode {
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
|
||||||
|
let pressed = input.state == winit::event::ElementState::Pressed;
|
||||||
|
|
||||||
|
// We could also use `WindowEvent::ModifiersChanged` instead, I guess.
|
||||||
|
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
|
||||||
|
self.egui_input.modifiers.alt = pressed;
|
||||||
|
}
|
||||||
|
if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) {
|
||||||
|
self.egui_input.modifiers.ctrl = pressed;
|
||||||
|
if !cfg!(target_os = "macos") {
|
||||||
|
self.egui_input.modifiers.command = pressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) {
|
||||||
|
self.egui_input.modifiers.shift = pressed;
|
||||||
|
}
|
||||||
|
if cfg!(target_os = "macos")
|
||||||
|
&& matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin)
|
||||||
|
{
|
||||||
|
self.egui_input.modifiers.mac_cmd = pressed;
|
||||||
|
self.egui_input.modifiers.command = pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
|
||||||
|
// so we detect these things manually:
|
||||||
|
if is_cut_command(self.egui_input.modifiers, keycode) {
|
||||||
|
self.egui_input.events.push(egui::Event::Cut);
|
||||||
|
} else if is_copy_command(self.egui_input.modifiers, keycode) {
|
||||||
|
self.egui_input.events.push(egui::Event::Copy);
|
||||||
|
} else if is_paste_command(self.egui_input.modifiers, keycode) {
|
||||||
|
if let Some(contents) = self.clipboard.get() {
|
||||||
|
self.egui_input.events.push(egui::Event::Text(contents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(key) = translate_virtual_key_code(keycode) {
|
||||||
|
self.egui_input.events.push(egui::Event::Key {
|
||||||
|
key,
|
||||||
|
pressed,
|
||||||
|
modifiers: self.egui_input.modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call with the output given by `egui`.
|
||||||
|
///
|
||||||
|
/// This will, if needed:
|
||||||
|
/// * update the cursor
|
||||||
|
/// * copy text to the clipboard
|
||||||
|
/// * open any clicked urls
|
||||||
|
/// * update the IME
|
||||||
|
/// *
|
||||||
|
pub fn handle_output(
|
||||||
|
&mut self,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
output: egui::Output,
|
||||||
|
) {
|
||||||
|
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||||
|
|
||||||
|
if egui_ctx.memory().options.screen_reader {
|
||||||
|
self.screen_reader.speak(&output.events_description());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_cursor_icon(window, output.cursor_icon);
|
||||||
|
|
||||||
|
if let Some(open) = output.open_url {
|
||||||
|
open_url(&open.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !output.copied_text.is_empty() {
|
||||||
|
self.clipboard.set(output.copied_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(egui::Pos2 { x, y }) = output.text_cursor_pos {
|
||||||
|
window.set_ime_position(winit::dpi::LogicalPosition { x, y })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if Alt-F4 (windows/linux) or Cmd-Q (Mac)
|
||||||
|
pub fn is_quit_shortcut(&self, input: &winit::event::KeyboardInput) -> bool {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
input.state == winit::event::ElementState::Pressed
|
||||||
|
&& self.egui_input.modifiers.mac_cmd
|
||||||
|
&& input.virtual_keycode == Some(winit::event::VirtualKeyCode::Q)
|
||||||
|
} else {
|
||||||
|
input.state == winit::event::ElementState::Pressed
|
||||||
|
&& self.egui_input.modifiers.alt
|
||||||
|
&& input.virtual_keycode == Some(winit::event::VirtualKeyCode::F4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this a close event or a Cmd-Q/Alt-F4 keyboard command.
|
||||||
|
pub fn is_quit_event(&self, event: &winit::event::WindowEvent<'_>) -> bool {
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested | WindowEvent::Destroyed => true,
|
||||||
|
WindowEvent::KeyboardInput { input, .. } => self.is_quit_shortcut(input),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||||
|
// prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing
|
||||||
|
if self.current_cursor_icon == cursor_icon {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.current_cursor_icon = cursor_icon;
|
||||||
|
|
||||||
|
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
|
||||||
|
window.set_cursor_visible(true);
|
||||||
|
|
||||||
|
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
|
||||||
|
if is_pointer_in_window {
|
||||||
|
window.set_cursor_icon(cursor_icon);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_url(_url: &str) {
|
||||||
|
#[cfg(feature = "webbrowser")]
|
||||||
|
if let Err(err) = webbrowser::open(_url) {
|
||||||
|
eprintln!("Failed to open url: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "webbrowser"))]
|
||||||
|
{
|
||||||
|
eprintln!("Cannot open url - feature \"links\" not enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
||||||
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::X)
|
||||||
|
|| (cfg!(target_os = "windows")
|
||||||
|
&& modifiers.shift
|
||||||
|
&& keycode == winit::event::VirtualKeyCode::Delete)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
||||||
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::C)
|
||||||
|
|| (cfg!(target_os = "windows")
|
||||||
|
&& modifiers.ctrl
|
||||||
|
&& keycode == winit::event::VirtualKeyCode::Insert)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
||||||
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::V)
|
||||||
|
|| (cfg!(target_os = "windows")
|
||||||
|
&& modifiers.shift
|
||||||
|
&& keycode == winit::event::VirtualKeyCode::Insert)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
|
||||||
|
match button {
|
||||||
|
winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
|
||||||
|
winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
|
||||||
|
winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
|
||||||
|
winit::event::MouseButton::Other(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<egui::Key> {
|
||||||
|
use egui::Key;
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
|
||||||
|
Some(match key {
|
||||||
|
VirtualKeyCode::Down => Key::ArrowDown,
|
||||||
|
VirtualKeyCode::Left => Key::ArrowLeft,
|
||||||
|
VirtualKeyCode::Right => Key::ArrowRight,
|
||||||
|
VirtualKeyCode::Up => Key::ArrowUp,
|
||||||
|
|
||||||
|
VirtualKeyCode::Escape => Key::Escape,
|
||||||
|
VirtualKeyCode::Tab => Key::Tab,
|
||||||
|
VirtualKeyCode::Back => Key::Backspace,
|
||||||
|
VirtualKeyCode::Return => Key::Enter,
|
||||||
|
VirtualKeyCode::Space => Key::Space,
|
||||||
|
|
||||||
|
VirtualKeyCode::Insert => Key::Insert,
|
||||||
|
VirtualKeyCode::Delete => Key::Delete,
|
||||||
|
VirtualKeyCode::Home => Key::Home,
|
||||||
|
VirtualKeyCode::End => Key::End,
|
||||||
|
VirtualKeyCode::PageUp => Key::PageUp,
|
||||||
|
VirtualKeyCode::PageDown => Key::PageDown,
|
||||||
|
|
||||||
|
VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
|
||||||
|
VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
|
||||||
|
VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
|
||||||
|
VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
|
||||||
|
VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
|
||||||
|
VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
|
||||||
|
VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
|
||||||
|
VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
|
||||||
|
VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
|
||||||
|
VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
|
||||||
|
|
||||||
|
VirtualKeyCode::A => Key::A,
|
||||||
|
VirtualKeyCode::B => Key::B,
|
||||||
|
VirtualKeyCode::C => Key::C,
|
||||||
|
VirtualKeyCode::D => Key::D,
|
||||||
|
VirtualKeyCode::E => Key::E,
|
||||||
|
VirtualKeyCode::F => Key::F,
|
||||||
|
VirtualKeyCode::G => Key::G,
|
||||||
|
VirtualKeyCode::H => Key::H,
|
||||||
|
VirtualKeyCode::I => Key::I,
|
||||||
|
VirtualKeyCode::J => Key::J,
|
||||||
|
VirtualKeyCode::K => Key::K,
|
||||||
|
VirtualKeyCode::L => Key::L,
|
||||||
|
VirtualKeyCode::M => Key::M,
|
||||||
|
VirtualKeyCode::N => Key::N,
|
||||||
|
VirtualKeyCode::O => Key::O,
|
||||||
|
VirtualKeyCode::P => Key::P,
|
||||||
|
VirtualKeyCode::Q => Key::Q,
|
||||||
|
VirtualKeyCode::R => Key::R,
|
||||||
|
VirtualKeyCode::S => Key::S,
|
||||||
|
VirtualKeyCode::T => Key::T,
|
||||||
|
VirtualKeyCode::U => Key::U,
|
||||||
|
VirtualKeyCode::V => Key::V,
|
||||||
|
VirtualKeyCode::W => Key::W,
|
||||||
|
VirtualKeyCode::X => Key::X,
|
||||||
|
VirtualKeyCode::Y => Key::Y,
|
||||||
|
VirtualKeyCode::Z => Key::Z,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
|
||||||
|
match cursor_icon {
|
||||||
|
egui::CursorIcon::None => None,
|
||||||
|
|
||||||
|
egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
|
||||||
|
egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
|
||||||
|
egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
|
||||||
|
egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
|
||||||
|
egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
|
||||||
|
egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
|
||||||
|
egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
|
||||||
|
egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
|
||||||
|
egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
|
||||||
|
egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
|
||||||
|
egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
|
||||||
|
egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
|
||||||
|
egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
|
||||||
|
egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand),
|
||||||
|
egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
|
||||||
|
egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
|
||||||
|
egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
|
||||||
|
egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
|
||||||
|
egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
|
||||||
|
egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
|
||||||
|
egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
|
||||||
|
egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
|
||||||
|
egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
|
||||||
|
egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ impl Default for ScreenReader {
|
||||||
|
|
||||||
impl ScreenReader {
|
impl ScreenReader {
|
||||||
#[cfg(not(feature = "screen_reader"))]
|
#[cfg(not(feature = "screen_reader"))]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
pub fn speak(&mut self, _text: &str) {}
|
pub fn speak(&mut self, _text: &str) {}
|
||||||
|
|
||||||
#[cfg(feature = "screen_reader")]
|
#[cfg(feature = "screen_reader")]
|
|
@ -47,7 +47,8 @@ cint = ["epaint/cint"]
|
||||||
|
|
||||||
persistence = ["serde", "epaint/persistence", "ron"]
|
persistence = ["serde", "epaint/persistence", "ron"]
|
||||||
|
|
||||||
# multi_threaded is only needed if you plan to use the same egui::Context from multiple threads.
|
# multi_threaded is only needed if you plan to use the same egui::Context
|
||||||
|
# from multiple threads. It comes with a minor performance impact.
|
||||||
single_threaded = ["epaint/single_threaded"]
|
single_threaded = ["epaint/single_threaded"]
|
||||||
multi_threaded = ["epaint/multi_threaded"]
|
multi_threaded = ["epaint/multi_threaded"]
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ impl RawInput {
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct HoveredFile {
|
pub struct HoveredFile {
|
||||||
/// Set by the `egui_glium` backend.
|
/// Set by the `egui-winit` backend.
|
||||||
pub path: Option<std::path::PathBuf>,
|
pub path: Option<std::path::PathBuf>,
|
||||||
/// With the `egui_web` backend, this is set to the mime-type of the file (if available).
|
/// With the `egui_web` backend, this is set to the mime-type of the file (if available).
|
||||||
pub mime: String,
|
pub mime: String,
|
||||||
|
@ -144,7 +144,7 @@ pub struct HoveredFile {
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct DroppedFile {
|
pub struct DroppedFile {
|
||||||
/// Set by the `egui_glium` backend.
|
/// Set by the `egui-winit` backend.
|
||||||
pub path: Option<std::path::PathBuf>,
|
pub path: Option<std::path::PathBuf>,
|
||||||
/// Name of the file. Set by the `egui_web` backend.
|
/// Name of the file. Set by the `egui_web` backend.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
@ -8,6 +8,8 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
||||||
* Add `epi::NativeTexture` trait for glium painter
|
* Add `epi::NativeTexture` trait for glium painter
|
||||||
* Deprecate 'Painter::register_glium_texture'
|
* Deprecate 'Painter::register_glium_texture'
|
||||||
* Increase scroll speed.
|
* Increase scroll speed.
|
||||||
|
* Restore window position on startup without flickering.
|
||||||
|
* A lot of the code has been moved to the new library [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit).
|
||||||
|
|
||||||
|
|
||||||
## 0.14.0 - 2021-08-24
|
## 0.14.0 - 2021-08-24
|
||||||
|
|
|
@ -22,31 +22,36 @@ include = [
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
copypasta = "0.7"
|
|
||||||
egui = { version = "0.14.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
egui = { version = "0.14.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||||
|
egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false }
|
||||||
epi = { version = "0.14.0", path = "../epi" }
|
epi = { version = "0.14.0", path = "../epi" }
|
||||||
glium = "0.30"
|
glium = "0.30"
|
||||||
webbrowser = "0.5"
|
|
||||||
|
|
||||||
# feature "persistence":
|
# feature "persistence":
|
||||||
directories-next = { version = "2", optional = true }
|
directories-next = { version = "2", optional = true }
|
||||||
ron = { version = "0.6", optional = true }
|
ron = { version = "0.6", optional = true }
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
|
|
||||||
# feature screen_reader
|
|
||||||
tts = { version = "0.17", optional = true }
|
|
||||||
|
|
||||||
# feature "time"
|
# feature "time"
|
||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default_fonts"]
|
default = ["clipboard", "default_fonts", "links"]
|
||||||
|
|
||||||
|
# enable cut/copy/paste to OS clipboard.
|
||||||
|
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||||
|
clipboard = ["egui-winit/clipboard"]
|
||||||
|
|
||||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
default_fonts = ["egui/default_fonts"]
|
default_fonts = ["egui/default_fonts"]
|
||||||
|
|
||||||
|
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
|
links = ["egui-winit/links"]
|
||||||
|
|
||||||
persistence = [
|
persistence = [
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"egui/persistence",
|
"egui/persistence",
|
||||||
|
@ -54,5 +59,9 @@ persistence = [
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
time = ["chrono"] # for seconds_since_midnight
|
|
||||||
screen_reader = ["tts"] # experimental
|
# experimental support for a screen reader
|
||||||
|
screen_reader = ["egui-winit/screen_reader"]
|
||||||
|
|
||||||
|
# for seconds_since_midnight (used in egui_demo_lib)
|
||||||
|
time = ["chrono"]
|
||||||
|
|
|
@ -13,3 +13,5 @@ To use on Linux, first run:
|
||||||
```
|
```
|
||||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit).
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn window_builder_drag_and_drop(
|
||||||
fn create_display(
|
fn create_display(
|
||||||
app: &dyn epi::App,
|
app: &dyn epi::App,
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
window_settings: Option<WindowSettings>,
|
window_settings: &Option<WindowSettings>,
|
||||||
window_icon: Option<glutin::window::Icon>,
|
window_icon: Option<glutin::window::Icon>,
|
||||||
event_loop: &glutin::event_loop::EventLoop<RequestRepaintEvent>,
|
event_loop: &glutin::event_loop::EventLoop<RequestRepaintEvent>,
|
||||||
) -> glium::Display {
|
) -> glium::Display {
|
||||||
|
@ -95,8 +95,8 @@ fn create_display(
|
||||||
|
|
||||||
let initial_size_points = native_options.initial_window_size;
|
let initial_size_points = native_options.initial_window_size;
|
||||||
|
|
||||||
if let Some(window_settings) = &window_settings {
|
if let Some(window_settings) = window_settings {
|
||||||
window_builder = window_settings.initialize_size(window_builder);
|
window_builder = window_settings.initialize_window(window_builder);
|
||||||
} else if let Some(initial_size_points) = initial_size_points {
|
} else if let Some(initial_size_points) = initial_size_points {
|
||||||
window_builder = window_builder.with_inner_size(glutin::dpi::LogicalSize {
|
window_builder = window_builder.with_inner_size(glutin::dpi::LogicalSize {
|
||||||
width: initial_size_points.x as f64,
|
width: initial_size_points.x as f64,
|
||||||
|
@ -110,20 +110,7 @@ fn create_display(
|
||||||
.with_stencil_buffer(0)
|
.with_stencil_buffer(0)
|
||||||
.with_vsync(true);
|
.with_vsync(true);
|
||||||
|
|
||||||
let display = glium::Display::new(window_builder, context_builder, event_loop).unwrap();
|
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||||
|
|
||||||
if !cfg!(target_os = "windows") {
|
|
||||||
// If the app last ran on two monitors and only one is now connected, then
|
|
||||||
// the given position is invalid.
|
|
||||||
// If this happens on Mac, the window is clamped into valid area.
|
|
||||||
// If this happens on Windows, the window is hidden and impossible to bring to get at.
|
|
||||||
// So we don't restore window positions on Windows.
|
|
||||||
if let Some(window_settings) = &window_settings {
|
|
||||||
window_settings.restore_positions(&display);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "persistence"))]
|
#[cfg(not(feature = "persistence"))]
|
||||||
|
@ -173,14 +160,14 @@ fn load_icon(icon_data: epi::IconData) -> Option<glutin::window::Icon> {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Run an egui app
|
/// Run an egui app
|
||||||
pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
pub fn run(mut app: Box<dyn epi::App>, native_options: &epi::NativeOptions) {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut storage = create_storage(app.name());
|
let mut storage = create_storage(app.name());
|
||||||
|
|
||||||
let window_settings = deserialize_window_settings(&storage);
|
let window_settings = deserialize_window_settings(&storage);
|
||||||
let mut event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let mut event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||||
let icon = native_options.icon_data.clone().and_then(load_icon);
|
let icon = native_options.icon_data.clone().and_then(load_icon);
|
||||||
let display = create_display(&*app, &native_options, window_settings, icon, &event_loop);
|
let display = create_display(&*app, native_options, &window_settings, icon, &event_loop);
|
||||||
|
|
||||||
let repaint_signal = std::sync::Arc::new(GliumRepaintSignal(std::sync::Mutex::new(
|
let repaint_signal = std::sync::Arc::new(GliumRepaintSignal(std::sync::Mutex::new(
|
||||||
event_loop.create_proxy(),
|
event_loop.create_proxy(),
|
||||||
|
@ -260,7 +247,7 @@ pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
||||||
} else {
|
} else {
|
||||||
// Winit uses up all the CPU of one core when returning ControlFlow::Wait.
|
// Winit uses up all the CPU of one core when returning ControlFlow::Wait.
|
||||||
// Sleeping here helps, but still uses 1-3% of CPU :(
|
// Sleeping here helps, but still uses 1-3% of CPU :(
|
||||||
if is_focused || !egui.input_state.raw.hovered_files.is_empty() {
|
if is_focused || !egui.egui_input().hovered_files.is_empty() {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
} else {
|
} else {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
|
|
@ -8,495 +8,87 @@
|
||||||
// Forbid warnings in release builds:
|
// Forbid warnings in release builds:
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![warn(clippy::all, missing_crate_level_docs, rust_2018_idioms)]
|
#![warn(
|
||||||
#![allow(clippy::manual_range_contains, clippy::single_match)]
|
clippy::all,
|
||||||
|
clippy::await_holding_lock,
|
||||||
|
clippy::char_lit_as_u8,
|
||||||
|
clippy::checked_conversions,
|
||||||
|
clippy::dbg_macro,
|
||||||
|
clippy::debug_assert_with_mut_call,
|
||||||
|
clippy::doc_markdown,
|
||||||
|
clippy::empty_enum,
|
||||||
|
clippy::enum_glob_use,
|
||||||
|
clippy::exit,
|
||||||
|
clippy::expl_impl_clone_on_copy,
|
||||||
|
clippy::explicit_deref_methods,
|
||||||
|
clippy::explicit_into_iter_loop,
|
||||||
|
clippy::fallible_impl_from,
|
||||||
|
clippy::filter_map_next,
|
||||||
|
clippy::float_cmp_const,
|
||||||
|
clippy::fn_params_excessive_bools,
|
||||||
|
clippy::if_let_mutex,
|
||||||
|
clippy::imprecise_flops,
|
||||||
|
clippy::inefficient_to_string,
|
||||||
|
clippy::invalid_upcast_comparisons,
|
||||||
|
clippy::large_types_passed_by_value,
|
||||||
|
clippy::let_unit_value,
|
||||||
|
clippy::linkedlist,
|
||||||
|
clippy::lossy_float_literal,
|
||||||
|
clippy::macro_use_imports,
|
||||||
|
clippy::manual_ok_or,
|
||||||
|
clippy::map_err_ignore,
|
||||||
|
clippy::map_flatten,
|
||||||
|
clippy::match_on_vec_items,
|
||||||
|
clippy::match_same_arms,
|
||||||
|
clippy::match_wildcard_for_single_variants,
|
||||||
|
clippy::mem_forget,
|
||||||
|
clippy::mismatched_target_os,
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::missing_safety_doc,
|
||||||
|
clippy::mut_mut,
|
||||||
|
clippy::mutex_integer,
|
||||||
|
clippy::needless_borrow,
|
||||||
|
clippy::needless_continue,
|
||||||
|
clippy::needless_pass_by_value,
|
||||||
|
clippy::option_option,
|
||||||
|
clippy::path_buf_push_overwrite,
|
||||||
|
clippy::ptr_as_ptr,
|
||||||
|
clippy::ref_option_ref,
|
||||||
|
clippy::rest_pat_in_fully_bound_structs,
|
||||||
|
clippy::same_functions_in_if_condition,
|
||||||
|
clippy::string_add_assign,
|
||||||
|
clippy::string_add,
|
||||||
|
clippy::string_lit_as_bytes,
|
||||||
|
clippy::string_to_string,
|
||||||
|
clippy::todo,
|
||||||
|
clippy::trait_duplication_in_bounds,
|
||||||
|
clippy::unimplemented,
|
||||||
|
clippy::unnested_or_patterns,
|
||||||
|
clippy::unused_self,
|
||||||
|
clippy::useless_transmute,
|
||||||
|
clippy::verbose_file_reads,
|
||||||
|
clippy::zero_sized_map_values,
|
||||||
|
future_incompatible,
|
||||||
|
missing_crate_level_docs,
|
||||||
|
nonstandard_style,
|
||||||
|
rust_2018_idioms
|
||||||
|
)]
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
mod painter;
|
mod painter;
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
pub mod screen_reader;
|
|
||||||
pub mod window_settings;
|
pub mod window_settings;
|
||||||
|
|
||||||
pub use backend::*;
|
pub use backend::*;
|
||||||
pub use painter::Painter;
|
pub use painter::Painter;
|
||||||
|
|
||||||
|
pub use egui_winit;
|
||||||
pub use epi::NativeOptions;
|
pub use epi::NativeOptions;
|
||||||
|
|
||||||
use {
|
use glium::glutin;
|
||||||
copypasta::ClipboardProvider,
|
|
||||||
egui::*,
|
|
||||||
glium::glutin::{
|
|
||||||
self,
|
|
||||||
event::{Force, VirtualKeyCode},
|
|
||||||
},
|
|
||||||
std::hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use copypasta::ClipboardContext;
|
|
||||||
|
|
||||||
pub struct GliumInputState {
|
|
||||||
pub pointer_pos_in_points: Option<Pos2>,
|
|
||||||
pub any_pointer_button_down: bool,
|
|
||||||
pub raw: egui::RawInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GliumInputState {
|
|
||||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
pointer_pos_in_points: Default::default(),
|
|
||||||
any_pointer_button_down: false,
|
|
||||||
raw: egui::RawInput {
|
|
||||||
pixels_per_point: Some(pixels_per_point),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper: checks for Alt-F4 (windows/linux) or Cmd-Q (Mac)
|
|
||||||
pub fn is_quit_shortcut(
|
|
||||||
input_state: &GliumInputState,
|
|
||||||
input: &glium::glutin::event::KeyboardInput,
|
|
||||||
) -> bool {
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
input.state == glutin::event::ElementState::Pressed
|
|
||||||
&& input_state.raw.modifiers.mac_cmd
|
|
||||||
&& input.virtual_keycode == Some(VirtualKeyCode::Q)
|
|
||||||
} else {
|
|
||||||
input.state == glutin::event::ElementState::Pressed
|
|
||||||
&& input_state.raw.modifiers.alt
|
|
||||||
&& input.virtual_keycode == Some(VirtualKeyCode::F4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this a close event or a Cmd-Q/Alt-F4 keyboard command?
|
|
||||||
pub fn is_quit_event(
|
|
||||||
input_state: &GliumInputState,
|
|
||||||
event: &glutin::event::WindowEvent<'_>,
|
|
||||||
) -> bool {
|
|
||||||
use glutin::event::WindowEvent;
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested | WindowEvent::Destroyed => true,
|
|
||||||
WindowEvent::KeyboardInput { input, .. } => is_quit_shortcut(input_state, input),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input_to_egui(
|
|
||||||
pixels_per_point: f32,
|
|
||||||
event: &glutin::event::WindowEvent<'_>,
|
|
||||||
clipboard: Option<&mut ClipboardContext>,
|
|
||||||
input_state: &mut GliumInputState,
|
|
||||||
) {
|
|
||||||
// Useful for debugging egui touch support on non-touch devices.
|
|
||||||
let simulate_touches = false;
|
|
||||||
|
|
||||||
use glutin::event::WindowEvent;
|
|
||||||
match event {
|
|
||||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
|
||||||
input_state.raw.pixels_per_point = Some(*scale_factor as f32);
|
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
|
||||||
if let Some(pos) = input_state.pointer_pos_in_points {
|
|
||||||
if let Some(button) = translate_mouse_button(*button) {
|
|
||||||
let pressed = *state == glutin::event::ElementState::Pressed;
|
|
||||||
|
|
||||||
input_state.raw.events.push(egui::Event::PointerButton {
|
|
||||||
pos,
|
|
||||||
button,
|
|
||||||
pressed,
|
|
||||||
modifiers: input_state.raw.modifiers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if simulate_touches {
|
|
||||||
if pressed {
|
|
||||||
input_state.any_pointer_button_down = true;
|
|
||||||
|
|
||||||
input_state.raw.events.push(egui::Event::Touch {
|
|
||||||
device_id: egui::TouchDeviceId(0),
|
|
||||||
id: egui::TouchId(0),
|
|
||||||
phase: egui::TouchPhase::Start,
|
|
||||||
pos,
|
|
||||||
force: 0.0
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
input_state.any_pointer_button_down = false;
|
|
||||||
|
|
||||||
input_state.raw.events.push(egui::Event::PointerGone);
|
|
||||||
|
|
||||||
input_state.raw.events.push(egui::Event::Touch {
|
|
||||||
device_id: egui::TouchDeviceId(0),
|
|
||||||
id: egui::TouchId(0),
|
|
||||||
phase: egui::TouchPhase::End,
|
|
||||||
pos,
|
|
||||||
force: 0.0
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
|
|
||||||
if simulate_touches {
|
|
||||||
if input_state.any_pointer_button_down {
|
|
||||||
input_state
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::PointerMoved(pos_in_points));
|
|
||||||
|
|
||||||
input_state.raw.events.push(egui::Event::Touch {
|
|
||||||
device_id: egui::TouchDeviceId(0),
|
|
||||||
id: egui::TouchId(0),
|
|
||||||
phase: egui::TouchPhase::Move,
|
|
||||||
pos: pos_in_points,
|
|
||||||
force: 0.0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
input_state
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::PointerMoved(pos_in_points));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
input_state.pointer_pos_in_points = None;
|
|
||||||
input_state.raw.events.push(egui::Event::PointerGone);
|
|
||||||
}
|
|
||||||
WindowEvent::ReceivedCharacter(ch) => {
|
|
||||||
if is_printable_char(*ch)
|
|
||||||
&& !input_state.raw.modifiers.ctrl
|
|
||||||
&& !input_state.raw.modifiers.mac_cmd
|
|
||||||
{
|
|
||||||
input_state.raw.events.push(Event::Text(ch.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { input, .. } => {
|
|
||||||
if let Some(keycode) = input.virtual_keycode {
|
|
||||||
let pressed = input.state == glutin::event::ElementState::Pressed;
|
|
||||||
|
|
||||||
// We could also use `WindowEvent::ModifiersChanged` instead, I guess.
|
|
||||||
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
|
|
||||||
input_state.raw.modifiers.alt = pressed;
|
|
||||||
}
|
|
||||||
if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) {
|
|
||||||
input_state.raw.modifiers.ctrl = pressed;
|
|
||||||
if !cfg!(target_os = "macos") {
|
|
||||||
input_state.raw.modifiers.command = pressed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) {
|
|
||||||
input_state.raw.modifiers.shift = pressed;
|
|
||||||
}
|
|
||||||
if cfg!(target_os = "macos")
|
|
||||||
&& matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin)
|
|
||||||
{
|
|
||||||
input_state.raw.modifiers.mac_cmd = pressed;
|
|
||||||
input_state.raw.modifiers.command = pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if pressed {
|
|
||||||
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
|
|
||||||
// so we detect these things manually:
|
|
||||||
if is_cut_command(input_state.raw.modifiers, keycode) {
|
|
||||||
input_state.raw.events.push(Event::Cut);
|
|
||||||
} else if is_copy_command(input_state.raw.modifiers, keycode) {
|
|
||||||
input_state.raw.events.push(Event::Copy);
|
|
||||||
} else if is_paste_command(input_state.raw.modifiers, keycode) {
|
|
||||||
if let Some(clipboard) = clipboard {
|
|
||||||
match clipboard.get_contents() {
|
|
||||||
Ok(contents) => {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::Focused(_) => {
|
|
||||||
// We will not be given a KeyboardInput event when the modifiers are released while
|
|
||||||
// the window does not have focus. Unset all modifier state to be safe.
|
|
||||||
input_state.raw.modifiers = Modifiers::default();
|
|
||||||
}
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
|
||||||
let mut delta = match *delta {
|
|
||||||
glutin::event::MouseScrollDelta::LineDelta(x, y) => {
|
|
||||||
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
|
||||||
vec2(x, y) * points_per_scroll_line
|
|
||||||
}
|
|
||||||
glutin::event::MouseScrollDelta::PixelDelta(delta) => {
|
|
||||||
vec2(delta.x as f32, delta.y as f32) / pixels_per_point
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
// This is still buggy in winit despite
|
|
||||||
// https://github.com/rust-windowing/winit/issues/1695 being closed
|
|
||||||
delta.x *= -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if input_state.raw.modifiers.ctrl || input_state.raw.modifiers.command {
|
|
||||||
// Treat as zoom instead:
|
|
||||||
input_state.raw.zoom_delta *= (delta.y / 200.0).exp();
|
|
||||||
} else {
|
|
||||||
input_state.raw.scroll_delta += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::TouchpadPressure {
|
|
||||||
// device_id,
|
|
||||||
// pressure,
|
|
||||||
// stage,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
WindowEvent::Touch(touch) => {
|
|
||||||
let pixels_per_point_recip = 1. / pixels_per_point;
|
|
||||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
touch.device_id.hash(&mut hasher);
|
|
||||||
input_state.raw.events.push(Event::Touch {
|
|
||||||
device_id: TouchDeviceId(hasher.finish()),
|
|
||||||
id: TouchId::from(touch.id),
|
|
||||||
phase: match touch.phase {
|
|
||||||
glutin::event::TouchPhase::Started => egui::TouchPhase::Start,
|
|
||||||
glutin::event::TouchPhase::Moved => egui::TouchPhase::Move,
|
|
||||||
glutin::event::TouchPhase::Ended => egui::TouchPhase::End,
|
|
||||||
glutin::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
|
|
||||||
},
|
|
||||||
pos: pos2(touch.location.x as f32 * pixels_per_point_recip,
|
|
||||||
touch.location.y as f32 * pixels_per_point_recip),
|
|
||||||
force: match touch.force {
|
|
||||||
Some(Force::Normalized(force)) => force as f32,
|
|
||||||
Some(Force::Calibrated {
|
|
||||||
force,
|
|
||||||
max_possible_force,
|
|
||||||
..
|
|
||||||
}) => (force / max_possible_force) as f32,
|
|
||||||
None => 0_f32,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
WindowEvent::HoveredFile(path) => {
|
|
||||||
input_state.raw.hovered_files.push(egui::HoveredFile {
|
|
||||||
path: Some(path.clone()),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
WindowEvent::HoveredFileCancelled => {
|
|
||||||
input_state.raw.hovered_files.clear();
|
|
||||||
}
|
|
||||||
WindowEvent::DroppedFile(path) => {
|
|
||||||
input_state.raw.hovered_files.clear();
|
|
||||||
input_state.raw.dropped_files.push(egui::DroppedFile {
|
|
||||||
path: Some(path.clone()),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_cut_command(modifiers: egui::Modifiers, keycode: VirtualKeyCode) -> bool {
|
|
||||||
(modifiers.command && keycode == VirtualKeyCode::X)
|
|
||||||
|| (cfg!(target_os = "windows") && modifiers.shift && keycode == VirtualKeyCode::Delete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_copy_command(modifiers: egui::Modifiers, keycode: VirtualKeyCode) -> bool {
|
|
||||||
(modifiers.command && keycode == VirtualKeyCode::C)
|
|
||||||
|| (cfg!(target_os = "windows") && modifiers.ctrl && keycode == VirtualKeyCode::Insert)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_paste_command(modifiers: egui::Modifiers, keycode: VirtualKeyCode) -> bool {
|
|
||||||
(modifiers.command && keycode == VirtualKeyCode::V)
|
|
||||||
|| (cfg!(target_os = "windows") && modifiers.shift && keycode == VirtualKeyCode::Insert)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
Space => Key::Space,
|
|
||||||
|
|
||||||
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,
|
|
||||||
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
U => Key::U,
|
|
||||||
V => Key::V,
|
|
||||||
W => Key::W,
|
|
||||||
X => Key::X,
|
|
||||||
Y => Key::Y,
|
|
||||||
Z => Key::Z,
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<glutin::window::CursorIcon> {
|
|
||||||
match cursor_icon {
|
|
||||||
CursorIcon::None => None,
|
|
||||||
|
|
||||||
CursorIcon::Alias => Some(glutin::window::CursorIcon::Alias),
|
|
||||||
CursorIcon::AllScroll => Some(glutin::window::CursorIcon::AllScroll),
|
|
||||||
CursorIcon::Cell => Some(glutin::window::CursorIcon::Cell),
|
|
||||||
CursorIcon::ContextMenu => Some(glutin::window::CursorIcon::ContextMenu),
|
|
||||||
CursorIcon::Copy => Some(glutin::window::CursorIcon::Copy),
|
|
||||||
CursorIcon::Crosshair => Some(glutin::window::CursorIcon::Crosshair),
|
|
||||||
CursorIcon::Default => Some(glutin::window::CursorIcon::Default),
|
|
||||||
CursorIcon::Grab => Some(glutin::window::CursorIcon::Grab),
|
|
||||||
CursorIcon::Grabbing => Some(glutin::window::CursorIcon::Grabbing),
|
|
||||||
CursorIcon::Help => Some(glutin::window::CursorIcon::Help),
|
|
||||||
CursorIcon::Move => Some(glutin::window::CursorIcon::Move),
|
|
||||||
CursorIcon::NoDrop => Some(glutin::window::CursorIcon::NoDrop),
|
|
||||||
CursorIcon::NotAllowed => Some(glutin::window::CursorIcon::NotAllowed),
|
|
||||||
CursorIcon::PointingHand => Some(glutin::window::CursorIcon::Hand),
|
|
||||||
CursorIcon::Progress => Some(glutin::window::CursorIcon::Progress),
|
|
||||||
CursorIcon::ResizeHorizontal => Some(glutin::window::CursorIcon::EwResize),
|
|
||||||
CursorIcon::ResizeNeSw => Some(glutin::window::CursorIcon::NeswResize),
|
|
||||||
CursorIcon::ResizeNwSe => Some(glutin::window::CursorIcon::NwseResize),
|
|
||||||
CursorIcon::ResizeVertical => Some(glutin::window::CursorIcon::NsResize),
|
|
||||||
CursorIcon::Text => Some(glutin::window::CursorIcon::Text),
|
|
||||||
CursorIcon::VerticalText => Some(glutin::window::CursorIcon::VerticalText),
|
|
||||||
CursorIcon::Wait => Some(glutin::window::CursorIcon::Wait),
|
|
||||||
CursorIcon::ZoomIn => Some(glutin::window::CursorIcon::ZoomIn),
|
|
||||||
CursorIcon::ZoomOut => Some(glutin::window::CursorIcon::ZoomOut),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cursor_icon(display: &glium::backend::glutin::Display, cursor_icon: egui::CursorIcon) {
|
|
||||||
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
|
|
||||||
display.gl_window().window().set_cursor_visible(true);
|
|
||||||
display.gl_window().window().set_cursor_icon(cursor_icon);
|
|
||||||
} else {
|
|
||||||
display.gl_window().window().set_cursor_visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_output(
|
|
||||||
output: egui::Output,
|
|
||||||
clipboard: Option<&mut ClipboardContext>,
|
|
||||||
display: &glium::Display,
|
|
||||||
) {
|
|
||||||
if let Some(open) = output.open_url {
|
|
||||||
if let Err(err) = webbrowser::open(&open.url) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(egui::Pos2 { x, y }) = output.text_cursor_pos {
|
|
||||||
display
|
|
||||||
.gl_window()
|
|
||||||
.window()
|
|
||||||
.set_ime_position(glium::glutin::dpi::LogicalPosition { x, y })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_clipboard() -> Option<ClipboardContext> {
|
|
||||||
match ClipboardContext::new() {
|
|
||||||
Ok(clipboard) => Some(clipboard),
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to initialize clipboard: {}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -514,9 +106,9 @@ pub fn seconds_since_midnight() -> Option<f64> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn screen_size_in_pixels(display: &glium::Display) -> Vec2 {
|
pub fn screen_size_in_pixels(display: &glium::Display) -> egui::Vec2 {
|
||||||
let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
|
let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
|
||||||
vec2(width_in_pixels as f32, height_in_pixels as f32)
|
egui::vec2(width_in_pixels as f32, height_in_pixels as f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn native_pixels_per_point(display: &glium::Display) -> f32 {
|
pub fn native_pixels_per_point(display: &glium::Display) -> f32 {
|
||||||
|
@ -528,26 +120,16 @@ pub fn native_pixels_per_point(display: &glium::Display) -> f32 {
|
||||||
/// Use [`egui`] from a [`glium`] app.
|
/// Use [`egui`] from a [`glium`] app.
|
||||||
pub struct EguiGlium {
|
pub struct EguiGlium {
|
||||||
egui_ctx: egui::CtxRef,
|
egui_ctx: egui::CtxRef,
|
||||||
start_time: std::time::Instant,
|
egui_winit: egui_winit::State,
|
||||||
clipboard: Option<crate::ClipboardContext>,
|
|
||||||
input_state: crate::GliumInputState,
|
|
||||||
painter: crate::Painter,
|
painter: crate::Painter,
|
||||||
current_cursor_icon: egui::CursorIcon,
|
|
||||||
screen_reader: crate::screen_reader::ScreenReader,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EguiGlium {
|
impl EguiGlium {
|
||||||
pub fn new(display: &glium::Display) -> Self {
|
pub fn new(display: &glium::Display) -> Self {
|
||||||
Self {
|
Self {
|
||||||
egui_ctx: Default::default(),
|
egui_ctx: Default::default(),
|
||||||
start_time: std::time::Instant::now(),
|
egui_winit: egui_winit::State::new(display.gl_window().window()),
|
||||||
clipboard: crate::init_clipboard(),
|
|
||||||
input_state: crate::GliumInputState::from_pixels_per_point(
|
|
||||||
crate::native_pixels_per_point(display),
|
|
||||||
),
|
|
||||||
painter: crate::Painter::new(display),
|
painter: crate::Painter::new(display),
|
||||||
current_cursor_icon: egui::CursorIcon::Default,
|
|
||||||
screen_reader: crate::screen_reader::ScreenReader::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,24 +146,26 @@ impl EguiGlium {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pixels_per_point(&self) -> f32 {
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
self.input_state
|
self.egui_winit.pixels_per_point()
|
||||||
.raw
|
|
||||||
.pixels_per_point
|
|
||||||
.unwrap_or_else(|| self.egui_ctx.pixels_per_point())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) {
|
pub fn egui_input(&self) -> &egui::RawInput {
|
||||||
crate::input_to_egui(
|
self.egui_winit.egui_input()
|
||||||
self.egui_ctx.pixels_per_point(),
|
}
|
||||||
event,
|
|
||||||
self.clipboard.as_mut(),
|
/// Returns `true` if egui wants exclusive use of this event
|
||||||
&mut self.input_state,
|
/// (e.g. a mouse click on an egui window, or entering text into a text field).
|
||||||
);
|
/// For instance, if you use egui for a game, you want to first call this
|
||||||
|
/// and only when this returns `false` pass on the events to your game.
|
||||||
|
///
|
||||||
|
/// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs.
|
||||||
|
pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) -> bool {
|
||||||
|
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this a close event or a Cmd-Q/Alt-F4 keyboard command?
|
/// Is this a close event or a Cmd-Q/Alt-F4 keyboard command?
|
||||||
pub fn is_quit_event(&self, event: &glutin::event::WindowEvent<'_>) -> bool {
|
pub fn is_quit_event(&self, event: &glutin::event::WindowEvent<'_>) -> bool {
|
||||||
crate::is_quit_event(&self.input_state, event)
|
self.egui_winit.is_quit_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_frame(&mut self, display: &glium::Display) {
|
pub fn begin_frame(&mut self, display: &glium::Display) {
|
||||||
|
@ -589,30 +173,14 @@ impl EguiGlium {
|
||||||
self.begin_frame_with_input(raw_input);
|
self.begin_frame_with_input(raw_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_frame_with_input(&mut self, raw_input: RawInput) {
|
pub fn begin_frame_with_input(&mut self, raw_input: egui::RawInput) {
|
||||||
self.egui_ctx.begin_frame(raw_input);
|
self.egui_ctx.begin_frame(raw_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare for a new frame. Normally you would call [`Self::begin_frame`] instead.
|
/// Prepare for a new frame. Normally you would call [`Self::begin_frame`] instead.
|
||||||
pub fn take_raw_input(&mut self, display: &glium::Display) -> egui::RawInput {
|
pub fn take_raw_input(&mut self, display: &glium::Display) -> egui::RawInput {
|
||||||
let pixels_per_point = self.pixels_per_point();
|
self.egui_winit
|
||||||
|
.take_egui_input(display.gl_window().window())
|
||||||
self.input_state.raw.time = Some(self.start_time.elapsed().as_secs_f64());
|
|
||||||
|
|
||||||
// On Windows, a minimized window will have 0 width and height.
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
|
||||||
// This solves an issue where egui window positions would be changed when minimizing on Windows.
|
|
||||||
let screen_size = screen_size_in_pixels(display);
|
|
||||||
self.input_state.raw.screen_rect = if screen_size.x > 0.0 && screen_size.y > 0.0 {
|
|
||||||
Some(Rect::from_min_size(
|
|
||||||
Default::default(),
|
|
||||||
screen_size / pixels_per_point,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
self.input_state.raw.take()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `needs_repaint` and shapes to draw.
|
/// Returns `needs_repaint` and shapes to draw.
|
||||||
|
@ -626,19 +194,9 @@ impl EguiGlium {
|
||||||
(needs_repaint, shapes)
|
(needs_repaint, shapes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_output(&mut self, display: &glium::Display, egui_output: egui::Output) {
|
pub fn handle_output(&mut self, display: &glium::Display, output: egui::Output) {
|
||||||
if self.egui_ctx.memory().options.screen_reader {
|
self.egui_winit
|
||||||
self.screen_reader.speak(&egui_output.events_description());
|
.handle_output(display.gl_window().window(), &self.egui_ctx, output);
|
||||||
}
|
|
||||||
|
|
||||||
if self.current_cursor_icon != egui_output.cursor_icon {
|
|
||||||
// call only when changed to prevent flickering near frame boundary
|
|
||||||
// when Windows OS tries to control cursor icon for window resizing
|
|
||||||
set_cursor_icon(display, egui_output.cursor_icon);
|
|
||||||
self.current_cursor_icon = egui_output.cursor_icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_output(egui_output, self.clipboard.as_mut(), display);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint<T: glium::Surface>(
|
pub fn paint<T: glium::Surface>(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![allow(deprecated)] // legacy implement_vertex macro
|
#![allow(deprecated)] // legacy implement_vertex macro
|
||||||
|
#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro
|
||||||
|
|
||||||
use {
|
use {
|
||||||
egui::{
|
egui::{
|
||||||
|
|
|
@ -81,6 +81,9 @@ pub fn read_memory(ctx: &egui::Context, memory_file_path: impl AsRef<std::path::
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alternative to `FileStorage`
|
/// Alternative to `FileStorage`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// When failing to serialize or create the file.
|
||||||
pub fn write_memory(
|
pub fn write_memory(
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
memory_file_path: impl AsRef<std::path::Path>,
|
memory_file_path: impl AsRef<std::path::Path>,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use glium::glutin;
|
use egui_winit::winit;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct WindowSettings {
|
pub struct WindowSettings {
|
||||||
/// outer position of window in physical pixels
|
/// outer position of window in physical pixels
|
||||||
|
@ -37,47 +38,31 @@ impl WindowSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_size(
|
pub fn initialize_window(
|
||||||
&self,
|
&self,
|
||||||
window: glutin::window::WindowBuilder,
|
mut window: winit::window::WindowBuilder,
|
||||||
) -> glutin::window::WindowBuilder {
|
) -> winit::window::WindowBuilder {
|
||||||
|
if !cfg!(target_os = "windows") {
|
||||||
|
// If the app last ran on two monitors and only one is now connected, then
|
||||||
|
// the given position is invalid.
|
||||||
|
// If this happens on Mac, the window is clamped into valid area.
|
||||||
|
// If this happens on Windows, the window is hidden and impossible to bring to get at.
|
||||||
|
// So we don't restore window positions on Windows.
|
||||||
|
if let Some(pos) = self.pos {
|
||||||
|
window = window.with_position(winit::dpi::PhysicalPosition {
|
||||||
|
x: pos.x as f64,
|
||||||
|
y: pos.y as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(inner_size_points) = self.inner_size_points {
|
if let Some(inner_size_points) = self.inner_size_points {
|
||||||
window.with_inner_size(glutin::dpi::LogicalSize {
|
window.with_inner_size(winit::dpi::LogicalSize {
|
||||||
width: inner_size_points.x as f64,
|
width: inner_size_points.x as f64,
|
||||||
height: inner_size_points.y as f64,
|
height: inner_size_points.y as f64,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not yet available in winit: https://github.com/rust-windowing/winit/issues/1190
|
|
||||||
// if let Some(pos) = self.pos {
|
|
||||||
// *window = window.with_outer_pos(glutin::dpi::PhysicalPosition {
|
|
||||||
// x: pos.x as f64,
|
|
||||||
// y: pos.y as f64,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_positions(&self, display: &glium::Display) {
|
|
||||||
// not needed, done by `initialize_size`
|
|
||||||
// let size = self.size.unwrap_or_else(|| vec2(1024.0, 800.0));
|
|
||||||
// display
|
|
||||||
// .gl_window()
|
|
||||||
// .window()
|
|
||||||
// .set_inner_size(glutin::dpi::PhysicalSize {
|
|
||||||
// width: size.x as f64,
|
|
||||||
// height: size.y as f64,
|
|
||||||
// });
|
|
||||||
|
|
||||||
if let Some(pos) = self.pos {
|
|
||||||
display
|
|
||||||
.gl_window()
|
|
||||||
.window()
|
|
||||||
.set_outer_position(glutin::dpi::PhysicalPosition::new(
|
|
||||||
pos.x as f64,
|
|
||||||
pos.y as f64,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ parking_lot = { version = "0.11", optional = true } # Using parking_lot over std
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["multi_threaded", "default_fonts"]
|
default = ["default_fonts", "multi_threaded"]
|
||||||
|
|
||||||
# If set, epaint will use `include_bytes!` to bundle some fonts.
|
# If set, epaint will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
|
@ -55,4 +55,5 @@ persistence = ["serde", "emath/serde"]
|
||||||
single_threaded = ["atomic_refcell"]
|
single_threaded = ["atomic_refcell"]
|
||||||
|
|
||||||
# Only needed if you plan to use the same fonts from multiple threads.
|
# Only needed if you plan to use the same fonts from multiple threads.
|
||||||
|
# It comes with a minor performance impact.
|
||||||
multi_threaded = ["parking_lot"]
|
multi_threaded = ["parking_lot"]
|
||||||
|
|
11
sh/check.sh
11
sh/check.sh
|
@ -15,9 +15,18 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy:
|
||||||
cargo test --workspace --all-targets --all-features
|
cargo test --workspace --all-targets --all-features
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui_glium --lib --no-deps --all-features
|
cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_glium --lib --no-deps --all-features
|
||||||
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
||||||
|
|
||||||
|
(cd emath && cargo check --no-default-features)
|
||||||
|
(cd epaint && cargo check --no-default-features --features "single_threaded")
|
||||||
|
(cd egui && cargo check --no-default-features --features "multi_threaded")
|
||||||
|
(cd eframe && cargo check --no-default-features)
|
||||||
|
(cd epi && cargo check --no-default-features)
|
||||||
|
(cd egui_web && cargo check --no-default-features)
|
||||||
|
(cd egui-winit && cargo check --no-default-features)
|
||||||
|
(cd egui_glium && cargo check --no-default-features)
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,6 @@ script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||||
cd "$script_path/.."
|
cd "$script_path/.."
|
||||||
|
|
||||||
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
||||||
cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui_glium --lib --no-deps --all-features --open
|
cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_glium --lib --no-deps --all-features --open
|
||||||
|
|
||||||
# cargo watch -c -x 'doc -p emath -p epaint -p egui --lib --no-deps --all-features'
|
# cargo watch -c -x 'doc -p emath -p epaint -p egui --lib --no-deps --all-features'
|
||||||
|
|
Loading…
Reference in a new issue