Trying to clena the mess opened by plugins
Some checks failed
Deploy Website / deploy (push) Failing after 4m53s

This commit is contained in:
2026-02-21 01:03:55 +01:00
parent ac0ddc7fb9
commit 75a8fd4401
67 changed files with 1246 additions and 69 deletions

View File

@@ -0,0 +1,24 @@
[package]
name = "egui-baseview"
version = "0.7.0"
authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"]
description = "A baseview backend for egui"
edition = "2021"
license = "MIT"
[features]
default = ["opengl", "default_fonts", "tracing"]
default_fonts = ["egui/default_fonts"]
opengl = ["dep:egui_glow", "baseview/opengl"]
tracing = ["dep:tracing"]
[dependencies]
baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3" }
raw-window-handle = "0.5"
egui = { version = "0.33", default-features = false, features = ["bytemuck"] }
egui_glow = { version = "0.33", features = ["x11"], optional = true }
keyboard-types = { version = "0.6", default-features = false }
copypasta = { version = "0.10", default-features = false, features = ["x11"] }
tracing = { version = "0.1", optional = true }
open = "5.1"
thiserror = "2.0"

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Billy Messenger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,10 @@
mod renderer;
mod translate;
mod window;
pub use window::{EguiWindow, KeyCapture, Queue};
pub use egui;
pub use renderer::GraphicsConfig;
pub use keyboard_types::Key;

View File

@@ -0,0 +1,4 @@
#[cfg(feature = "opengl")]
mod opengl;
#[cfg(feature = "opengl")]
pub use opengl::renderer::{GraphicsConfig, Renderer};

View File

@@ -0,0 +1,12 @@
use egui_glow::PainterError;
use thiserror::Error;
pub mod renderer;
#[derive(Error, Debug)]
pub enum OpenGlError {
#[error("Failed to get baseview's GL context")]
NoContext,
#[error("Error occured when initializing painter: \n {0}")]
CreatePainter(PainterError),
}

View File

@@ -0,0 +1,130 @@
use baseview::{PhySize, Window};
use egui::FullOutput;
use egui_glow::Painter;
use std::sync::Arc;
use super::OpenGlError;
#[derive(Debug, Clone)]
pub struct GraphicsConfig {
/// Controls whether to apply dithering to minimize banding artifacts.
///
/// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
/// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
/// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
///
/// Defaults to true.
pub dithering: bool,
/// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture.
/// See <https://github.com/emilk/egui/pull/1993>.
///
/// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader").
pub shader_version: Option<egui_glow::ShaderVersion>,
}
impl Default for GraphicsConfig {
fn default() -> Self {
Self {
shader_version: None,
dithering: true,
}
}
}
pub struct Renderer {
glow_context: Arc<egui_glow::glow::Context>,
painter: Painter,
}
impl Renderer {
pub fn new(window: &Window, config: GraphicsConfig) -> Result<Self, OpenGlError> {
let context = window.gl_context().ok_or(OpenGlError::NoContext)?;
unsafe {
context.make_current();
}
#[allow(clippy::arc_with_non_send_sync)]
let glow_context = Arc::new(unsafe {
egui_glow::glow::Context::from_loader_function(|s| context.get_proc_address(s))
});
let painter = egui_glow::Painter::new(
Arc::clone(&glow_context),
"",
config.shader_version,
config.dithering,
)
.map_err(OpenGlError::CreatePainter)?;
unsafe {
context.make_not_current();
}
Ok(Self {
glow_context,
painter,
})
}
pub fn max_texture_side(&self) -> usize {
self.painter.max_texture_side()
}
pub fn render(
&mut self,
window: &Window,
bg_color: egui::Rgba,
physical_size: PhySize,
pixels_per_point: f32,
egui_ctx: &mut egui::Context,
full_output: &mut FullOutput,
) {
let PhySize {
width: canvas_width,
height: canvas_height,
} = physical_size;
let shapes = std::mem::take(&mut full_output.shapes);
let textures_delta = &mut full_output.textures_delta;
let context = window
.gl_context()
.expect("failed to get baseview gl context");
unsafe {
context.make_current();
}
unsafe {
use egui_glow::glow::HasContext as _;
self.glow_context
.clear_color(bg_color.r(), bg_color.g(), bg_color.b(), bg_color.a());
self.glow_context.clear(egui_glow::glow::COLOR_BUFFER_BIT);
}
for (id, image_delta) in &textures_delta.set {
self.painter.set_texture(*id, image_delta);
}
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
let dimensions: [u32; 2] = [canvas_width, canvas_height];
self.painter
.paint_primitives(dimensions, pixels_per_point, &clipped_primitives);
for id in textures_delta.free.drain(..) {
self.painter.free_texture(id);
}
unsafe {
context.swap_buffers();
context.make_not_current();
}
}
}
impl Drop for Renderer {
fn drop(&mut self) {
self.painter.destroy()
}
}

