init
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
name = "egui-winit"
|
||||
version = "0.34.3"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
build = false
|
||||
include = [
|
||||
"../../LICENSE-APACHE",
|
||||
"../../LICENSE-MIT",
|
||||
"**/*.rs",
|
||||
"Cargo.toml",
|
||||
]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Bindings for using egui with winit"
|
||||
homepage = "https://github.com/emilk/egui/tree/main/crates/egui-winit"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"winit",
|
||||
"egui",
|
||||
"gui",
|
||||
"gamedev",
|
||||
]
|
||||
categories = [
|
||||
"gui",
|
||||
"game-development",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/emilk/egui/tree/main/crates/egui-winit"
|
||||
resolver = "2"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[features]
|
||||
accesskit = ["dep:accesskit_winit"]
|
||||
android-game-activity = ["winit/android-game-activity"]
|
||||
android-native-activity = ["winit/android-native-activity"]
|
||||
bytemuck = [
|
||||
"egui/bytemuck",
|
||||
"dep:bytemuck",
|
||||
]
|
||||
clipboard = [
|
||||
"arboard",
|
||||
"bytemuck",
|
||||
"smithay-clipboard",
|
||||
]
|
||||
default = [
|
||||
"clipboard",
|
||||
"links",
|
||||
"wayland",
|
||||
"winit/default",
|
||||
"x11",
|
||||
]
|
||||
links = ["webbrowser"]
|
||||
serde = [
|
||||
"egui/serde",
|
||||
"dep:serde",
|
||||
]
|
||||
wayland = [
|
||||
"winit/wayland",
|
||||
"bytemuck",
|
||||
]
|
||||
x11 = [
|
||||
"winit/x11",
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "egui_winit"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.accesskit_winit]
|
||||
version = "0.32.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.24.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.document-features]
|
||||
version = "0.2.11"
|
||||
optional = true
|
||||
|
||||
[dependencies.egui]
|
||||
version = "0.34.3"
|
||||
default-features = false
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.28"
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.profiling]
|
||||
version = "1.0.17"
|
||||
default-features = false
|
||||
|
||||
[dependencies.raw-window-handle]
|
||||
version = "0.6.2"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.228"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.web-time]
|
||||
version = "1.1.0"
|
||||
|
||||
[dependencies.webbrowser]
|
||||
version = "1.0.5"
|
||||
optional = true
|
||||
|
||||
[dependencies.winit]
|
||||
version = "0.30.13"
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies.smithay-clipboard]
|
||||
version = "0.7.2"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies.wayland-cursor]
|
||||
version = "0.31.11"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies.arboard]
|
||||
version = "3.6.1"
|
||||
features = ["image-data"]
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.objc2]
|
||||
version = "0.6.4"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
|
||||
version = "0.3.2"
|
||||
features = [
|
||||
"std",
|
||||
"NSThread",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
|
||||
version = "0.3.2"
|
||||
features = [
|
||||
"std",
|
||||
"objc2-core-foundation",
|
||||
"UIApplication",
|
||||
"UIGeometry",
|
||||
"UIResponder",
|
||||
"UIScene",
|
||||
"UISceneDefinitions",
|
||||
"UIView",
|
||||
"UIWindow",
|
||||
"UIWindowScene",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[lints.clippy]
|
||||
allow_attributes = "warn"
|
||||
as_ptr_cast_mut = "warn"
|
||||
assigning_clones = "allow"
|
||||
await_holding_lock = "warn"
|
||||
bool_to_int_with_if = "warn"
|
||||
branches_sharing_code = "warn"
|
||||
cast_possible_wrap = "allow"
|
||||
char_lit_as_u8 = "warn"
|
||||
checked_conversions = "warn"
|
||||
clear_with_drain = "warn"
|
||||
clone_on_ref_ptr = "warn"
|
||||
cloned_instead_of_copied = "warn"
|
||||
coerce_container_to_any = "warn"
|
||||
comparison_chain = "allow"
|
||||
dbg_macro = "warn"
|
||||
debug_assert_with_mut_call = "warn"
|
||||
default_union_representation = "warn"
|
||||
derive_partial_eq_without_eq = "warn"
|
||||
disallowed_macros = "warn"
|
||||
disallowed_methods = "warn"
|
||||
disallowed_names = "warn"
|
||||
disallowed_script_idents = "warn"
|
||||
disallowed_types = "warn"
|
||||
doc_broken_link = "warn"
|
||||
doc_comment_double_space_linebreaks = "warn"
|
||||
doc_include_without_cfg = "warn"
|
||||
doc_link_with_quotes = "warn"
|
||||
doc_markdown = "warn"
|
||||
elidable_lifetime_names = "warn"
|
||||
empty_enum = "warn"
|
||||
empty_enum_variants_with_brackets = "warn"
|
||||
empty_line_after_outer_attr = "warn"
|
||||
enum_glob_use = "warn"
|
||||
equatable_if_let = "warn"
|
||||
exit = "warn"
|
||||
expl_impl_clone_on_copy = "warn"
|
||||
explicit_deref_methods = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
explicit_iter_loop = "warn"
|
||||
fallible_impl_from = "warn"
|
||||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
float_cmp_const = "warn"
|
||||
fn_params_excessive_bools = "warn"
|
||||
fn_to_numeric_cast_any = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
get_unwrap = "warn"
|
||||
if_let_mutex = "warn"
|
||||
ignore_without_reason = "warn"
|
||||
implicit_clone = "warn"
|
||||
implied_bounds_in_impls = "warn"
|
||||
imprecise_flops = "warn"
|
||||
inconsistent_struct_constructor = "warn"
|
||||
index_refutable_slice = "warn"
|
||||
inefficient_to_string = "warn"
|
||||
infinite_loop = "warn"
|
||||
into_iter_without_iter = "warn"
|
||||
invalid_upcast_comparisons = "warn"
|
||||
ip_constant = "warn"
|
||||
iter_filter_is_ok = "warn"
|
||||
iter_filter_is_some = "warn"
|
||||
iter_not_returning_iterator = "warn"
|
||||
iter_on_empty_collections = "warn"
|
||||
iter_on_single_items = "warn"
|
||||
iter_over_hash_type = "warn"
|
||||
iter_without_into_iter = "warn"
|
||||
large_digit_groups = "warn"
|
||||
large_futures = "warn"
|
||||
large_include_file = "warn"
|
||||
large_stack_arrays = "warn"
|
||||
large_stack_frames = "warn"
|
||||
large_types_passed_by_value = "warn"
|
||||
let_underscore_must_use = "allow"
|
||||
let_underscore_untyped = "allow"
|
||||
let_unit_value = "warn"
|
||||
linkedlist = "warn"
|
||||
literal_string_with_formatting_args = "warn"
|
||||
lossy_float_literal = "warn"
|
||||
macro_use_imports = "warn"
|
||||
manual_assert = "warn"
|
||||
manual_clamp = "warn"
|
||||
manual_instant_elapsed = "warn"
|
||||
manual_is_power_of_two = "warn"
|
||||
manual_is_variant_and = "warn"
|
||||
manual_let_else = "warn"
|
||||
manual_midpoint = "warn"
|
||||
manual_ok_or = "warn"
|
||||
manual_range_contains = "allow"
|
||||
manual_string_new = "warn"
|
||||
map_err_ignore = "warn"
|
||||
map_flatten = "warn"
|
||||
map_unwrap_or = "allow"
|
||||
match_bool = "warn"
|
||||
match_same_arms = "warn"
|
||||
match_wild_err_arm = "warn"
|
||||
match_wildcard_for_single_variants = "warn"
|
||||
mem_forget = "warn"
|
||||
mismatching_type_param_order = "warn"
|
||||
missing_assert_message = "warn"
|
||||
missing_enforced_import_renames = "warn"
|
||||
missing_errors_doc = "warn"
|
||||
missing_safety_doc = "warn"
|
||||
mixed_attributes_style = "warn"
|
||||
mut_mut = "warn"
|
||||
mutex_integer = "warn"
|
||||
needless_borrow = "warn"
|
||||
needless_continue = "warn"
|
||||
needless_for_each = "warn"
|
||||
needless_pass_by_ref_mut = "warn"
|
||||
needless_pass_by_value = "warn"
|
||||
negative_feature_names = "warn"
|
||||
non_std_lazy_statics = "warn"
|
||||
non_zero_suggestions = "warn"
|
||||
nonstandard_macro_braces = "warn"
|
||||
option_as_ref_cloned = "warn"
|
||||
option_option = "warn"
|
||||
or_fun_call = "warn"
|
||||
path_buf_push_overwrite = "warn"
|
||||
pathbuf_init_then_push = "warn"
|
||||
precedence_bits = "warn"
|
||||
print_stderr = "warn"
|
||||
print_stdout = "warn"
|
||||
ptr_as_ptr = "warn"
|
||||
ptr_cast_constness = "warn"
|
||||
pub_underscore_fields = "warn"
|
||||
pub_without_shorthand = "warn"
|
||||
rc_mutex = "warn"
|
||||
readonly_write_lock = "warn"
|
||||
redundant_type_annotations = "warn"
|
||||
ref_as_ptr = "warn"
|
||||
ref_option_ref = "warn"
|
||||
ref_patterns = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
return_and_then = "warn"
|
||||
same_functions_in_if_condition = "warn"
|
||||
self_named_module_files = "allow"
|
||||
self_only_used_in_recursion = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
set_contains_or_insert = "warn"
|
||||
should_panic_without_expect = "allow"
|
||||
significant_drop_tightening = "allow"
|
||||
single_char_pattern = "warn"
|
||||
single_match_else = "warn"
|
||||
single_option_map = "warn"
|
||||
str_split_at_newline = "warn"
|
||||
str_to_string = "warn"
|
||||
string_add = "warn"
|
||||
string_add_assign = "warn"
|
||||
string_lit_as_bytes = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
suspicious_command_arg_space = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
todo = "warn"
|
||||
too_long_first_doc_paragraph = "warn"
|
||||
too_many_lines = "allow"
|
||||
trailing_empty_array = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
transmute_ptr_to_ptr = "warn"
|
||||
tuple_array_conversions = "warn"
|
||||
unchecked_time_subtraction = "warn"
|
||||
undocumented_unsafe_blocks = "warn"
|
||||
unimplemented = "warn"
|
||||
uninhabited_references = "warn"
|
||||
uninlined_format_args = "warn"
|
||||
unnecessary_box_returns = "warn"
|
||||
unnecessary_debug_formatting = "warn"
|
||||
unnecessary_literal_bound = "warn"
|
||||
unnecessary_safety_comment = "warn"
|
||||
unnecessary_safety_doc = "warn"
|
||||
unnecessary_self_imports = "warn"
|
||||
unnecessary_semicolon = "warn"
|
||||
unnecessary_struct_initialization = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
unused_async = "warn"
|
||||
unused_peekable = "warn"
|
||||
unused_rounding = "warn"
|
||||
unused_self = "warn"
|
||||
unused_trait_names = "warn"
|
||||
unwrap_used = "warn"
|
||||
use_self = "warn"
|
||||
useless_let_if_seq = "warn"
|
||||
useless_transmute = "warn"
|
||||
verbose_file_reads = "warn"
|
||||
wildcard_dependencies = "warn"
|
||||
wildcard_imports = "allow"
|
||||
zero_sized_map_values = "warn"
|
||||
|
||||
[lints.clippy.all]
|
||||
level = "warn"
|
||||
priority = -1
|
||||
|
||||
[lints.rust]
|
||||
elided_lifetimes_in_paths = "warn"
|
||||
rust_2021_prelude_collisions = "warn"
|
||||
semicolon_in_expressions_from_macros = "warn"
|
||||
trivial_casts = "allow"
|
||||
trivial_numeric_casts = "warn"
|
||||
unexpected_cfgs = "warn"
|
||||
unsafe_code = "deny"
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
unused_extern_crates = "warn"
|
||||
unused_import_braces = "warn"
|
||||
unused_lifetimes = "warn"
|
||||
unused_qualifications = "allow"
|
||||
|
||||
[lints.rust.future_incompatible]
|
||||
level = "warn"
|
||||
priority = -1
|
||||
|
||||
[lints.rust.nonstandard_style]
|
||||
level = "warn"
|
||||
priority = -1
|
||||
|
||||
[lints.rust.rust_2018_idioms]
|
||||
level = "warn"
|
||||
priority = -1
|
||||
|
||||
[lints.rustdoc]
|
||||
all = "warn"
|
||||
broken_intra_doc_links = "warn"
|
||||
missing_crate_level_docs = "warn"
|
||||
@@ -0,0 +1,194 @@
|
||||
use raw_window_handle::RawDisplayHandle;
|
||||
|
||||
/// Handles interfacing with the OS clipboard.
|
||||
///
|
||||
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
|
||||
/// then a fallback clipboard that just works within the same app is used instead.
|
||||
pub struct Clipboard {
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
arboard: Option<arboard::Clipboard>,
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
smithay: Option<smithay_clipboard::Clipboard>,
|
||||
|
||||
/// Fallback manual clipboard.
|
||||
clipboard: String,
|
||||
}
|
||||
|
||||
impl Clipboard {
|
||||
/// Construct a new instance
|
||||
pub fn new(_raw_display_handle: Option<RawDisplayHandle>) -> Self {
|
||||
Self {
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
arboard: init_arboard(),
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
smithay: init_smithay_clipboard(_raw_display_handle),
|
||||
|
||||
clipboard: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self) -> Option<String> {
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.smithay {
|
||||
match clipboard.load() {
|
||||
Ok(text) => return Some(text),
|
||||
Err(err) => {
|
||||
log::error!("smithay paste error: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
return match clipboard.get_text() {
|
||||
Ok(text) => Some(text),
|
||||
Err(err) => {
|
||||
log::error!("arboard paste error: {err}");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some(self.clipboard.clone())
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: String) {
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.smithay {
|
||||
clipboard.store(text);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
if let Err(err) = clipboard.set_text(text) {
|
||||
log::error!("arboard copy/cut error: {err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.clipboard = text;
|
||||
}
|
||||
|
||||
pub fn set_image(&mut self, image: &egui::ColorImage) {
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
if let Err(err) = clipboard.set_image(arboard::ImageData {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
bytes: std::borrow::Cow::Borrowed(bytemuck::cast_slice(&image.pixels)),
|
||||
}) {
|
||||
log::error!("arboard copy/cut error: {err}");
|
||||
}
|
||||
log::debug!("Copied image to clipboard");
|
||||
return;
|
||||
}
|
||||
|
||||
log::error!(
|
||||
"Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it."
|
||||
);
|
||||
_ = image;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "arboard",
|
||||
))]
|
||||
fn init_arboard() -> Option<arboard::Clipboard> {
|
||||
profiling::function_scope!();
|
||||
|
||||
log::trace!("Initializing arboard clipboard…");
|
||||
match arboard::Clipboard::new() {
|
||||
Ok(clipboard) => Some(clipboard),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to initialize arboard clipboard: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
fn init_smithay_clipboard(
|
||||
raw_display_handle: Option<RawDisplayHandle>,
|
||||
) -> Option<smithay_clipboard::Clipboard> {
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
profiling::function_scope!();
|
||||
|
||||
if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle {
|
||||
log::trace!("Initializing smithay clipboard…");
|
||||
#[expect(unsafe_code)]
|
||||
Some(unsafe { smithay_clipboard::Clipboard::new(display.display.as_ptr()) })
|
||||
} else {
|
||||
#[cfg(feature = "wayland")]
|
||||
log::debug!("Cannot init smithay clipboard without a Wayland display handle");
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
log::debug!(
|
||||
"Cannot init smithay clipboard: the 'wayland' feature of 'egui-winit' is not enabled"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
#[cfg(target_os = "ios")]
|
||||
pub use ios::get_safe_area_insets;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios {
|
||||
use egui::{SafeAreaInsets, epaint::MarginF32};
|
||||
use objc2::{ClassType, rc::Retained};
|
||||
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
use objc2_ui_kit::{UIApplication, UISceneActivationState, UIWindowScene};
|
||||
|
||||
/// Gets the ios safe area insets.
|
||||
///
|
||||
/// A safe area defines the area within a view that isn’t covered by a navigation bar, tab bar,
|
||||
/// toolbar, or other views a window might provide. Safe areas are essential for avoiding a
|
||||
/// device’s interactive and display features, like Dynamic Island on iPhone or the camera
|
||||
/// housing on some Mac models.
|
||||
///
|
||||
/// Once winit v0.31 has been released this can be removed in favor of
|
||||
/// `winit::Window::safe_area`.
|
||||
pub fn get_safe_area_insets() -> SafeAreaInsets {
|
||||
let Some(main_thread_marker) = MainThreadMarker::new() else {
|
||||
log::error!("Getting safe area insets needs to be performed on the main thread");
|
||||
return SafeAreaInsets::default();
|
||||
};
|
||||
|
||||
let app = UIApplication::sharedApplication(main_thread_marker);
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
// Look for the first window scene that's in the foreground
|
||||
for scene in app.connectedScenes() {
|
||||
if scene.isKindOfClass(UIWindowScene::class())
|
||||
&& matches!(
|
||||
scene.activationState(),
|
||||
UISceneActivationState::ForegroundActive
|
||||
| UISceneActivationState::ForegroundInactive
|
||||
)
|
||||
{
|
||||
// SAFETY: class kind was checked above with `isKindOfClass`
|
||||
let window_scene = Retained::cast_unchecked::<UIWindowScene>(scene.clone());
|
||||
if let Some(window) = window_scene.keyWindow() {
|
||||
let insets = window.safeAreaInsets();
|
||||
return SafeAreaInsets(MarginF32 {
|
||||
top: insets.top as f32,
|
||||
left: insets.left as f32,
|
||||
right: insets.right as f32,
|
||||
bottom: insets.bottom as f32,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SafeAreaInsets::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
use egui::ViewportBuilder;
|
||||
|
||||
/// Can be used to store native window settings (position and size).
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct WindowSettings {
|
||||
/// Position of window content in physical pixels.
|
||||
inner_position_pixels: Option<egui::Pos2>,
|
||||
|
||||
/// Position of window frame/titlebar in physical pixels.
|
||||
outer_position_pixels: Option<egui::Pos2>,
|
||||
|
||||
fullscreen: bool,
|
||||
|
||||
maximized: bool,
|
||||
|
||||
/// Inner size of window in logical pixels
|
||||
inner_size_points: Option<egui::Vec2>,
|
||||
}
|
||||
|
||||
impl WindowSettings {
|
||||
pub fn from_window(egui_zoom_factor: f32, window: &winit::window::Window) -> Self {
|
||||
let inner_size_points = window
|
||||
.inner_size()
|
||||
.to_logical::<f32>(egui_zoom_factor as f64 * window.scale_factor());
|
||||
|
||||
let inner_position_pixels = window
|
||||
.inner_position()
|
||||
.ok()
|
||||
.map(|p| egui::pos2(p.x as f32, p.y as f32));
|
||||
|
||||
let outer_position_pixels = window
|
||||
.outer_position()
|
||||
.ok()
|
||||
.map(|p| egui::pos2(p.x as f32, p.y as f32));
|
||||
|
||||
Self {
|
||||
inner_position_pixels,
|
||||
outer_position_pixels,
|
||||
|
||||
fullscreen: window.fullscreen().is_some(),
|
||||
maximized: window.is_maximized(),
|
||||
|
||||
inner_size_points: Some(egui::vec2(
|
||||
inner_size_points.width,
|
||||
inner_size_points.height,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_size_points(&self) -> Option<egui::Vec2> {
|
||||
self.inner_size_points
|
||||
}
|
||||
|
||||
pub fn initialize_viewport_builder(
|
||||
&self,
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
mut viewport_builder: ViewportBuilder,
|
||||
) -> ViewportBuilder {
|
||||
profiling::function_scope!();
|
||||
|
||||
// `WindowBuilder::with_position` expects inner position in Macos, and outer position elsewhere
|
||||
// See [`winit::window::WindowBuilder::with_position`] for details.
|
||||
let pos_px = if cfg!(target_os = "macos") {
|
||||
self.inner_position_pixels
|
||||
} else {
|
||||
self.outer_position_pixels
|
||||
};
|
||||
if let Some(pos) = pos_px {
|
||||
let monitor_scale_factor = if let Some(inner_size_points) = self.inner_size_points {
|
||||
find_active_monitor(egui_zoom_factor, event_loop, inner_size_points, &pos)
|
||||
.map_or(1.0, |monitor| monitor.scale_factor() as f32)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let scaled_pos = pos / (egui_zoom_factor * monitor_scale_factor);
|
||||
viewport_builder = viewport_builder.with_position(scaled_pos);
|
||||
}
|
||||
|
||||
if let Some(inner_size_points) = self.inner_size_points {
|
||||
viewport_builder = viewport_builder
|
||||
.with_inner_size(inner_size_points)
|
||||
.with_fullscreen(self.fullscreen)
|
||||
.with_maximized(self.maximized);
|
||||
}
|
||||
|
||||
viewport_builder
|
||||
}
|
||||
|
||||
pub fn initialize_window(&self, window: &winit::window::Window) {
|
||||
if cfg!(target_os = "macos") {
|
||||
// Mac sometimes has problems restoring the window to secondary monitors
|
||||
// using only `WindowBuilder::with_position`, so we need this extra step:
|
||||
if let Some(pos) = self.outer_position_pixels {
|
||||
window.set_outer_position(winit::dpi::PhysicalPosition { x: pos.x, y: pos.y });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_size_to_sane_values(&mut self, largest_monitor_size_points: egui::Vec2) {
|
||||
use egui::NumExt as _;
|
||||
|
||||
if let Some(size) = &mut self.inner_size_points {
|
||||
// Prevent ridiculously small windows:
|
||||
let min_size = egui::Vec2::splat(64.0);
|
||||
*size = size.at_least(min_size);
|
||||
|
||||
// Make sure we don't try to create a window larger than the largest monitor
|
||||
// because on Linux that can lead to a crash.
|
||||
*size = size.at_most(largest_monitor_size_points);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_position_to_monitors(
|
||||
&mut self,
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
) {
|
||||
// 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 becomes invisible to the user 🤦♂️
|
||||
// So on Windows we clamp the position to the monitor it is on.
|
||||
if !cfg!(target_os = "windows") {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(inner_size_points) = self.inner_size_points else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(pos_px) = &mut self.inner_position_pixels {
|
||||
clamp_pos_to_monitors(egui_zoom_factor, event_loop, inner_size_points, pos_px);
|
||||
}
|
||||
if let Some(pos_px) = &mut self.outer_position_pixels {
|
||||
clamp_pos_to_monitors(egui_zoom_factor, event_loop, inner_size_points, pos_px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_active_monitor(
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
window_size_pts: egui::Vec2,
|
||||
position_px: &egui::Pos2,
|
||||
) -> Option<winit::monitor::MonitorHandle> {
|
||||
profiling::function_scope!();
|
||||
let monitors = event_loop.available_monitors();
|
||||
|
||||
// default to primary monitor, in case the correct monitor was disconnected.
|
||||
let Some(mut active_monitor) = event_loop
|
||||
.primary_monitor()
|
||||
.or_else(|| event_loop.available_monitors().next())
|
||||
else {
|
||||
return None; // no monitors 🤷
|
||||
};
|
||||
|
||||
for monitor in monitors {
|
||||
let window_size_px = window_size_pts * (egui_zoom_factor * monitor.scale_factor() as f32);
|
||||
let monitor_x_range = (monitor.position().x - window_size_px.x as i32)
|
||||
..(monitor.position().x + monitor.size().width as i32);
|
||||
let monitor_y_range = (monitor.position().y - window_size_px.y as i32)
|
||||
..(monitor.position().y + monitor.size().height as i32);
|
||||
|
||||
if monitor_x_range.contains(&(position_px.x as i32))
|
||||
&& monitor_y_range.contains(&(position_px.y as i32))
|
||||
{
|
||||
active_monitor = monitor;
|
||||
}
|
||||
}
|
||||
|
||||
Some(active_monitor)
|
||||
}
|
||||
|
||||
fn clamp_pos_to_monitors(
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
window_size_pts: egui::Vec2,
|
||||
position_px: &mut egui::Pos2,
|
||||
) {
|
||||
profiling::function_scope!();
|
||||
|
||||
let Some(active_monitor) =
|
||||
find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px)
|
||||
else {
|
||||
return; // no monitors 🤷
|
||||
};
|
||||
|
||||
let mut window_size_px =
|
||||
window_size_pts * (egui_zoom_factor * active_monitor.scale_factor() as f32);
|
||||
// Add size of title bar. This is 32 px by default in Win 10/11.
|
||||
if cfg!(target_os = "windows") {
|
||||
window_size_px += egui::Vec2::new(
|
||||
0.0,
|
||||
32.0 * egui_zoom_factor * active_monitor.scale_factor() as f32,
|
||||
);
|
||||
}
|
||||
let monitor_position = egui::Pos2::new(
|
||||
active_monitor.position().x as f32,
|
||||
active_monitor.position().y as f32,
|
||||
);
|
||||
let monitor_size_px = egui::Vec2::new(
|
||||
active_monitor.size().width as f32,
|
||||
active_monitor.size().height as f32,
|
||||
);
|
||||
|
||||
// Window size cannot be negative or the subsequent `clamp` will panic.
|
||||
let window_size = (monitor_size_px - window_size_px).max(egui::Vec2::ZERO);
|
||||
// To get the maximum position, we get the rightmost corner of the display, then
|
||||
// subtract the size of the window to get the bottom right most value window.position
|
||||
// can have.
|
||||
*position_px = position_px.clamp(monitor_position, monitor_position + window_size);
|
||||
}
|
||||
Reference in New Issue
Block a user