This commit is contained in:
2026-07-09 02:59:53 +03:00
commit 7ace048d94
817 changed files with 234289 additions and 0 deletions
+396
View File
@@ -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"
+194
View File
@@ -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
+56
View File
@@ -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 isnt covered by a navigation bar, tab bar,
/// toolbar, or other views a window might provide. Safe areas are essential for avoiding a
/// devices 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()
}
}
+216
View File
@@ -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);
}