feat: keyboard input and input region
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
# will have compiled files and executables
|
||||
debug
|
||||
target
|
||||
logs.txt
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
@@ -1,18 +1,192 @@
|
||||
use bevy::{prelude::*, winit::WinitPlugin};
|
||||
use bevy_wayland::{layer_shell::LayerShellWindow, WaylandPlugin};
|
||||
use std::time::Duration;
|
||||
|
||||
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() {
|
||||
App::new()
|
||||
.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,
|
||||
))
|
||||
.init_resource::<NewWindowInfo>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (button_system, setup_new_window, spawn_window))
|
||||
.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 {
|
||||
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::{
|
||||
delegate_layer,
|
||||
reexports::client::{globals::GlobalList, QueueHandle},
|
||||
reexports::client::{globals::GlobalList, Proxy, QueueHandle},
|
||||
shell::{
|
||||
wlr_layer::{Layer, LayerShell, LayerShellHandler},
|
||||
wlr_layer::{
|
||||
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
|
||||
},
|
||||
WaylandSurface,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{surface_handler::WaylandSurfaces, WaylandState};
|
||||
use crate::{
|
||||
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
|
||||
WaylandState,
|
||||
};
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct LayerShellWindow {}
|
||||
#[derive(Component)]
|
||||
struct LayerShellRoleAssigned;
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
struct LayerShellWindows(HashMap<Entity, LayerShellWindow>);
|
||||
|
||||
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;
|
||||
impl Plugin for LayerShellPlugin {
|
||||
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>,
|
||||
queue_handle: NonSend<QueueHandle<WaylandState>>,
|
||||
globals: NonSend<GlobalList>,
|
||||
layer_shell_windows: Query<
|
||||
(Entity, &Window, &LayerShellWindow),
|
||||
Without<LayerShellRoleAssigned>,
|
||||
>,
|
||||
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
|
||||
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
|
||||
) {
|
||||
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 surface = window_wrapper
|
||||
.expect("tried to assign role before creating surface!")
|
||||
@@ -43,13 +129,27 @@ fn assign_layer_shell_role(
|
||||
let layer = layer_shell.create_layer_surface(
|
||||
&queue_handle,
|
||||
surface.clone(),
|
||||
Layer::Top,
|
||||
layer_shell_settings.layer,
|
||||
Some("simple_layer"),
|
||||
None,
|
||||
);
|
||||
layer.commit();
|
||||
Box::leak(Box::new(layer));
|
||||
commands.entity(entity).insert(LayerShellRoleAssigned);
|
||||
|
||||
let _ = layer_shell_windows.insert(
|
||||
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 smithay_client_toolkit::{
|
||||
delegate_registry,
|
||||
globals::ProvidesBoundGlobal,
|
||||
output::OutputState,
|
||||
reexports::{
|
||||
calloop::EventLoop,
|
||||
calloop_wayland_source::WaylandSource,
|
||||
client::{globals::registry_queue_init, Connection},
|
||||
client::{globals::registry_queue_init, protocol::wl_compositor::WlCompositor, Connection},
|
||||
},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
registry_handlers,
|
||||
@@ -13,6 +14,7 @@ use smithay_client_toolkit::{
|
||||
};
|
||||
|
||||
mod input_handler;
|
||||
pub mod input_region;
|
||||
pub mod layer_shell;
|
||||
mod output_handler;
|
||||
mod surface_handler;
|
||||
@@ -44,6 +46,7 @@ impl Plugin for WaylandPlugin {
|
||||
input_handler::InputHandlerPlugin,
|
||||
surface_handler::SurfaceHandlerPlugin,
|
||||
layer_shell::LayerShellPlugin,
|
||||
input_region::InputRegionPlugin,
|
||||
));
|
||||
app.set_runner(|app| runner(app, event_loop));
|
||||
}
|
||||
@@ -76,4 +79,5 @@ impl ProvidesRegistryState for WaylandState {
|
||||
}
|
||||
registry_handlers!(OutputState, SeatState);
|
||||
}
|
||||
|
||||
delegate_registry!(WaylandState);
|
||||
|
||||
@@ -19,6 +19,8 @@ use smithay_client_toolkit::{
|
||||
|
||||
use crate::WaylandState;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SurfaceConfigured;
|
||||
pub struct SurfaceHandlerPlugin;
|
||||
impl Plugin for SurfaceHandlerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
@@ -113,6 +115,10 @@ impl WaylandSurfaces {
|
||||
.get(&entity)
|
||||
.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 {
|
||||
@@ -173,8 +179,6 @@ pub fn create_windows(
|
||||
if wayland_surfaces.get_window_wrapper(entity).is_some() {
|
||||
continue;
|
||||
}
|
||||
println!("Creating Window");
|
||||
|
||||
let surface = wayland_surfaces.create_surface(
|
||||
entity,
|
||||
&queue_handle,
|
||||
|
||||
Reference in New Issue
Block a user