View File

@@ -0,0 +1,118 @@
pub(crate) fn translate_mouse_button(button: baseview::MouseButton) -> Option<egui::PointerButton> {
match button {
baseview::MouseButton::Left => Some(egui::PointerButton::Primary),
baseview::MouseButton::Right => Some(egui::PointerButton::Secondary),
baseview::MouseButton::Middle => Some(egui::PointerButton::Middle),
_ => None,
}
}
pub(crate) fn translate_virtual_key(key: &keyboard_types::Key) -> Option<egui::Key> {
use egui::Key;
use keyboard_types::Key as K;
Some(match key {
K::ArrowDown => Key::ArrowDown,
K::ArrowLeft => Key::ArrowLeft,
K::ArrowRight => Key::ArrowRight,
K::ArrowUp => Key::ArrowUp,
K::Escape => Key::Escape,
K::Tab => Key::Tab,
K::Backspace => Key::Backspace,
K::Enter => Key::Enter,
K::Insert => Key::Insert,
K::Delete => Key::Delete,
K::Home => Key::Home,
K::End => Key::End,
K::PageUp => Key::PageUp,
K::PageDown => Key::PageDown,
K::Character(s) => match s.chars().next()? {
' ' => Key::Space,
'0' => Key::Num0,
'1' => Key::Num1,
'2' => Key::Num2,
'3' => Key::Num3,
'4' => Key::Num4,
'5' => Key::Num5,
'6' => Key::Num6,
'7' => Key::Num7,
'8' => Key::Num8,
'9' => 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;
}
},
_ => {
return None;
}
})
}
pub(crate) fn translate_cursor_icon(cursor: egui::CursorIcon) -> baseview::MouseCursor {
match cursor {
egui::CursorIcon::Default => baseview::MouseCursor::Default,
egui::CursorIcon::None => baseview::MouseCursor::Hidden,
egui::CursorIcon::ContextMenu => baseview::MouseCursor::Hand,
egui::CursorIcon::Help => baseview::MouseCursor::Help,
egui::CursorIcon::PointingHand => baseview::MouseCursor::Hand,
egui::CursorIcon::Progress => baseview::MouseCursor::PtrWorking,
egui::CursorIcon::Wait => baseview::MouseCursor::Working,
egui::CursorIcon::Cell => baseview::MouseCursor::Cell,
egui::CursorIcon::Crosshair => baseview::MouseCursor::Crosshair,
egui::CursorIcon::Text => baseview::MouseCursor::Text,
egui::CursorIcon::VerticalText => baseview::MouseCursor::VerticalText,
egui::CursorIcon::Alias => baseview::MouseCursor::Alias,
egui::CursorIcon::Copy => baseview::MouseCursor::Copy,
egui::CursorIcon::Move => baseview::MouseCursor::Move,
egui::CursorIcon::NoDrop => baseview::MouseCursor::NotAllowed,
egui::CursorIcon::NotAllowed => baseview::MouseCursor::NotAllowed,
egui::CursorIcon::Grab => baseview::MouseCursor::Hand,
egui::CursorIcon::Grabbing => baseview::MouseCursor::HandGrabbing,
egui::CursorIcon::AllScroll => baseview::MouseCursor::AllScroll,
egui::CursorIcon::ResizeHorizontal => baseview::MouseCursor::EwResize,
egui::CursorIcon::ResizeNeSw => baseview::MouseCursor::NeswResize,
egui::CursorIcon::ResizeNwSe => baseview::MouseCursor::NwseResize,
egui::CursorIcon::ResizeVertical => baseview::MouseCursor::NsResize,
egui::CursorIcon::ResizeEast => baseview::MouseCursor::EResize,
egui::CursorIcon::ResizeSouthEast => baseview::MouseCursor::SeResize,
egui::CursorIcon::ResizeSouth => baseview::MouseCursor::SResize,
egui::CursorIcon::ResizeSouthWest => baseview::MouseCursor::SwResize,
egui::CursorIcon::ResizeWest => baseview::MouseCursor::WResize,
egui::CursorIcon::ResizeNorthWest => baseview::MouseCursor::NwResize,
egui::CursorIcon::ResizeNorth => baseview::MouseCursor::NResize,
egui::CursorIcon::ResizeNorthEast => baseview::MouseCursor::NeResize,
egui::CursorIcon::ResizeColumn => baseview::MouseCursor::ColResize,
egui::CursorIcon::ResizeRow => baseview::MouseCursor::RowResize,
egui::CursorIcon::ZoomIn => baseview::MouseCursor::ZoomIn,
egui::CursorIcon::ZoomOut => baseview::MouseCursor::ZoomOut,
}
}

