feat: keyboard input and input region
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
debug
|
debug
|
||||||
target
|
target
|
||||||
|
logs.txt
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|||||||
@@ -1,18 +1,192 @@
|
|||||||
use bevy::{prelude::*, winit::WinitPlugin};
|
use std::time::Duration;
|
||||||
use bevy_wayland::{layer_shell::LayerShellWindow, WaylandPlugin};
|
|
||||||
|
use bevy::{
|
||||||
|
color::palettes::basic::*,
|
||||||
|
prelude::*,
|
||||||
|
window::{WindowCreated, WindowResolution},
|
||||||
|
winit::WinitPlugin,
|
||||||
|
};
|
||||||
|
use bevy_wayland::{layer_shell::LayerShellSettings, WaylandPlugin};
|
||||||
|
use smithay_client_toolkit::shell::wlr_layer::{Anchor, Layer};
|
||||||
|
|
||||||
|
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
|
||||||
|
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
|
||||||
|
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins.build().disable::<WinitPlugin>(),
|
DefaultPlugins
|
||||||
|
.build()
|
||||||
|
.disable::<WinitPlugin>()
|
||||||
|
.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
resolution: WindowResolution::new(400.0, 400.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
WaylandPlugin,
|
WaylandPlugin,
|
||||||
))
|
))
|
||||||
|
.init_resource::<NewWindowInfo>()
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (button_system, setup_new_window, spawn_window))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, windows: Query<Entity, With<Window>>) {
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn button_system(
|
||||||
|
mut interaction_query: Query<
|
||||||
|
(
|
||||||
|
&Interaction,
|
||||||
|
&mut BackgroundColor,
|
||||||
|
&mut BorderColor,
|
||||||
|
&Children,
|
||||||
|
),
|
||||||
|
(Changed<Interaction>, With<Button>),
|
||||||
|
>,
|
||||||
|
mut text_query: Query<&mut Text>,
|
||||||
|
) {
|
||||||
|
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
|
||||||
|
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||||
|
match *interaction {
|
||||||
|
Interaction::Pressed => {
|
||||||
|
**text = "Press".to_string();
|
||||||
|
*color = PRESSED_BUTTON.into();
|
||||||
|
border_color.0 = RED.into();
|
||||||
|
}
|
||||||
|
Interaction::Hovered => {
|
||||||
|
**text = "Hover".to_string();
|
||||||
|
*color = HOVERED_BUTTON.into();
|
||||||
|
border_color.0 = Color::WHITE;
|
||||||
|
}
|
||||||
|
Interaction::None => {
|
||||||
|
**text = "Button".to_string();
|
||||||
|
*color = NORMAL_BUTTON.into();
|
||||||
|
border_color.0 = Color::BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Deref, DerefMut)]
|
||||||
|
struct WindowTimer(Timer);
|
||||||
|
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
|
||||||
for entity in &windows {
|
for entity in &windows {
|
||||||
commands.entity(entity).insert(LayerShellWindow::default());
|
commands.entity(entity).insert((
|
||||||
|
LayerShellSettings {
|
||||||
|
anchor: Anchor::all(),
|
||||||
|
layer: Layer::Bottom,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
WindowTimer(Timer::new(Duration::from_secs(5), TimerMode::Once)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// ui camera
|
||||||
|
commands.spawn(Camera2d);
|
||||||
|
commands.spawn(button(&assets));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_window(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut windows: Query<(Entity, &mut WindowTimer)>,
|
||||||
|
mut new_window_info: ResMut<NewWindowInfo>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (entity, mut timer) in &mut windows {
|
||||||
|
timer.tick(time.delta());
|
||||||
|
|
||||||
|
if timer.finished() {
|
||||||
|
commands.entity(entity).remove::<WindowTimer>();
|
||||||
|
let new_window_entity = commands
|
||||||
|
.spawn((
|
||||||
|
Window {
|
||||||
|
title: "UI Only Window".to_string(),
|
||||||
|
resolution: (400., 50.).into(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
LayerShellSettings {
|
||||||
|
layer: Layer::Top,
|
||||||
|
anchor: Anchor::TOP | Anchor::LEFT,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
WindowTimer(Timer::new(Duration::from_secs(5), TimerMode::Once)),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
new_window_info.entity = Some(new_window_entity);
|
||||||
|
new_window_info.is_setup_pending = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct NewWindowInfo {
|
||||||
|
entity: Option<Entity>,
|
||||||
|
is_setup_pending: bool,
|
||||||
|
}
|
||||||
|
fn setup_new_window(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut window_created_events: EventReader<WindowCreated>,
|
||||||
|
mut new_window_info: ResMut<NewWindowInfo>,
|
||||||
|
asset_server: Res<AssetServer>, // For fonts
|
||||||
|
) {
|
||||||
|
for event in window_created_events.read() {
|
||||||
|
if Some(event.window) == new_window_info.entity && new_window_info.is_setup_pending {
|
||||||
|
info!(
|
||||||
|
"New UI window created (ID: {:?}), setting up its camera and UI.",
|
||||||
|
event.window
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Camera {
|
||||||
|
target: bevy::render::camera::RenderTarget::Window(
|
||||||
|
bevy::window::WindowRef::Entity(event.window),
|
||||||
|
),
|
||||||
|
clear_color: ClearColorConfig::Custom(Color::default()),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Camera2d,
|
||||||
|
));
|
||||||
|
new_window_info.is_setup_pending = false; // Mark as setup complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(asset_server: &AssetServer) -> impl Bundle + use<> {
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
children![(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
width: Val::Px(150.0),
|
||||||
|
height: Val::Px(65.0),
|
||||||
|
border: UiRect::all(Val::Px(5.0)),
|
||||||
|
// horizontally center child text
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
// vertically center child text
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BorderColor(Color::BLACK),
|
||||||
|
BorderRadius::MAX,
|
||||||
|
BackgroundColor(NORMAL_BUTTON),
|
||||||
|
children![(
|
||||||
|
Text::new("Button"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 33.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::srgb(0.9, 0.9, 0.9)),
|
||||||
|
TextShadow::default(),
|
||||||
|
)]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use smithay_client_toolkit::{
|
|
||||||
delegate_seat,
|
|
||||||
reexports::client::QueueHandle,
|
|
||||||
seat::{SeatHandler, SeatState},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::WaylandState;
|
|
||||||
|
|
||||||
pub struct InputHandlerPlugin;
|
|
||||||
impl Plugin for InputHandlerPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
let globals = app.world().non_send_resource();
|
|
||||||
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
|
|
||||||
let seat_state = SeatState::new(globals, queue_handle);
|
|
||||||
|
|
||||||
app.insert_non_send_resource(seat_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeatHandler for WaylandState {
|
|
||||||
fn seat_state(&mut self) -> &mut SeatState {
|
|
||||||
self.world_mut()
|
|
||||||
.non_send_resource_mut::<SeatState>()
|
|
||||||
.into_inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_seat(
|
|
||||||
&mut self,
|
|
||||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_capability(
|
|
||||||
&mut self,
|
|
||||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
|
||||||
_capability: smithay_client_toolkit::seat::Capability,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_capability(
|
|
||||||
&mut self,
|
|
||||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
|
||||||
_capability: smithay_client_toolkit::seat::Capability,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_seat(
|
|
||||||
&mut self,
|
|
||||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delegate_seat!(WaylandState);
|
|
||||||
@@ -0,0 +1,460 @@
|
|||||||
|
use bevy::input::keyboard::Key;
|
||||||
|
use bevy::input::{keyboard::KeyboardInput, ButtonState};
|
||||||
|
use bevy::log::warn;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::window::WindowEvent;
|
||||||
|
use smithay_client_toolkit::reexports::client::Proxy;
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
delegate_keyboard,
|
||||||
|
seat::keyboard::{KeyEvent, KeyboardHandler, Keysym},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::surface_handler::WaylandSurfaces;
|
||||||
|
use crate::WaylandState;
|
||||||
|
|
||||||
|
/// Converts a Smithay keyboard event to a Bevy keyboard input event.
|
||||||
|
fn convert_keyboard_event(
|
||||||
|
event: KeyEvent,
|
||||||
|
entity: Entity,
|
||||||
|
state: ButtonState,
|
||||||
|
) -> bevy::input::keyboard::KeyboardInput {
|
||||||
|
KeyboardInput {
|
||||||
|
state,
|
||||||
|
text: None,
|
||||||
|
window: entity,
|
||||||
|
key_code: convert_to_key_code(event.keysym),
|
||||||
|
logical_key: convert_to_logical_key(event.keysym),
|
||||||
|
repeat: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a Smithay Keysym to a Bevy Key.
|
||||||
|
fn convert_to_logical_key(keysym: Keysym) -> bevy::input::keyboard::Key {
|
||||||
|
// First, attempt to get the character representation based on keyboard layout.
|
||||||
|
if let Some(c) = keysym.key_char() {
|
||||||
|
// Use Key::Character for printable chars. Convert to SmolStr.
|
||||||
|
return Key::Character((c.to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If key_char() returned None, it's a non-printable key (Control, Function, Arrow etc.)
|
||||||
|
// Map the Keysym variant to the corresponding Bevy Key variant.
|
||||||
|
match keysym {
|
||||||
|
// Function Keys
|
||||||
|
Keysym::F1 => Key::F1,
|
||||||
|
Keysym::F2 => Key::F2,
|
||||||
|
Keysym::F3 => Key::F3,
|
||||||
|
Keysym::F4 => Key::F4,
|
||||||
|
Keysym::F5 => Key::F5,
|
||||||
|
Keysym::F6 => Key::F6,
|
||||||
|
Keysym::F7 => Key::F7,
|
||||||
|
Keysym::F8 => Key::F8,
|
||||||
|
Keysym::F9 => Key::F9,
|
||||||
|
Keysym::F10 => Key::F10,
|
||||||
|
Keysym::F11 => Key::F11,
|
||||||
|
Keysym::F12 => Key::F12,
|
||||||
|
Keysym::F13 => Key::F13,
|
||||||
|
Keysym::F14 => Key::F14,
|
||||||
|
Keysym::F15 => Key::F15,
|
||||||
|
Keysym::F16 => Key::F16,
|
||||||
|
Keysym::F17 => Key::F17,
|
||||||
|
Keysym::F18 => Key::F18,
|
||||||
|
Keysym::F19 => Key::F19,
|
||||||
|
Keysym::F20 => Key::F20,
|
||||||
|
Keysym::F21 => Key::F21,
|
||||||
|
Keysym::F22 => Key::F22,
|
||||||
|
Keysym::F23 => Key::F23,
|
||||||
|
Keysym::F24 => Key::F24,
|
||||||
|
|
||||||
|
// Control & Navigation
|
||||||
|
Keysym::Escape => Key::Escape,
|
||||||
|
Keysym::Pause => Key::Pause,
|
||||||
|
Keysym::Scroll_Lock => Key::ScrollLock,
|
||||||
|
Keysym::Print => Key::PrintScreen,
|
||||||
|
Keysym::Insert => Key::Insert,
|
||||||
|
Keysym::Delete => Key::Delete,
|
||||||
|
Keysym::Home => Key::Home,
|
||||||
|
Keysym::End => Key::End,
|
||||||
|
Keysym::Page_Up => Key::PageUp,
|
||||||
|
Keysym::Page_Down => Key::PageDown,
|
||||||
|
Keysym::BackSpace => Key::Backspace,
|
||||||
|
Keysym::Return | Keysym::KP_Enter => Key::Enter, // Map both main and numpad Enter
|
||||||
|
Keysym::Tab => Key::Tab,
|
||||||
|
Keysym::Menu => Key::ContextMenu,
|
||||||
|
|
||||||
|
// Arrow Keys
|
||||||
|
Keysym::Left => Key::ArrowLeft,
|
||||||
|
Keysym::Right => Key::ArrowRight,
|
||||||
|
Keysym::Up => Key::ArrowUp,
|
||||||
|
Keysym::Down => Key::ArrowDown,
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
Keysym::Shift_L => Key::Shift,
|
||||||
|
Keysym::Shift_R => Key::Shift,
|
||||||
|
Keysym::Control_L => Key::Control,
|
||||||
|
Keysym::Control_R => Key::Control,
|
||||||
|
Keysym::Alt_L => Key::Alt,
|
||||||
|
Keysym::Alt_R => Key::Alt,
|
||||||
|
Keysym::Super_L | Keysym::Meta_L => Key::Super, // Combine Super/Meta
|
||||||
|
Keysym::Super_R | Keysym::Meta_R => Key::Super,
|
||||||
|
Keysym::Caps_Lock => Key::CapsLock,
|
||||||
|
Keysym::Num_Lock => Key::NumLock,
|
||||||
|
|
||||||
|
// Keys that *might* have produced a character but key_char() returned None.
|
||||||
|
// This could happen for dead keys, IME input, or if key_char() implementation is limited.
|
||||||
|
// We need a fallback. Panicking with todo!() highlights these cases during development.
|
||||||
|
// A production system might need more robust handling (e.g., logging, ignoring, specific Key variant if available).
|
||||||
|
Keysym::A
|
||||||
|
| Keysym::B
|
||||||
|
| Keysym::C
|
||||||
|
| Keysym::D
|
||||||
|
| Keysym::E
|
||||||
|
| Keysym::F
|
||||||
|
| Keysym::G
|
||||||
|
| Keysym::H
|
||||||
|
| Keysym::I
|
||||||
|
| Keysym::J
|
||||||
|
| Keysym::K
|
||||||
|
| Keysym::L
|
||||||
|
| Keysym::M
|
||||||
|
| Keysym::N
|
||||||
|
| Keysym::O
|
||||||
|
| Keysym::P
|
||||||
|
| Keysym::Q
|
||||||
|
| Keysym::R
|
||||||
|
| Keysym::S
|
||||||
|
| Keysym::T
|
||||||
|
| Keysym::U
|
||||||
|
| Keysym::V
|
||||||
|
| Keysym::W
|
||||||
|
| Keysym::X
|
||||||
|
| Keysym::Y
|
||||||
|
| Keysym::Z
|
||||||
|
| Keysym::_0
|
||||||
|
| Keysym::_1
|
||||||
|
| Keysym::_2
|
||||||
|
| Keysym::_3
|
||||||
|
| Keysym::_4
|
||||||
|
| Keysym::_5
|
||||||
|
| Keysym::_6
|
||||||
|
| Keysym::_7
|
||||||
|
| Keysym::_8
|
||||||
|
| Keysym::_9
|
||||||
|
| Keysym::grave
|
||||||
|
| Keysym::minus
|
||||||
|
| Keysym::equal
|
||||||
|
| Keysym::bracketleft
|
||||||
|
| Keysym::bracketright
|
||||||
|
| Keysym::backslash
|
||||||
|
| Keysym::semicolon
|
||||||
|
| Keysym::apostrophe
|
||||||
|
| Keysym::comma
|
||||||
|
| Keysym::period
|
||||||
|
| Keysym::slash
|
||||||
|
| Keysym::space => {
|
||||||
|
warn!(
|
||||||
|
"Keysym {:?} did not produce a character via key_char(). This might be unexpected.",
|
||||||
|
keysym
|
||||||
|
);
|
||||||
|
// Bevy's Key enum doesn't have a generic 'Unidentified' or 'Unknown' variant.
|
||||||
|
// Panicking via todo!() helps catch these during development.
|
||||||
|
// You might need custom logic or decide to ignore these cases in production.
|
||||||
|
todo!(
|
||||||
|
"Unhandled case: Keysym {:?} did not yield a char via key_char(). How to map to bevy::input::keyboard::Key?",
|
||||||
|
keysym
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch-all for any other Keysym variants not handled above.
|
||||||
|
_ => {
|
||||||
|
todo!("Unhandled keysym variant: {:?}", keysym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a Smithay Keysym to a Bevy KeyCode.
|
||||||
|
fn convert_to_key_code(keysym: Keysym) -> bevy::prelude::KeyCode {
|
||||||
|
match keysym {
|
||||||
|
// Letters
|
||||||
|
Keysym::A => bevy::prelude::KeyCode::KeyA,
|
||||||
|
Keysym::B => bevy::prelude::KeyCode::KeyB,
|
||||||
|
Keysym::C => bevy::prelude::KeyCode::KeyC,
|
||||||
|
Keysym::D => bevy::prelude::KeyCode::KeyD,
|
||||||
|
Keysym::E => bevy::prelude::KeyCode::KeyE,
|
||||||
|
Keysym::F => bevy::prelude::KeyCode::KeyF,
|
||||||
|
Keysym::G => bevy::prelude::KeyCode::KeyG,
|
||||||
|
Keysym::H => bevy::prelude::KeyCode::KeyH,
|
||||||
|
Keysym::I => bevy::prelude::KeyCode::KeyI,
|
||||||
|
Keysym::J => bevy::prelude::KeyCode::KeyJ,
|
||||||
|
Keysym::K => bevy::prelude::KeyCode::KeyK,
|
||||||
|
Keysym::L => bevy::prelude::KeyCode::KeyL,
|
||||||
|
Keysym::M => bevy::prelude::KeyCode::KeyM,
|
||||||
|
Keysym::N => bevy::prelude::KeyCode::KeyN,
|
||||||
|
Keysym::O => bevy::prelude::KeyCode::KeyO,
|
||||||
|
Keysym::P => bevy::prelude::KeyCode::KeyP,
|
||||||
|
Keysym::Q => bevy::prelude::KeyCode::KeyQ,
|
||||||
|
Keysym::R => bevy::prelude::KeyCode::KeyR,
|
||||||
|
Keysym::S => bevy::prelude::KeyCode::KeyS,
|
||||||
|
Keysym::T => bevy::prelude::KeyCode::KeyT,
|
||||||
|
Keysym::U => bevy::prelude::KeyCode::KeyU,
|
||||||
|
Keysym::V => bevy::prelude::KeyCode::KeyV,
|
||||||
|
Keysym::W => bevy::prelude::KeyCode::KeyW,
|
||||||
|
Keysym::X => bevy::prelude::KeyCode::KeyX,
|
||||||
|
Keysym::Y => bevy::prelude::KeyCode::KeyY,
|
||||||
|
Keysym::Z => bevy::prelude::KeyCode::KeyZ,
|
||||||
|
|
||||||
|
Keysym::a => bevy::prelude::KeyCode::KeyA,
|
||||||
|
Keysym::b => bevy::prelude::KeyCode::KeyB,
|
||||||
|
Keysym::c => bevy::prelude::KeyCode::KeyC,
|
||||||
|
Keysym::d => bevy::prelude::KeyCode::KeyD,
|
||||||
|
Keysym::e => bevy::prelude::KeyCode::KeyE,
|
||||||
|
Keysym::f => bevy::prelude::KeyCode::KeyF,
|
||||||
|
Keysym::g => bevy::prelude::KeyCode::KeyG,
|
||||||
|
Keysym::h => bevy::prelude::KeyCode::KeyH,
|
||||||
|
Keysym::i => bevy::prelude::KeyCode::KeyI,
|
||||||
|
Keysym::j => bevy::prelude::KeyCode::KeyJ,
|
||||||
|
Keysym::k => bevy::prelude::KeyCode::KeyK,
|
||||||
|
Keysym::l => bevy::prelude::KeyCode::KeyL,
|
||||||
|
Keysym::m => bevy::prelude::KeyCode::KeyM,
|
||||||
|
Keysym::n => bevy::prelude::KeyCode::KeyN,
|
||||||
|
Keysym::o => bevy::prelude::KeyCode::KeyO,
|
||||||
|
Keysym::p => bevy::prelude::KeyCode::KeyP,
|
||||||
|
Keysym::q => bevy::prelude::KeyCode::KeyQ,
|
||||||
|
Keysym::r => bevy::prelude::KeyCode::KeyR,
|
||||||
|
Keysym::s => bevy::prelude::KeyCode::KeyS,
|
||||||
|
Keysym::t => bevy::prelude::KeyCode::KeyT,
|
||||||
|
Keysym::u => bevy::prelude::KeyCode::KeyU,
|
||||||
|
Keysym::v => bevy::prelude::KeyCode::KeyV,
|
||||||
|
Keysym::w => bevy::prelude::KeyCode::KeyW,
|
||||||
|
Keysym::x => bevy::prelude::KeyCode::KeyX,
|
||||||
|
Keysym::y => bevy::prelude::KeyCode::KeyY,
|
||||||
|
Keysym::z => bevy::prelude::KeyCode::KeyZ,
|
||||||
|
|
||||||
|
// Numbers Row (might be Keysym::0, Keysym::1 etc. in some libs)
|
||||||
|
Keysym::_0 => bevy::prelude::KeyCode::Digit0,
|
||||||
|
Keysym::_1 => bevy::prelude::KeyCode::Digit1,
|
||||||
|
Keysym::_2 => bevy::prelude::KeyCode::Digit2,
|
||||||
|
Keysym::_3 => bevy::prelude::KeyCode::Digit3,
|
||||||
|
Keysym::_4 => bevy::prelude::KeyCode::Digit4,
|
||||||
|
Keysym::_5 => bevy::prelude::KeyCode::Digit5,
|
||||||
|
Keysym::_6 => bevy::prelude::KeyCode::Digit6,
|
||||||
|
Keysym::_7 => bevy::prelude::KeyCode::Digit7,
|
||||||
|
Keysym::_8 => bevy::prelude::KeyCode::Digit8,
|
||||||
|
Keysym::_9 => bevy::prelude::KeyCode::Digit9,
|
||||||
|
|
||||||
|
// Function Keys
|
||||||
|
Keysym::F1 => bevy::prelude::KeyCode::F1,
|
||||||
|
Keysym::F2 => bevy::prelude::KeyCode::F2,
|
||||||
|
Keysym::F3 => bevy::prelude::KeyCode::F3,
|
||||||
|
Keysym::F4 => bevy::prelude::KeyCode::F4,
|
||||||
|
Keysym::F5 => bevy::prelude::KeyCode::F5,
|
||||||
|
Keysym::F6 => bevy::prelude::KeyCode::F6,
|
||||||
|
Keysym::F7 => bevy::prelude::KeyCode::F7,
|
||||||
|
Keysym::F8 => bevy::prelude::KeyCode::F8,
|
||||||
|
Keysym::F9 => bevy::prelude::KeyCode::F9,
|
||||||
|
Keysym::F10 => bevy::prelude::KeyCode::F10,
|
||||||
|
Keysym::F11 => bevy::prelude::KeyCode::F11,
|
||||||
|
Keysym::F12 => bevy::prelude::KeyCode::F12,
|
||||||
|
Keysym::F13 => bevy::prelude::KeyCode::F13,
|
||||||
|
Keysym::F14 => bevy::prelude::KeyCode::F14,
|
||||||
|
Keysym::F15 => bevy::prelude::KeyCode::F15,
|
||||||
|
Keysym::F16 => bevy::prelude::KeyCode::F16,
|
||||||
|
Keysym::F17 => bevy::prelude::KeyCode::F17,
|
||||||
|
Keysym::F18 => bevy::prelude::KeyCode::F18,
|
||||||
|
Keysym::F19 => bevy::prelude::KeyCode::F19,
|
||||||
|
Keysym::F20 => bevy::prelude::KeyCode::F20,
|
||||||
|
Keysym::F21 => bevy::prelude::KeyCode::F21,
|
||||||
|
Keysym::F22 => bevy::prelude::KeyCode::F22,
|
||||||
|
Keysym::F23 => bevy::prelude::KeyCode::F23,
|
||||||
|
Keysym::F24 => bevy::prelude::KeyCode::F24,
|
||||||
|
|
||||||
|
// Arrow Keys
|
||||||
|
Keysym::Left => bevy::prelude::KeyCode::ArrowLeft,
|
||||||
|
Keysym::Right => bevy::prelude::KeyCode::ArrowRight,
|
||||||
|
Keysym::Up => bevy::prelude::KeyCode::ArrowUp,
|
||||||
|
Keysym::Down => bevy::prelude::KeyCode::ArrowDown,
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
Keysym::Shift_L => bevy::prelude::KeyCode::ShiftLeft,
|
||||||
|
Keysym::Shift_R => bevy::prelude::KeyCode::ShiftRight,
|
||||||
|
Keysym::Control_L => bevy::prelude::KeyCode::ControlLeft,
|
||||||
|
Keysym::Control_R => bevy::prelude::KeyCode::ControlRight,
|
||||||
|
Keysym::Alt_L => bevy::prelude::KeyCode::AltLeft,
|
||||||
|
Keysym::Alt_R => bevy::prelude::KeyCode::AltRight,
|
||||||
|
Keysym::Super_L | Keysym::Meta_L => bevy::prelude::KeyCode::SuperLeft, // Handle Super/Meta variations
|
||||||
|
Keysym::Super_R | Keysym::Meta_R => bevy::prelude::KeyCode::SuperRight,
|
||||||
|
Keysym::Caps_Lock => bevy::prelude::KeyCode::CapsLock,
|
||||||
|
Keysym::Num_Lock => bevy::prelude::KeyCode::NumLock,
|
||||||
|
Keysym::Scroll_Lock => bevy::prelude::KeyCode::ScrollLock,
|
||||||
|
|
||||||
|
// Navigation & Editing
|
||||||
|
Keysym::Home => bevy::prelude::KeyCode::Home,
|
||||||
|
Keysym::End => bevy::prelude::KeyCode::End,
|
||||||
|
Keysym::Page_Up => bevy::prelude::KeyCode::PageUp,
|
||||||
|
Keysym::Page_Down => bevy::prelude::KeyCode::PageDown,
|
||||||
|
Keysym::Insert => bevy::prelude::KeyCode::Insert,
|
||||||
|
Keysym::Delete => bevy::prelude::KeyCode::Delete,
|
||||||
|
Keysym::BackSpace => bevy::prelude::KeyCode::Backspace, // Note: Bevy uses Backspace
|
||||||
|
|
||||||
|
// Whitespace & Control (Keysym::Return for main Enter)
|
||||||
|
Keysym::space => bevy::prelude::KeyCode::Space,
|
||||||
|
Keysym::Tab => bevy::prelude::KeyCode::Tab,
|
||||||
|
Keysym::Return | Keysym::KP_Enter => bevy::prelude::KeyCode::Enter, // Map both Enter and Numpad Enter
|
||||||
|
Keysym::Escape => bevy::prelude::KeyCode::Escape,
|
||||||
|
|
||||||
|
// Numpad Keys (might start with KP_ in some libs)
|
||||||
|
Keysym::KP_0 => bevy::prelude::KeyCode::Numpad0,
|
||||||
|
Keysym::KP_1 => bevy::prelude::KeyCode::Numpad1,
|
||||||
|
Keysym::KP_2 => bevy::prelude::KeyCode::Numpad2,
|
||||||
|
Keysym::KP_3 => bevy::prelude::KeyCode::Numpad3,
|
||||||
|
Keysym::KP_4 => bevy::prelude::KeyCode::Numpad4,
|
||||||
|
Keysym::KP_5 => bevy::prelude::KeyCode::Numpad5,
|
||||||
|
Keysym::KP_6 => bevy::prelude::KeyCode::Numpad6,
|
||||||
|
Keysym::KP_7 => bevy::prelude::KeyCode::Numpad7,
|
||||||
|
Keysym::KP_8 => bevy::prelude::KeyCode::Numpad8,
|
||||||
|
Keysym::KP_9 => bevy::prelude::KeyCode::Numpad9,
|
||||||
|
Keysym::KP_Add => bevy::prelude::KeyCode::NumpadAdd,
|
||||||
|
Keysym::KP_Subtract => bevy::prelude::KeyCode::NumpadSubtract,
|
||||||
|
Keysym::KP_Multiply => bevy::prelude::KeyCode::NumpadMultiply,
|
||||||
|
Keysym::KP_Divide => bevy::prelude::KeyCode::NumpadDivide,
|
||||||
|
Keysym::KP_Decimal => bevy::prelude::KeyCode::NumpadDecimal,
|
||||||
|
Keysym::KP_Separator => bevy::prelude::KeyCode::NumpadComma, // Bevy uses NumpadComma for the separator
|
||||||
|
Keysym::KP_Equal => bevy::prelude::KeyCode::NumpadEqual,
|
||||||
|
// KP_Enter handled above with Return
|
||||||
|
|
||||||
|
// Punctuation & Symbols (Names can vary significantly)
|
||||||
|
Keysym::grave => bevy::prelude::KeyCode::Backquote, // `
|
||||||
|
Keysym::minus => bevy::prelude::KeyCode::Minus, // -
|
||||||
|
Keysym::equal => bevy::prelude::KeyCode::Equal, // =
|
||||||
|
Keysym::bracketleft => bevy::prelude::KeyCode::BracketLeft, // [
|
||||||
|
Keysym::bracketright => bevy::prelude::KeyCode::BracketRight, // ]
|
||||||
|
Keysym::backslash => bevy::prelude::KeyCode::Backslash, // \
|
||||||
|
Keysym::semicolon => bevy::prelude::KeyCode::Semicolon, // ;
|
||||||
|
Keysym::apostrophe => bevy::prelude::KeyCode::Quote, // ' (Bevy uses Quote)
|
||||||
|
Keysym::comma => bevy::prelude::KeyCode::Comma, // ,
|
||||||
|
Keysym::period => bevy::prelude::KeyCode::Period, // .
|
||||||
|
Keysym::slash => bevy::prelude::KeyCode::Slash, // /
|
||||||
|
|
||||||
|
// Other Keys
|
||||||
|
Keysym::Print => bevy::prelude::KeyCode::PrintScreen,
|
||||||
|
Keysym::Pause => bevy::prelude::KeyCode::Pause,
|
||||||
|
Keysym::Menu => bevy::prelude::KeyCode::ContextMenu, // Bevy uses ContextMenu
|
||||||
|
|
||||||
|
// --- Fallback ---
|
||||||
|
// This arm catches any Keysym not explicitly handled above.
|
||||||
|
// Using todo!() will cause a panic, alerting you during development
|
||||||
|
// that a key mapping is missing. For production, consider returning Option<KeyCode>
|
||||||
|
// or logging an error instead of panicking.
|
||||||
|
_ => {
|
||||||
|
// e.g., eprintln!("Warning: Unhandled keysym: {:?}", keysym);
|
||||||
|
// If the function returned Option<KeyCode>, you'd return None here.
|
||||||
|
// Since it must return KeyCode, panic is the explicit way to fail.
|
||||||
|
todo!("Unhandled keysym: {:?}", keysym)
|
||||||
|
// Or potentially return a "default" like Escape, but this hides errors:
|
||||||
|
// KeyCode::Escape
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct ActiveWindow(Entity);
|
||||||
|
impl KeyboardHandler for WaylandState {
|
||||||
|
fn enter(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
|
||||||
|
_serial: u32,
|
||||||
|
_raw: &[u32],
|
||||||
|
_keysyms: &[Keysym],
|
||||||
|
) {
|
||||||
|
let wayland_surfaces = self.world().non_send_resource::<WaylandSurfaces>();
|
||||||
|
let active_window_entity = *wayland_surfaces
|
||||||
|
.get_window_entity(&surface.id())
|
||||||
|
.expect("keyboard event was passed before creating a window!");
|
||||||
|
self.world_mut()
|
||||||
|
.insert_resource(ActiveWindow(active_window_entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
|
||||||
|
_serial: u32,
|
||||||
|
) {
|
||||||
|
let wayland_surfaces = self.world().non_send_resource::<WaylandSurfaces>();
|
||||||
|
let left_window_entity = *wayland_surfaces
|
||||||
|
.get_window_entity(&surface.id())
|
||||||
|
.expect("keyboard event was passed before creating a window!");
|
||||||
|
let active_window_entity = **self.world().resource::<ActiveWindow>();
|
||||||
|
if left_window_entity != active_window_entity {
|
||||||
|
// Currently we are assuming that there can only be one window which has access to the
|
||||||
|
// keyboard so this check is just redundancy.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.world_mut().remove_resource::<ActiveWindow>();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_key(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
_serial: u32,
|
||||||
|
event: KeyEvent,
|
||||||
|
) {
|
||||||
|
info!("Wayland got a keypress event {:?}", event);
|
||||||
|
let active_window_entity = **self.world().resource::<ActiveWindow>();
|
||||||
|
let keyboard_event =
|
||||||
|
convert_keyboard_event(event, active_window_entity, ButtonState::Pressed);
|
||||||
|
self.world_mut()
|
||||||
|
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repeat_key(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
_serial: u32,
|
||||||
|
event: KeyEvent,
|
||||||
|
) {
|
||||||
|
let active_window_entity = **self.world().resource::<ActiveWindow>();
|
||||||
|
let mut keyboard_event =
|
||||||
|
convert_keyboard_event(event, active_window_entity, ButtonState::Pressed);
|
||||||
|
keyboard_event.repeat = true;
|
||||||
|
self.world_mut()
|
||||||
|
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_key(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
_serial: u32,
|
||||||
|
event: KeyEvent,
|
||||||
|
) {
|
||||||
|
let active_window_entity = **self.world().resource::<ActiveWindow>();
|
||||||
|
let keyboard_event =
|
||||||
|
convert_keyboard_event(event, active_window_entity, ButtonState::Released);
|
||||||
|
self.world_mut()
|
||||||
|
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_modifiers(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
|
||||||
|
_serial: u32,
|
||||||
|
_modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
|
||||||
|
_raw_modifiers: smithay_client_toolkit::seat::keyboard::RawModifiers,
|
||||||
|
_layout: u32,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_keyboard!(WaylandState);
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
delegate_seat,
|
||||||
|
reexports::client::{
|
||||||
|
protocol::{wl_keyboard::WlKeyboard, wl_pointer::WlPointer},
|
||||||
|
QueueHandle,
|
||||||
|
},
|
||||||
|
seat::{Capability, SeatHandler, SeatState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::WaylandState;
|
||||||
|
|
||||||
|
mod keyboard;
|
||||||
|
mod pointer;
|
||||||
|
|
||||||
|
pub struct InputHandlerPlugin;
|
||||||
|
impl Plugin for InputHandlerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let globals = app.world().non_send_resource();
|
||||||
|
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
|
||||||
|
let seat_state = SeatState::new(globals, queue_handle);
|
||||||
|
|
||||||
|
app.insert_non_send_resource(seat_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeatHandler for WaylandState {
|
||||||
|
fn seat_state(&mut self) -> &mut SeatState {
|
||||||
|
self.world_mut()
|
||||||
|
.non_send_resource_mut::<SeatState>()
|
||||||
|
.into_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_seat(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_capability(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
||||||
|
capability: smithay_client_toolkit::seat::Capability,
|
||||||
|
) {
|
||||||
|
if capability == Capability::Keyboard {
|
||||||
|
//let mut seat_state = self.world_mut().non_send_resource_mut::<SeatState>();
|
||||||
|
//let wl_keyboard = seat_state
|
||||||
|
// .get_keyboard(qh, &seat, None)
|
||||||
|
// .expect("error while attaching keyboard!");
|
||||||
|
//self.world_mut().insert_non_send_resource(wl_keyboard);
|
||||||
|
info!("Keyboard Attached");
|
||||||
|
}
|
||||||
|
if capability == Capability::Pointer {
|
||||||
|
let mut seat_state = self.world_mut().non_send_resource_mut::<SeatState>();
|
||||||
|
let wl_pointer = seat_state
|
||||||
|
.get_pointer(qh, &seat)
|
||||||
|
.expect("error while attaching pointer!");
|
||||||
|
self.world_mut().insert_non_send_resource(wl_pointer);
|
||||||
|
info!("Pointer Attached");
|
||||||
|
}
|
||||||
|
if capability == Capability::Touch {
|
||||||
|
info!("Touchscreen Attached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_capability(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
||||||
|
capability: smithay_client_toolkit::seat::Capability,
|
||||||
|
) {
|
||||||
|
if capability == Capability::Keyboard {
|
||||||
|
self.world_mut().remove_non_send_resource::<WlKeyboard>();
|
||||||
|
info!("Keyboard detatched");
|
||||||
|
}
|
||||||
|
if capability == Capability::Pointer {
|
||||||
|
self.world_mut().remove_non_send_resource::<WlPointer>();
|
||||||
|
info!("Pointer detatched");
|
||||||
|
}
|
||||||
|
if capability == Capability::Touch {
|
||||||
|
info!("Touchscreen Attached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_seat(
|
||||||
|
&mut self,
|
||||||
|
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_seat!(WaylandState);
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
use bevy::{
|
||||||
|
input::{
|
||||||
|
mouse::{MouseButtonInput, MouseScrollUnit, MouseWheel},
|
||||||
|
ButtonState,
|
||||||
|
},
|
||||||
|
prelude::MouseButton,
|
||||||
|
window::{CursorEntered, CursorLeft, CursorMoved, Window, WindowEvent},
|
||||||
|
};
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
delegate_pointer,
|
||||||
|
reexports::{client::Proxy, csd_frame::WindowState},
|
||||||
|
seat::pointer::PointerHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{surface_handler::WaylandSurfaces, WaylandState};
|
||||||
|
|
||||||
|
/// Converts a u32 button code to a Bevy MouseButton.
|
||||||
|
fn convert_to_mouse_button(button: u32) -> MouseButton {
|
||||||
|
match button {
|
||||||
|
272 => MouseButton::Left,
|
||||||
|
273 => MouseButton::Right,
|
||||||
|
274 => MouseButton::Middle,
|
||||||
|
277 => MouseButton::Forward,
|
||||||
|
278 => MouseButton::Back,
|
||||||
|
other => MouseButton::Other(other as u16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerHandler for WaylandState {
|
||||||
|
fn pointer_frame(
|
||||||
|
&mut self,
|
||||||
|
_: &smithay_client_toolkit::reexports::client::Connection,
|
||||||
|
_: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||||
|
_: &smithay_client_toolkit::reexports::client::protocol::wl_pointer::WlPointer,
|
||||||
|
events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
|
||||||
|
) {
|
||||||
|
for event in events {
|
||||||
|
let smithay_windows = self.world().non_send_resource::<WaylandSurfaces>();
|
||||||
|
let entity = smithay_windows.get_window_entity(&event.surface.id());
|
||||||
|
|
||||||
|
if entity.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let entity = *entity.unwrap();
|
||||||
|
|
||||||
|
let window = self.world().get::<Window>(entity).unwrap().clone();
|
||||||
|
let mut position = bevy::math::Vec2 {
|
||||||
|
x: event.position.0 as f32,
|
||||||
|
y: event.position.1 as f32,
|
||||||
|
};
|
||||||
|
let delta = window
|
||||||
|
.physical_cursor_position()
|
||||||
|
.map(|old_position| (position - old_position) / window.scale_factor());
|
||||||
|
let pointer_event: WindowEvent = match event.kind {
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Enter { .. } => {
|
||||||
|
CursorEntered { window: entity }.into()
|
||||||
|
}
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Leave { .. } => {
|
||||||
|
CursorLeft { window: entity }.into()
|
||||||
|
}
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Motion { .. } => {
|
||||||
|
self.world_mut()
|
||||||
|
.get_mut::<Window>(entity)
|
||||||
|
.unwrap()
|
||||||
|
.set_physical_cursor_position(Some(position.as_dvec2()));
|
||||||
|
position /= window.scale_factor();
|
||||||
|
CursorMoved {
|
||||||
|
window: entity,
|
||||||
|
position,
|
||||||
|
delta,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Press {
|
||||||
|
button, ..
|
||||||
|
} => MouseButtonInput {
|
||||||
|
button: convert_to_mouse_button(button),
|
||||||
|
state: ButtonState::Pressed,
|
||||||
|
window: entity,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Release {
|
||||||
|
button, ..
|
||||||
|
} => MouseButtonInput {
|
||||||
|
button: convert_to_mouse_button(button),
|
||||||
|
state: ButtonState::Released,
|
||||||
|
window: entity,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
smithay_client_toolkit::seat::pointer::PointerEventKind::Axis {
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
..
|
||||||
|
} => MouseWheel {
|
||||||
|
unit: MouseScrollUnit::Pixel,
|
||||||
|
x: horizontal.absolute as f32,
|
||||||
|
y: vertical.absolute as f32,
|
||||||
|
window: entity,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
let window_event: WindowEvent = pointer_event;
|
||||||
|
self.world_mut().send_event::<WindowEvent>(window_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_pointer!(WaylandState);
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use bevy::{core_pipeline::core_2d::graph::input, prelude::*};
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
compositor::{CompositorState, Region},
|
||||||
|
reexports::client::{protocol::wl_compositor::WlCompositor, QueueHandle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{input_region, surface_handler::WaylandSurfaces, WaylandState};
|
||||||
|
|
||||||
|
#[derive(Component, Deref)]
|
||||||
|
pub struct InputRegion(Rect);
|
||||||
|
|
||||||
|
pub struct InputRegionPlugin;
|
||||||
|
impl Plugin for InputRegionPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Update, update_input_region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_input_region(
|
||||||
|
windows: Query<(Entity, Option<&InputRegion>), With<Window>>,
|
||||||
|
compositor: NonSendMut<CompositorState>,
|
||||||
|
wayland_surfaces: NonSendMut<WaylandSurfaces>,
|
||||||
|
) {
|
||||||
|
for (entity, input_region) in &windows {
|
||||||
|
let window_wrapper = wayland_surfaces.get_window_wrapper(entity).unwrap();
|
||||||
|
let region = input_region.map(|input_region| {
|
||||||
|
let region = Region::new(compositor.as_ref()).unwrap();
|
||||||
|
region.add(
|
||||||
|
input_region.min.x as i32,
|
||||||
|
input_region.max.y as i32,
|
||||||
|
input_region.width() as i32,
|
||||||
|
input_region.height() as i32,
|
||||||
|
);
|
||||||
|
region
|
||||||
|
});
|
||||||
|
if let Some(region) = region {
|
||||||
|
window_wrapper
|
||||||
|
.wl_surface()
|
||||||
|
.set_input_region(Some(region.wl_region()));
|
||||||
|
} else {
|
||||||
|
window_wrapper.wl_surface().set_input_region(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
-18
@@ -1,24 +1,112 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::{platform::collections::HashMap, prelude::*, ui::update};
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
delegate_layer,
|
delegate_layer,
|
||||||
reexports::client::{globals::GlobalList, QueueHandle},
|
reexports::client::{globals::GlobalList, Proxy, QueueHandle},
|
||||||
shell::{
|
shell::{
|
||||||
wlr_layer::{Layer, LayerShell, LayerShellHandler},
|
wlr_layer::{
|
||||||
|
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
|
||||||
|
},
|
||||||
WaylandSurface,
|
WaylandSurface,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{surface_handler::WaylandSurfaces, WaylandState};
|
use crate::{
|
||||||
|
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
|
||||||
|
WaylandState,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Default, Deref, DerefMut)]
|
||||||
pub struct LayerShellWindow {}
|
struct LayerShellWindows(HashMap<Entity, LayerShellWindow>);
|
||||||
#[derive(Component)]
|
|
||||||
struct LayerShellRoleAssigned;
|
struct LayerShellWindow {
|
||||||
|
layer_surface: LayerSurface,
|
||||||
|
layer_shell_settings: LayerShellSettings,
|
||||||
|
}
|
||||||
|
impl LayerShellWindow {
|
||||||
|
fn new(layer_surface: LayerSurface, layer_shell_settings: LayerShellSettings) -> Self {
|
||||||
|
let mut layer_shell_window = Self {
|
||||||
|
layer_surface,
|
||||||
|
layer_shell_settings,
|
||||||
|
};
|
||||||
|
layer_shell_window.sync();
|
||||||
|
layer_shell_window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync(&mut self) {
|
||||||
|
self.layer_surface
|
||||||
|
.set_layer(self.layer_shell_settings.layer);
|
||||||
|
self.layer_surface
|
||||||
|
.set_anchor(self.layer_shell_settings.anchor);
|
||||||
|
self.layer_surface
|
||||||
|
.set_keyboard_interactivity(self.layer_shell_settings.keyboard_interactivity);
|
||||||
|
self.layer_surface
|
||||||
|
.set_exclusive_zone(self.layer_shell_settings.exclusive_zone);
|
||||||
|
|
||||||
|
let (width, height) = self.layer_shell_settings.size;
|
||||||
|
self.layer_surface.set_size(width, height);
|
||||||
|
|
||||||
|
let (top, right, bottom, left) = self.layer_shell_settings.margin;
|
||||||
|
self.layer_surface.set_margin(top, right, bottom, left);
|
||||||
|
self.layer_surface.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_settings(&mut self, layer_shell_settings: LayerShellSettings) {
|
||||||
|
if self.layer_shell_settings == layer_shell_settings {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.layer_shell_settings = layer_shell_settings;
|
||||||
|
self.sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LayerShellSettings {
|
||||||
|
/// Defines where the layer surface should be anchored to the screen.
|
||||||
|
///
|
||||||
|
/// You can anchor the layer surface to any combination of the top, bottom, left, and right edges of the screen.
|
||||||
|
pub anchor: Anchor,
|
||||||
|
/// Defines the size of the layer surface in pixels.
|
||||||
|
pub size: (u32, u32),
|
||||||
|
/// Defines the amount of exclusive space the layer surface should reserve.
|
||||||
|
///
|
||||||
|
/// Other surfaces will not be placed in this area. A negative value means that the layer surface
|
||||||
|
/// will not reserve any exclusive space.
|
||||||
|
pub exclusive_zone: i32,
|
||||||
|
/// Defines the margins for the layer surface.
|
||||||
|
///
|
||||||
|
/// Margins are specified in the order: top, right, bottom, left.
|
||||||
|
pub margin: (i32, i32, i32, i32),
|
||||||
|
/// Defines how the layer surface should handle keyboard interactivity.
|
||||||
|
///
|
||||||
|
/// If set to `Exclusive`, the layer surface will receive all keyboard input.
|
||||||
|
/// If set to `OnDemand`, the layer surface will only receive keyboard input when it is focused.
|
||||||
|
/// If set to `None`, the layer surface will never receive keyboard input.
|
||||||
|
pub keyboard_interactivity: KeyboardInteractivity,
|
||||||
|
/// Defines the layer that the surface should be placed on.
|
||||||
|
///
|
||||||
|
/// The layer determines the stacking order of the surface. Surfaces on higher layers are
|
||||||
|
/// always drawn on top of surfaces on lower layers.
|
||||||
|
pub layer: Layer,
|
||||||
|
}
|
||||||
|
impl Default for LayerShellSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
anchor: Anchor::empty(),
|
||||||
|
size: Default::default(),
|
||||||
|
exclusive_zone: Default::default(),
|
||||||
|
margin: Default::default(),
|
||||||
|
keyboard_interactivity: Default::default(),
|
||||||
|
layer: Layer::Top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LayerShellPlugin;
|
pub struct LayerShellPlugin;
|
||||||
impl Plugin for LayerShellPlugin {
|
impl Plugin for LayerShellPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Update, assign_layer_shell_role);
|
app.add_systems(PreUpdate, assign_layer_shell_role.after(create_windows))
|
||||||
|
.add_systems(Update, update_layer_shell_settings)
|
||||||
|
.insert_non_send_resource(LayerShellWindows::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,12 +115,10 @@ fn assign_layer_shell_role(
|
|||||||
wayland_surfaces: NonSend<WaylandSurfaces>,
|
wayland_surfaces: NonSend<WaylandSurfaces>,
|
||||||
queue_handle: NonSend<QueueHandle<WaylandState>>,
|
queue_handle: NonSend<QueueHandle<WaylandState>>,
|
||||||
globals: NonSend<GlobalList>,
|
globals: NonSend<GlobalList>,
|
||||||
layer_shell_windows: Query<
|
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
|
||||||
(Entity, &Window, &LayerShellWindow),
|
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
|
||||||
Without<LayerShellRoleAssigned>,
|
|
||||||
>,
|
|
||||||
) {
|
) {
|
||||||
for (entity, _window, _layer_shell_settings) in &layer_shell_windows {
|
for (entity, _window, layer_shell_settings) in &windows {
|
||||||
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
|
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
|
||||||
let surface = window_wrapper
|
let surface = window_wrapper
|
||||||
.expect("tried to assign role before creating surface!")
|
.expect("tried to assign role before creating surface!")
|
||||||
@@ -43,13 +129,27 @@ fn assign_layer_shell_role(
|
|||||||
let layer = layer_shell.create_layer_surface(
|
let layer = layer_shell.create_layer_surface(
|
||||||
&queue_handle,
|
&queue_handle,
|
||||||
surface.clone(),
|
surface.clone(),
|
||||||
Layer::Top,
|
layer_shell_settings.layer,
|
||||||
Some("simple_layer"),
|
Some("simple_layer"),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
layer.commit();
|
|
||||||
Box::leak(Box::new(layer));
|
let _ = layer_shell_windows.insert(
|
||||||
commands.entity(entity).insert(LayerShellRoleAssigned);
|
entity,
|
||||||
|
LayerShellWindow::new(layer, layer_shell_settings.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.entity(entity).insert(SurfaceConfigured);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_layer_shell_settings(
|
||||||
|
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
|
||||||
|
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
|
||||||
|
) {
|
||||||
|
for (entity, _window, layer_shell_settings) in &windows {
|
||||||
|
let layer_shell_window = layer_shell_windows.get_mut(&entity).unwrap();
|
||||||
|
layer_shell_window.set_settings(layer_shell_settings.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,11 +1,12 @@
|
|||||||
use bevy::{app::PluginsState, prelude::*};
|
use bevy::{app::PluginsState, prelude::*};
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
delegate_registry,
|
delegate_registry,
|
||||||
|
globals::ProvidesBoundGlobal,
|
||||||
output::OutputState,
|
output::OutputState,
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::EventLoop,
|
calloop::EventLoop,
|
||||||
calloop_wayland_source::WaylandSource,
|
calloop_wayland_source::WaylandSource,
|
||||||
client::{globals::registry_queue_init, Connection},
|
client::{globals::registry_queue_init, protocol::wl_compositor::WlCompositor, Connection},
|
||||||
},
|
},
|
||||||
registry::{ProvidesRegistryState, RegistryState},
|
registry::{ProvidesRegistryState, RegistryState},
|
||||||
registry_handlers,
|
registry_handlers,
|
||||||
@@ -13,6 +14,7 @@ use smithay_client_toolkit::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod input_handler;
|
mod input_handler;
|
||||||
|
pub mod input_region;
|
||||||
pub mod layer_shell;
|
pub mod layer_shell;
|
||||||
mod output_handler;
|
mod output_handler;
|
||||||
mod surface_handler;
|
mod surface_handler;
|
||||||
@@ -44,6 +46,7 @@ impl Plugin for WaylandPlugin {
|
|||||||
input_handler::InputHandlerPlugin,
|
input_handler::InputHandlerPlugin,
|
||||||
surface_handler::SurfaceHandlerPlugin,
|
surface_handler::SurfaceHandlerPlugin,
|
||||||
layer_shell::LayerShellPlugin,
|
layer_shell::LayerShellPlugin,
|
||||||
|
input_region::InputRegionPlugin,
|
||||||
));
|
));
|
||||||
app.set_runner(|app| runner(app, event_loop));
|
app.set_runner(|app| runner(app, event_loop));
|
||||||
}
|
}
|
||||||
@@ -76,4 +79,5 @@ impl ProvidesRegistryState for WaylandState {
|
|||||||
}
|
}
|
||||||
registry_handlers!(OutputState, SeatState);
|
registry_handlers!(OutputState, SeatState);
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate_registry!(WaylandState);
|
delegate_registry!(WaylandState);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ use smithay_client_toolkit::{
|
|||||||
|
|
||||||
use crate::WaylandState;
|
use crate::WaylandState;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SurfaceConfigured;
|
||||||
pub struct SurfaceHandlerPlugin;
|
pub struct SurfaceHandlerPlugin;
|
||||||
impl Plugin for SurfaceHandlerPlugin {
|
impl Plugin for SurfaceHandlerPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
@@ -113,6 +115,10 @@ impl WaylandSurfaces {
|
|||||||
.get(&entity)
|
.get(&entity)
|
||||||
.map(|surface_id| self.windows.get(surface_id))?
|
.map(|surface_id| self.windows.get(surface_id))?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_window_entity(&self, surface_id: &ObjectId) -> Option<&Entity> {
|
||||||
|
self.surface_to_entity.get(surface_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WaylandSurface {
|
pub struct WaylandSurface {
|
||||||
@@ -173,8 +179,6 @@ pub fn create_windows(
|
|||||||
if wayland_surfaces.get_window_wrapper(entity).is_some() {
|
if wayland_surfaces.get_window_wrapper(entity).is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
println!("Creating Window");
|
|
||||||
|
|
||||||
let surface = wayland_surfaces.create_surface(
|
let surface = wayland_surfaces.create_surface(
|
||||||
entity,
|
entity,
|
||||||
&queue_handle,
|
&queue_handle,
|
||||||
|
|||||||
Reference in New Issue
Block a user