View File

@@ -0,0 +1,691 @@
use std::time::Instant;
use baseview::{
Event, EventStatus, PhySize, Window, WindowHandle, WindowHandler, WindowOpenOptions,
WindowScalePolicy,
};
use copypasta::ClipboardProvider;
use egui::{pos2, vec2, Pos2, Rect, Rgba, ViewportCommand};
use keyboard_types::Modifiers;
use raw_window_handle::HasRawWindowHandle;
use crate::{renderer::Renderer, GraphicsConfig};
#[cfg(feature = "tracing")]
use tracing::{error, warn};
pub struct Queue<'a> {
bg_color: &'a mut Rgba,
close_requested: &'a mut bool,
physical_size: &'a mut PhySize,
key_capture: &'a mut KeyCapture,
}
impl<'a> Queue<'a> {
pub(crate) fn new(
bg_color: &'a mut Rgba,
close_requested: &'a mut bool,
physical_size: &'a mut PhySize,
key_capture: &'a mut KeyCapture,
) -> Self {
Self {
bg_color,
//renderer,
//repaint_requested,
close_requested,
physical_size,
key_capture,
}
}
/// Set the background color.
pub fn bg_color(&mut self, bg_color: Rgba) {
*self.bg_color = bg_color;
}
/// Set size of the window.
pub fn resize(&mut self, physical_size: PhySize) {
*self.physical_size = physical_size;
}
/// Close the window.
pub fn close_window(&mut self) {
*self.close_requested = true;
}
/// Set how to handle capturing key events from the host.
pub fn set_key_capture(&mut self, key_capture: KeyCapture) {
*self.key_capture = key_capture;
}
}
struct OpenSettings {
scale_policy: WindowScalePolicy,
logical_width: f64,
logical_height: f64,
title: String,
}
impl OpenSettings {
fn new(settings: &WindowOpenOptions) -> Self {
// WindowScalePolicy does not implement copy/clone.
let scale_policy = match &settings.scale {
WindowScalePolicy::SystemScaleFactor => WindowScalePolicy::SystemScaleFactor,
WindowScalePolicy::ScaleFactor(scale) => WindowScalePolicy::ScaleFactor(*scale),
};
Self {
scale_policy,
logical_width: settings.size.width,
logical_height: settings.size.height,
title: settings.title.clone(),
}
}
}
/// Describes how to handle capturing key events from the host.
#[derive(Default, Debug, Clone, PartialEq)]
pub enum KeyCapture {
#[default]
/// All keys will be captured from the host.
CaptureAll,
/// No keys will be captured from the host.
IgnoreAll,
/// Only the given keys will be captured from the host.
CaptureKeys(Vec<keyboard_types::Key>),
/// All keys except the given ones will be captured from the host.
IgnoreKeys(Vec<keyboard_types::Key>),
}
/// Handles an egui-baseview application
pub struct EguiWindow<State, U>
where
State: 'static + Send,
U: FnMut(&egui::Context, &mut Queue, &mut State),
U: 'static + Send,
{
user_state: Option<State>,
user_update: U,
egui_ctx: egui::Context,
viewport_id: egui::ViewportId,
start_time: Instant,
egui_input: egui::RawInput,
pointer_pos_in_points: Option<egui::Pos2>,
current_cursor_icon: baseview::MouseCursor,
renderer: Renderer,
clipboard_ctx: Option<copypasta::ClipboardContext>,
physical_size: PhySize,
scale_policy: WindowScalePolicy,
pixels_per_point: f32,
points_per_pixel: f32,
bg_color: Rgba,
close_requested: bool,
repaint_after: Option<Instant>,
key_capture: KeyCapture,
}
impl<State, U> EguiWindow<State, U>
where
State: 'static + Send,
U: FnMut(&egui::Context, &mut Queue, &mut State),
U: 'static + Send,
{
fn new<B>(
window: &mut baseview::Window<'_>,
open_settings: OpenSettings,
graphics_config: GraphicsConfig,
mut build: B,
update: U,
mut state: State,
) -> EguiWindow<State, U>
where
B: FnMut(&egui::Context, &mut Queue, &mut State),
B: 'static + Send,
{
let renderer = Renderer::new(window, graphics_config).unwrap_or_else(|err| {
// TODO: better error log and not panicking, but that's gonna require baseview changes
error!("oops! the gpu backend couldn't initialize! \n {err}");
panic!("gpu backend failed to initialize: \n {err}")
});
let egui_ctx = egui::Context::default();
// Assume scale for now until there is an event with a new one.
let pixels_per_point = match open_settings.scale_policy {
WindowScalePolicy::ScaleFactor(scale) => scale,
WindowScalePolicy::SystemScaleFactor => 1.0,
} as f32;
let points_per_pixel = pixels_per_point.recip();
let screen_rect = Rect::from_min_size(
Pos2::new(0f32, 0f32),
vec2(
open_settings.logical_width as f32,
open_settings.logical_height as f32,
),
);
let viewport_info = egui::ViewportInfo {
parent: None,
title: Some(open_settings.title),
native_pixels_per_point: Some(pixels_per_point),
focused: Some(true),
inner_rect: Some(screen_rect),
..Default::default()
};
let viewport_id = egui::ViewportId::default();
let mut egui_input = egui::RawInput {
max_texture_side: Some(renderer.max_texture_side()),
screen_rect: Some(screen_rect),
..Default::default()
};
let _ = egui_input.viewports.insert(viewport_id, viewport_info);
let mut physical_size = PhySize {
width: (open_settings.logical_width * pixels_per_point as f64).round() as u32,
height: (open_settings.logical_height * pixels_per_point as f64).round() as u32,
};
let mut bg_color = Rgba::BLACK;
let mut close_requested = false;
let mut key_capture = KeyCapture::default();
let mut queue = Queue::new(
&mut bg_color,
&mut close_requested,
&mut physical_size,
&mut key_capture,
);
(build)(&egui_ctx, &mut queue, &mut state);
let clipboard_ctx = match copypasta::ClipboardContext::new() {
Ok(clipboard_ctx) => Some(clipboard_ctx),
Err(e) => {
error!("Failed to initialize clipboard: {}", e);
None
}
};
let start_time = Instant::now();
Self {
user_state: Some(state),
user_update: update,
egui_ctx,
viewport_id,
start_time,
egui_input,
pointer_pos_in_points: None,
current_cursor_icon: baseview::MouseCursor::Default,
renderer,
clipboard_ctx,
physical_size,
pixels_per_point,
points_per_pixel,
scale_policy: open_settings.scale_policy,
bg_color,
close_requested,
repaint_after: Some(start_time),
key_capture,
}
}
/// Open a new child window.
///
/// * `parent` - The parent window.
/// * `settings` - The settings of the window.
/// * `state` - The initial state of your application.
/// * `build` - Called once before the first frame. Allows you to do setup code and to
/// call `ctx.set_fonts()`. Optional.
/// * `update` - Called before each frame. Here you should update the state of your
/// application and build the UI.
pub fn open_parented<P, B>(
parent: &P,
#[allow(unused_mut)] mut settings: WindowOpenOptions,
graphics_config: GraphicsConfig,
state: State,
build: B,
update: U,
) -> WindowHandle
where
P: HasRawWindowHandle,
B: FnMut(&egui::Context, &mut Queue, &mut State),
B: 'static + Send,
{
#[cfg(feature = "opengl")]
if settings.gl_config.is_none() {
settings.gl_config = Some(Default::default());
}
let open_settings = OpenSettings::new(&settings);
Window::open_parented(
parent,
settings,
move |window: &mut baseview::Window<'_>| -> EguiWindow<State, U> {
EguiWindow::new(window, open_settings, graphics_config, build, update, state)
},
)
}
/// Open a new window that blocks the current thread until the window is destroyed.
///
/// * `settings` - The settings of the window.
/// * `state` - The initial state of your application.
/// * `build` - Called once before the first frame. Allows you to do setup code and to
/// call `ctx.set_fonts()`. Optional.
/// * `update` - Called before each frame. Here you should update the state of your
/// application and build the UI.
pub fn open_blocking<B>(
#[allow(unused_mut)] mut settings: WindowOpenOptions,
graphics_config: GraphicsConfig,
state: State,
build: B,
update: U,
) where
B: FnMut(&egui::Context, &mut Queue, &mut State),
B: 'static + Send,
{
#[cfg(feature = "opengl")]
if settings.gl_config.is_none() {
settings.gl_config = Some(Default::default());
}
let open_settings = OpenSettings::new(&settings);
Window::open_blocking(
settings,
move |window: &mut baseview::Window<'_>| -> EguiWindow<State, U> {
EguiWindow::new(window, open_settings, graphics_config, build, update, state)
},
)
}
/// Update the pressed key modifiers when a mouse event has sent a new set of modifiers.
fn update_modifiers(&mut self, modifiers: &Modifiers) {
self.egui_input.modifiers.alt = !(*modifiers & Modifiers::ALT).is_empty();
self.egui_input.modifiers.shift = !(*modifiers & Modifiers::SHIFT).is_empty();
self.egui_input.modifiers.command = !(*modifiers & Modifiers::CONTROL).is_empty();
}
}
impl<State, U> WindowHandler for EguiWindow<State, U>
where
State: 'static + Send,
U: FnMut(&egui::Context, &mut Queue, &mut State),
U: 'static + Send,
{
fn on_frame(&mut self, window: &mut Window) {
let Some(state) = &mut self.user_state else {
return;
};
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
self.egui_input.screen_rect = Some(calculate_screen_rect(
self.physical_size,
self.points_per_pixel,
));
self.egui_ctx.begin_pass(self.egui_input.take());
//let mut repaint_requested = false;
let mut queue = Queue::new(
&mut self.bg_color,
&mut self.close_requested,
&mut self.physical_size,
&mut self.key_capture,
);
(self.user_update)(&self.egui_ctx, &mut queue, state);
if self.close_requested {
window.close();
}
// Prevent data from being allocated every frame by storing this
// in a member field.
let mut full_output = self.egui_ctx.end_pass();
let Some(viewport_output) = full_output.viewport_output.get(&self.viewport_id) else {
// The main window was closed by egui.
window.close();
return;
};
for command in viewport_output.commands.iter() {
match command {
ViewportCommand::Close => {
window.close();
}
ViewportCommand::InnerSize(size) => window.resize(baseview::Size {
width: size.x.max(1.0) as f64,
height: size.y.max(1.0) as f64,
}),
_ => {}
}
}
let actual_size = window.physical_size();
if actual_size != self.physical_size {
self.physical_size = actual_size;
}
let now = Instant::now();
let do_repaint_now = if let Some(t) = self.repaint_after {
now >= t || viewport_output.repaint_delay.is_zero()
} else {
viewport_output.repaint_delay.is_zero()
};
if do_repaint_now {
self.renderer.render(
#[cfg(feature = "opengl")]
window,
self.bg_color,
self.physical_size,
self.pixels_per_point,
&mut self.egui_ctx,
&mut full_output,
);
self.repaint_after = None;
} else if let Some(repaint_after) = now.checked_add(viewport_output.repaint_delay) {
// Schedule to repaint after the requested time has elapsed.
self.repaint_after = Some(repaint_after);
}
for command in full_output.platform_output.commands {
match command {
egui::OutputCommand::CopyText(text) => {
if let Some(clipboard_ctx) = &mut self.clipboard_ctx {
if let Err(err) = clipboard_ctx.set_contents(text) {
error!("Copy/Cut error: {}", err);
}
}
}
egui::OutputCommand::CopyImage(_) => {
warn!("Copying images is not supported in egui_baseview.");
}
egui::OutputCommand::OpenUrl(open_url) => {
if let Err(err) = open::that_detached(&open_url.url) {
error!("Open error: {}", err);
}
}
}
}
let cursor_icon =
crate::translate::translate_cursor_icon(full_output.platform_output.cursor_icon);
if self.current_cursor_icon != cursor_icon {
self.current_cursor_icon = cursor_icon;
// TODO: Set mouse cursor for MacOS once baseview supports it.
#[cfg(not(target_os = "macos"))]
window.set_mouse_cursor(cursor_icon);
}
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
let mut return_status = EventStatus::Captured;
match &event {
baseview::Event::Mouse(event) => match event {
baseview::MouseEvent::CursorMoved {
position,
modifiers,
..
} => {
self.update_modifiers(modifiers);
let pos = pos2(position.x as f32, position.y as f32);
self.pointer_pos_in_points = Some(pos);
self.egui_input.events.push(egui::Event::PointerMoved(pos));
}
baseview::MouseEvent::ButtonPressed { button, modifiers } => {
self.update_modifiers(modifiers);
if let Some(pos) = self.pointer_pos_in_points {
if let Some(button) = crate::translate::translate_mouse_button(*button) {
self.egui_input.events.push(egui::Event::PointerButton {
pos,
button,
pressed: true,
modifiers: self.egui_input.modifiers,
});
}
}
}
baseview::MouseEvent::ButtonReleased { button, modifiers } => {
self.update_modifiers(modifiers);
if let Some(pos) = self.pointer_pos_in_points {
if let Some(button) = crate::translate::translate_mouse_button(*button) {
self.egui_input.events.push(egui::Event::PointerButton {
pos,
button,
pressed: false,
modifiers: self.egui_input.modifiers,
});
}
}
}
baseview::MouseEvent::WheelScrolled {
delta: scroll_delta,
modifiers,
} => {
self.update_modifiers(modifiers);
#[allow(unused_mut)]
let (unit, mut delta) = match scroll_delta {
baseview::ScrollDelta::Lines { x, y } => {
(egui::MouseWheelUnit::Line, egui::vec2(*x, *y))
}
baseview::ScrollDelta::Pixels { x, y } => (
egui::MouseWheelUnit::Point,
egui::vec2(*x, *y) * self.points_per_pixel,
),
};
if cfg!(target_os = "macos") {
// This is still buggy in winit despite
// https://github.com/rust-windowing/winit/issues/1695 being closed
//
// TODO: See if this is an issue in baseview as well.
delta.x *= -1.0;
}
self.egui_input.events.push(egui::Event::MouseWheel {
unit,
delta,
modifiers: self.egui_input.modifiers,
});
}
baseview::MouseEvent::CursorLeft => {
self.pointer_pos_in_points = None;
self.egui_input.events.push(egui::Event::PointerGone);
}
_ => {}
},
baseview::Event::Keyboard(event) => {
use keyboard_types::Code;
let pressed = event.state == keyboard_types::KeyState::Down;
match event.code {
Code::ShiftLeft | Code::ShiftRight => self.egui_input.modifiers.shift = pressed,
Code::ControlLeft | Code::ControlRight => {
self.egui_input.modifiers.ctrl = pressed;
#[cfg(not(target_os = "macos"))]
{
self.egui_input.modifiers.command = pressed;
}
}
Code::AltLeft | Code::AltRight => self.egui_input.modifiers.alt = pressed,
Code::MetaLeft | Code::MetaRight => {
#[cfg(target_os = "macos")]
{
self.egui_input.modifiers.mac_cmd = pressed;
self.egui_input.modifiers.command = pressed;
}
// prevent `rustfmt` from breaking this
}
_ => (),
}
if let Some(key) = crate::translate::translate_virtual_key(&event.key) {
self.egui_input.events.push(egui::Event::Key {
key,
physical_key: None,
pressed,
repeat: event.repeat,
modifiers: self.egui_input.modifiers,
});
}
if pressed {
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
// so we detect these things manually:
//
// TODO: See if this is an issue in baseview as well.
if is_cut_command(self.egui_input.modifiers, event.code) {
self.egui_input.events.push(egui::Event::Cut);
} else if is_copy_command(self.egui_input.modifiers, event.code) {
self.egui_input.events.push(egui::Event::Copy);
} else if is_paste_command(self.egui_input.modifiers, event.code) {
if let Some(clipboard_ctx) = &mut self.clipboard_ctx {
match clipboard_ctx.get_contents() {
Ok(contents) => {
self.egui_input.events.push(egui::Event::Text(contents))
}
Err(err) => {
error!("Paste error: {}", err);
}
}
}
} else if let keyboard_types::Key::Character(written) = &event.key {
if !self.egui_input.modifiers.ctrl && !self.egui_input.modifiers.command {
self.egui_input
.events
.push(egui::Event::Text(written.clone()));
}
}
}
match &self.key_capture {
KeyCapture::CaptureAll => {}
KeyCapture::IgnoreAll => return_status = EventStatus::Ignored,
KeyCapture::CaptureKeys(keys) => {
if !keys.contains(&event.key) {
return_status = EventStatus::Ignored
}
}
KeyCapture::IgnoreKeys(keys) => {
if keys.contains(&event.key) {
return_status = EventStatus::Ignored
}
}
}
}
baseview::Event::Window(event) => match event {
baseview::WindowEvent::Resized(window_info) => {
self.pixels_per_point = match self.scale_policy {
WindowScalePolicy::ScaleFactor(scale) => scale,
WindowScalePolicy::SystemScaleFactor => window_info.scale(),
} as f32;
self.points_per_pixel = self.pixels_per_point.recip();
self.physical_size = window_info.physical_size();
let screen_rect =
calculate_screen_rect(self.physical_size, self.points_per_pixel);
self.egui_input.screen_rect = Some(screen_rect);
let viewport_info = self
.egui_input
.viewports
.get_mut(&self.viewport_id)
.unwrap();
viewport_info.native_pixels_per_point = Some(self.pixels_per_point);
viewport_info.inner_rect = Some(screen_rect);
// Schedule to repaint on the next frame.
self.repaint_after = Some(Instant::now());
}
baseview::WindowEvent::Focused => {
self.egui_input
.events
.push(egui::Event::WindowFocused(true));
self.egui_input
.viewports
.get_mut(&self.viewport_id)
.unwrap()
.focused = Some(true);
}
baseview::WindowEvent::Unfocused => {
self.egui_input
.events
.push(egui::Event::WindowFocused(false));
self.egui_input
.viewports
.get_mut(&self.viewport_id)
.unwrap()
.focused = Some(false);
}
baseview::WindowEvent::WillClose => {}
},
}
match &event {
baseview::Event::Keyboard(_) => return_status,
baseview::Event::Mouse(_) => {
if self.egui_ctx.is_using_pointer() || self.egui_ctx.wants_pointer_input() {
EventStatus::Captured
} else {
EventStatus::Ignored
}
}
baseview::Event::Window(_) => EventStatus::Captured,
}
}
}
fn is_cut_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
(modifiers.command && keycode == keyboard_types::Code::KeyX)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == keyboard_types::Code::Delete)
}
fn is_copy_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
(modifiers.command && keycode == keyboard_types::Code::KeyC)
|| (cfg!(target_os = "windows")
&& modifiers.ctrl
&& keycode == keyboard_types::Code::Insert)
}
fn is_paste_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
(modifiers.command && keycode == keyboard_types::Code::KeyV)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == keyboard_types::Code::Insert)
}
/// Calculate screen rectangle in logical size.
fn calculate_screen_rect(physical_size: PhySize, points_per_pixel: f32) -> Rect {
let logical_size = (
physical_size.width as f32 * points_per_pixel,
physical_size.height as f32 * points_per_pixel,
);
Rect::from_min_size(Pos2::new(0f32, 0f32), vec2(logical_size.0, logical_size.1))
}