Compare commits
13 Commits
e1fcc70667
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
1eee28ccc8
|
|||
|
64e1e5481e
|
|||
|
97dfe31197
|
|||
| 6d9e637217 | |||
| acf9fd122f | |||
| 541e0e646f | |||
| a91508672e | |||
| 556222135c | |||
| 6564845183 | |||
| 3259ef0d53 | |||
| fc96e7a8ac | |||
| 494b2cdf52 | |||
| ce15260e3d |
@@ -20,3 +20,4 @@ logs.txt
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
/.direnv
|
||||
|
||||
Generated
+826
-1474
File diff suppressed because it is too large
Load Diff
+11
-2
@@ -1,11 +1,20 @@
|
||||
[package]
|
||||
name = "bevy_wayland"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.16.1"
|
||||
bevy = { version = "0.18.1", default-features = false, features = [
|
||||
"bevy_ui",
|
||||
"bevy_window",
|
||||
"trace",
|
||||
"bevy_winit",
|
||||
"wayland",
|
||||
"multi_threaded",
|
||||
] }
|
||||
|
||||
lazy_static = "1.5.0"
|
||||
raw-window-handle = "0.6.2"
|
||||
smithay-client-toolkit = "0.20.0"
|
||||
wayland-backend = { version = "0.3.11", features = ["client_system"] }
|
||||
wayland-protocols-wlr = "0.3.9"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,136 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
|
||||
use bevy_wayland::{prelude::*, ExternalEventDispatcher};
|
||||
|
||||
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>()
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resolution: WindowResolution::new(400.0, 400.0),
|
||||
present_mode: bevy::window::PresentMode::AutoVsync,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
WaylandPlugin,
|
||||
))
|
||||
.add_systems(Startup, (setup, external_tick_sender))
|
||||
.add_systems(Update, (button_system, exit_on_esc))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn external_tick_sender(external_event_dispatcher: Res<ExternalEventDispatcher>) {
|
||||
let displatcher = external_event_dispatcher.clone();
|
||||
let mut count = 5;
|
||||
std::thread::spawn(move || loop {
|
||||
println!("Spawned Thread");
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
displatcher.dispatch().unwrap();
|
||||
count -= 1;
|
||||
if count < 0 {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[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>,
|
||||
) {
|
||||
//info!("Button system was called!!");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
|
||||
for entity in &windows {
|
||||
commands.entity(entity).insert((LayerShellSettings {
|
||||
anchor: Anchor::TOP | Anchor::LEFT,
|
||||
layer: Layer::Bottom,
|
||||
..Default::default()
|
||||
},));
|
||||
}
|
||||
// ui camera
|
||||
commands.spawn(Camera2d);
|
||||
commands.spawn(button(&assets));
|
||||
}
|
||||
|
||||
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
|
||||
if keys.just_pressed(KeyCode::Escape) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
)]
|
||||
)],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
|
||||
use bevy_wayland::{foreign_toplevel_manager::ForeignToplevelEvent, prelude::*};
|
||||
|
||||
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>()
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resolution: WindowResolution::new(400.0, 400.0),
|
||||
present_mode: bevy::window::PresentMode::AutoVsync,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
WaylandPlugin,
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (button_system, exit_on_esc))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(
|
||||
&Interaction,
|
||||
&mut BackgroundColor,
|
||||
&mut BorderColor,
|
||||
&Children,
|
||||
),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut foreign_toplevel_event_writer: EventWriter<ForeignToplevelEvent>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
//info!("Button system was called!!");
|
||||
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();
|
||||
foreign_toplevel_event_writer.write(ForeignToplevelEvent::MinimizeOthers);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
|
||||
for entity in &windows {
|
||||
commands.entity(entity).insert((LayerShellSettings {
|
||||
anchor: Anchor::TOP | Anchor::LEFT,
|
||||
layer: Layer::Top,
|
||||
..Default::default()
|
||||
},));
|
||||
}
|
||||
// ui camera
|
||||
commands.spawn(Camera2d);
|
||||
commands.spawn(button(&assets));
|
||||
}
|
||||
|
||||
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
|
||||
if keys.just_pressed(KeyCode::Escape) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
)]
|
||||
)],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
window::{exit_on_all_closed, WindowRef},
|
||||
winit::WinitPlugin,
|
||||
};
|
||||
use bevy_wayland::prelude::*;
|
||||
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);
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins
|
||||
.build()
|
||||
.disable::<WinitPlugin>()
|
||||
.set(WindowPlugin {
|
||||
primary_window: None,
|
||||
..Default::default()
|
||||
}),
|
||||
WaylandPlugin,
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
button_system,
|
||||
exit_on_esc,
|
||||
setup_session_lock_windows,
|
||||
exit_on_all_closed,
|
||||
),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(
|
||||
&Interaction,
|
||||
&mut BackgroundColor,
|
||||
&mut BorderColor,
|
||||
&Children,
|
||||
),
|
||||
(Changed<Interaction>, With<UnlockButton>),
|
||||
>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
|
||||
) {
|
||||
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 => {
|
||||
session_lock_event_writer.write(SessionLockEvent::Unlock);
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
**text = "Click to unlock".to_string();
|
||||
*color = HOVERED_BUTTON.into();
|
||||
border_color.0 = Color::WHITE;
|
||||
}
|
||||
Interaction::None => {
|
||||
**text = "Click to unlock".to_string();
|
||||
*color = NORMAL_BUTTON.into();
|
||||
border_color.0 = Color::BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct LockButton;
|
||||
#[derive(Component)]
|
||||
struct UnlockButton;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
assets: Res<AssetServer>,
|
||||
windows: Query<Entity, With<Window>>,
|
||||
|
||||
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
|
||||
) {
|
||||
session_lock_event_writer.write(SessionLockEvent::Lock);
|
||||
for entity in &windows {
|
||||
commands.entity(entity).insert((LayerShellSettings {
|
||||
anchor: Anchor::TOP | Anchor::LEFT,
|
||||
layer: Layer::Bottom,
|
||||
..Default::default()
|
||||
},));
|
||||
}
|
||||
// ui camera
|
||||
commands.spawn(Camera2d);
|
||||
commands.spawn(lock_button(&assets));
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ConfiguredWindow;
|
||||
#[derive(Component)]
|
||||
struct SessionLockCamera;
|
||||
|
||||
fn setup_session_lock_windows(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
windows: Query<(Entity, &SessionLockWindow), Without<ConfiguredWindow>>,
|
||||
) {
|
||||
for (entity, _) in &windows {
|
||||
let camera = commands
|
||||
.spawn((
|
||||
Camera2d,
|
||||
Camera {
|
||||
target: bevy::render::camera::RenderTarget::Window(WindowRef::Entity(entity)),
|
||||
..Default::default()
|
||||
},
|
||||
SessionLockCamera,
|
||||
))
|
||||
.id();
|
||||
commands.entity(entity).insert(ConfiguredWindow);
|
||||
commands.spawn(unlock_button(&asset_server, camera));
|
||||
}
|
||||
}
|
||||
|
||||
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
|
||||
if keys.just_pressed(KeyCode::Escape) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_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,
|
||||
LockButton,
|
||||
Node {
|
||||
width: Val::Px(250.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(),
|
||||
)]
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
fn unlock_button(asset_server: &AssetServer, camera: Entity) -> impl Bundle + use<> {
|
||||
(
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
UiTargetCamera(camera),
|
||||
children![(
|
||||
Button,
|
||||
UnlockButton,
|
||||
Node {
|
||||
width: Val::Px(250.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(),
|
||||
)]
|
||||
)],
|
||||
)
|
||||
}
|
||||
@@ -6,8 +6,7 @@ use bevy::{
|
||||
window::{WindowCreated, WindowResolution},
|
||||
winit::WinitPlugin,
|
||||
};
|
||||
use bevy_wayland::{input_region::InputRegion, layer_shell::LayerShellSettings, WaylandPlugin};
|
||||
use smithay_client_toolkit::shell::wlr_layer::{Anchor, Layer};
|
||||
use bevy_wayland::prelude::*;
|
||||
|
||||
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
|
||||
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
|
||||
@@ -28,12 +27,8 @@ fn main() {
|
||||
}),
|
||||
WaylandPlugin,
|
||||
))
|
||||
.init_resource::<NewWindowInfo>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(button_system, spawn_window, setup_new_window, exit_on_esc),
|
||||
)
|
||||
.add_systems(Update, (button_system, exit_on_esc))
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -72,18 +67,13 @@ fn button_system(
|
||||
}
|
||||
}
|
||||
|
||||
#[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((
|
||||
LayerShellSettings {
|
||||
anchor: Anchor::TOP | Anchor::LEFT,
|
||||
layer: Layer::Bottom,
|
||||
..Default::default()
|
||||
},
|
||||
InputRegion(Rect::new(0., 0., 200., 200.)),
|
||||
));
|
||||
commands.entity(entity).insert((LayerShellSettings {
|
||||
anchor: Anchor::TOP | Anchor::LEFT,
|
||||
layer: Layer::Bottom,
|
||||
..Default::default()
|
||||
},));
|
||||
}
|
||||
// ui camera
|
||||
commands.spawn(Camera2d);
|
||||
@@ -96,69 +86,6 @@ fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_window(
|
||||
mut commands: Commands,
|
||||
mut windows: Query<(Entity, &mut WindowTimer)>,
|
||||
mut new_window_info: ResMut<NewWindowInfo>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if keys.pressed(KeyCode::KeyN) {
|
||||
println!("Pressed");
|
||||
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 {
|
||||
Generated
+94
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"nodes": {
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1779575509,
|
||||
"narHash": "sha256-wXKYURZz76ZC5lbuDA1oVQA/MxSB3pSJ1raF1HG0oIc=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "831c50f4a4304068f125e603add6a8839f08b3eb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1779130139,
|
||||
"narHash": "sha256-BLrtr42azquO7MdGFU5a7KiMl3YpFlTeIXqy1fT5GlQ=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "edb38893982a3338972bb4a2ec7ce7c29ba10fd9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1779536132,
|
||||
"narHash": "sha256-q+fF42iv/geEbHfgSzy3tS0FF/EyD6XTZ98E6yxiBO8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3d8f0f3f72a6cd4d93d0ad13203f2ea1cb7e1456",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"advisory-db": "advisory-db",
|
||||
"crane": "crane",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
{
|
||||
description = "Build a cargo project";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
crane.url = "github:ipetkov/crane";
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
advisory-db = {
|
||||
url = "github:rustsec/advisory-db";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
flake-utils,
|
||||
advisory-db,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
inherit (pkgs) lib;
|
||||
|
||||
craneLib = crane.mkLib pkgs;
|
||||
src = craneLib.cleanCargoSource ./.;
|
||||
|
||||
# Common arguments can be set here to avoid repeating them later
|
||||
commonArgs = {
|
||||
inherit src;
|
||||
strictDeps = true;
|
||||
|
||||
buildInputs = with pkgs;
|
||||
[
|
||||
mold
|
||||
# for Linux
|
||||
# Audio (Linux only)
|
||||
alsa-lib
|
||||
# Cross Platform 3D Graphics API
|
||||
vulkan-loader
|
||||
# For debugging around vulkan
|
||||
vulkan-tools
|
||||
# Other dependencies
|
||||
libudev-zero
|
||||
libx11
|
||||
libxcursor
|
||||
libxi
|
||||
libxrandr
|
||||
libxkbcommon
|
||||
wayland
|
||||
]
|
||||
++ lib.optionals pkgs.stdenv.isDarwin [
|
||||
# Additional darwin specific inputs can be set here
|
||||
pkgs.libiconv
|
||||
];
|
||||
|
||||
# Additional environment variables can be set directly
|
||||
# MY_CUSTOM_VAR = "some value";
|
||||
};
|
||||
|
||||
# Build *just* the cargo dependencies, so we can reuse
|
||||
# all of that work (e.g. via cachix) when running in CI
|
||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||
|
||||
# Build the actual crate itself, reusing the dependency
|
||||
# artifacts from above.
|
||||
my-crate = craneLib.buildPackage (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
}
|
||||
);
|
||||
in {
|
||||
checks = {
|
||||
# Build the crate as part of `nix flake check` for convenience
|
||||
inherit my-crate;
|
||||
|
||||
# Run clippy (and deny all warnings) on the crate source,
|
||||
# again, reusing the dependency artifacts from above.
|
||||
#
|
||||
# Note that this is done as a separate derivation so that
|
||||
# we can block the CI if there are issues here, but not
|
||||
# prevent downstream consumers from building our crate by itself.
|
||||
my-crate-clippy = craneLib.cargoClippy (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||
}
|
||||
);
|
||||
|
||||
my-crate-doc = craneLib.cargoDoc (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
# This can be commented out or tweaked as necessary, e.g. set to
|
||||
# `--deny rustdoc::broken-intra-doc-links` to only enforce that lint
|
||||
env.RUSTDOCFLAGS = "--deny warnings";
|
||||
}
|
||||
);
|
||||
|
||||
# Check formatting
|
||||
my-crate-fmt = craneLib.cargoFmt {
|
||||
inherit src;
|
||||
};
|
||||
|
||||
my-crate-toml-fmt = craneLib.taploFmt {
|
||||
src = pkgs.lib.sources.sourceFilesBySuffices src [".toml"];
|
||||
# taplo arguments can be further customized below as needed
|
||||
# taploExtraArgs = "--config ./taplo.toml";
|
||||
};
|
||||
|
||||
# Audit dependencies
|
||||
my-crate-audit = craneLib.cargoAudit {
|
||||
inherit src advisory-db;
|
||||
};
|
||||
|
||||
# Audit licenses
|
||||
my-crate-deny = craneLib.cargoDeny {
|
||||
inherit src;
|
||||
};
|
||||
|
||||
# Run tests with cargo-nextest
|
||||
# Consider setting `doCheck = false` on `my-crate` if you do not want
|
||||
# the tests to run twice
|
||||
my-crate-nextest = craneLib.cargoNextest (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
partitions = 1;
|
||||
partitionType = "count";
|
||||
cargoNextestPartitionsExtraArgs = "--no-tests=pass";
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
packages = {
|
||||
default = my-crate;
|
||||
};
|
||||
|
||||
apps.default = flake-utils.lib.mkApp {
|
||||
drv = my-crate;
|
||||
};
|
||||
|
||||
devShells.default = craneLib.devShell {
|
||||
# Inherit inputs from checks.
|
||||
checks = self.checks.${system};
|
||||
|
||||
# Additional dev-shell environment variables can be set directly
|
||||
# MY_CUSTOM_DEVELOPMENT_VAR = "something else";
|
||||
|
||||
# Extra inputs can be added here; cargo and rustc are provided by default.
|
||||
packages = with pkgs; [
|
||||
pkgs.clang
|
||||
mold
|
||||
# for Linux
|
||||
# Audio (Linux only)
|
||||
alsa-lib
|
||||
# Cross Platform 3D Graphics API
|
||||
vulkan-loader
|
||||
# For debugging around vulkan
|
||||
vulkan-tools
|
||||
# Other dependencies
|
||||
libudev-zero
|
||||
libx11
|
||||
libxcursor
|
||||
libxi
|
||||
libxrandr
|
||||
libxkbcommon
|
||||
wayland
|
||||
pkg-config
|
||||
];
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||
pkgs.vulkan-loader
|
||||
pkgs.libx11
|
||||
pkgs.libxi
|
||||
pkgs.libxcursor
|
||||
pkgs.libxkbcommon
|
||||
pkgs.wayland
|
||||
];
|
||||
# PKG_CONFIG_PATH = "${pkgs.wayland}/lib/pkgconfig:${pkgs.alsa-lib}/lib";
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use bevy::prelude::*;
|
||||
use smithay_client_toolkit::{
|
||||
reexports::client::{Dispatch, QueueHandle, event_created_child},
|
||||
registry::RegistryState,
|
||||
};
|
||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||
zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},
|
||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||
};
|
||||
|
||||
use crate::WaylandState;
|
||||
#[derive(Debug, Copy, Clone, Message)]
|
||||
pub enum ForeignToplevelEvent {
|
||||
MinimizeOthers,
|
||||
}
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
struct ForeignToplevels(Vec<ZwlrForeignToplevelHandleV1>);
|
||||
|
||||
pub struct ForeignToplevelManagerPlugin;
|
||||
impl Plugin for ForeignToplevelManagerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let registry_state = app.world().non_send_resource::<RegistryState>();
|
||||
let queue_handle = app.world().non_send_resource::<QueueHandle<WaylandState>>();
|
||||
let foreign_top_level_manager =
|
||||
registry_state.bind_one::<ZwlrForeignToplevelManagerV1, _, _>(queue_handle, 2..=3, ());
|
||||
if let Ok(foreign_top_level_manager) = foreign_top_level_manager {
|
||||
info!("Foreign toplevel manager was bound!");
|
||||
app.insert_non_send_resource(foreign_top_level_manager);
|
||||
app.insert_non_send_resource(ForeignToplevels::default());
|
||||
app.add_message::<ForeignToplevelEvent>();
|
||||
app.add_systems(Update, foreign_top_level_event_handler);
|
||||
} else {
|
||||
let bind_error = foreign_top_level_manager.err().unwrap();
|
||||
error!("Couldn't bind foreign toplevel manager! {:?}", bind_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn foreign_top_level_event_handler(
|
||||
foreign_top_levels: NonSendMut<ForeignToplevels>,
|
||||
mut events: MessageReader<ForeignToplevelEvent>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
match event {
|
||||
ForeignToplevelEvent::MinimizeOthers => {
|
||||
info!("Minimizing other windows");
|
||||
for toplevel in foreign_top_levels.iter() {
|
||||
toplevel.set_minimized();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrForeignToplevelManagerV1, ()> for WaylandState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &ZwlrForeignToplevelManagerV1,
|
||||
event: <ZwlrForeignToplevelManagerV1 as smithay_client_toolkit::reexports::client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
let mut foreign_toplevels = state
|
||||
.world_mut()
|
||||
.non_send_resource_mut::<ForeignToplevels>();
|
||||
match event {
|
||||
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::Event::Toplevel { toplevel } => {
|
||||
foreign_toplevels.push(toplevel);
|
||||
},
|
||||
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::Event::Finished => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(WaylandState, ZwlrForeignToplevelManagerV1, [
|
||||
// Opcode 0 is the `toplevel` event. It creates a new `zwlr_foreign_toplevel_handle_v1`.
|
||||
zwlr_foreign_toplevel_manager_v1::EVT_TOPLEVEL_OPCODE => (
|
||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||
()
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for WaylandState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrForeignToplevelHandleV1,
|
||||
_event: <ZwlrForeignToplevelHandleV1 as smithay_client_toolkit::reexports::client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -409,9 +409,9 @@ impl KeyboardHandler for WaylandState {
|
||||
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(keyboard_event.clone());
|
||||
self.world_mut().write_message(keyboard_event.clone());
|
||||
self.world_mut()
|
||||
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||
.write_message(WindowEvent::KeyboardInput(keyboard_event));
|
||||
}
|
||||
|
||||
fn repeat_key(
|
||||
@@ -426,9 +426,9 @@ impl KeyboardHandler for WaylandState {
|
||||
let mut keyboard_event =
|
||||
convert_keyboard_event(event, active_window_entity, ButtonState::Pressed);
|
||||
keyboard_event.repeat = true;
|
||||
self.world_mut().send_event(keyboard_event.clone());
|
||||
self.world_mut().write_message(keyboard_event.clone());
|
||||
self.world_mut()
|
||||
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||
.write_message(WindowEvent::KeyboardInput(keyboard_event));
|
||||
}
|
||||
|
||||
fn release_key(
|
||||
@@ -442,9 +442,9 @@ impl KeyboardHandler for WaylandState {
|
||||
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(keyboard_event.clone());
|
||||
self.world_mut().write_message(keyboard_event.clone());
|
||||
self.world_mut()
|
||||
.send_event(WindowEvent::KeyboardInput(keyboard_event));
|
||||
.write_message(WindowEvent::KeyboardInput(keyboard_event));
|
||||
}
|
||||
|
||||
fn update_modifiers(
|
||||
|
||||
@@ -101,26 +101,26 @@ impl PointerHandler for WaylandState {
|
||||
let window_event: WindowEvent = pointer_event;
|
||||
match window_event.clone() {
|
||||
WindowEvent::CursorEntered(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
WindowEvent::CursorLeft(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
WindowEvent::CursorMoved(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
WindowEvent::MouseButtonInput(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
WindowEvent::MouseMotion(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
WindowEvent::MouseWheel(e) => {
|
||||
self.world_mut().send_event(e);
|
||||
self.world_mut().write_message(e);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.world_mut().send_event::<WindowEvent>(window_event);
|
||||
self.world_mut().write_message::<WindowEvent>(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -1,10 +1,7 @@
|
||||
use bevy::{core_pipeline::core_2d::graph::input, prelude::*};
|
||||
use smithay_client_toolkit::{
|
||||
compositor::{CompositorState, Region},
|
||||
reexports::client::{protocol::wl_compositor::WlCompositor, QueueHandle},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use smithay_client_toolkit::compositor::{CompositorState, Region};
|
||||
|
||||
use crate::{input_region, surface_handler::WaylandSurfaces, WaylandState};
|
||||
use crate::surface_handler::WaylandSurfaces;
|
||||
|
||||
#[derive(Component, Deref)]
|
||||
pub struct InputRegion(pub Rect);
|
||||
|
||||
+37
-11
@@ -1,7 +1,7 @@
|
||||
use bevy::{platform::collections::HashMap, prelude::*, ui::update};
|
||||
use bevy::{platform::collections::HashMap, prelude::*};
|
||||
use smithay_client_toolkit::{
|
||||
delegate_layer,
|
||||
reexports::client::{globals::GlobalList, Connection, Proxy, QueueHandle},
|
||||
reexports::client::{globals::GlobalList, QueueHandle},
|
||||
shell::{
|
||||
wlr_layer::{
|
||||
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
|
||||
@@ -11,7 +11,7 @@ use smithay_client_toolkit::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
|
||||
surface_handler::{SurfaceConfigured, SurfaceHandlerSystems, WaylandSurfaces},
|
||||
WaylandState,
|
||||
};
|
||||
|
||||
@@ -21,12 +21,18 @@ struct LayerShellWindows(HashMap<Entity, LayerShellWindow>);
|
||||
struct LayerShellWindow {
|
||||
layer_surface: LayerSurface,
|
||||
layer_shell_settings: LayerShellSettings,
|
||||
window_size: (u32, u32),
|
||||
}
|
||||
impl LayerShellWindow {
|
||||
fn new(layer_surface: LayerSurface, layer_shell_settings: LayerShellSettings) -> Self {
|
||||
fn new(
|
||||
layer_surface: LayerSurface,
|
||||
layer_shell_settings: LayerShellSettings,
|
||||
window_size: (u32, u32),
|
||||
) -> Self {
|
||||
let mut layer_shell_window = Self {
|
||||
layer_surface,
|
||||
layer_shell_settings,
|
||||
window_size,
|
||||
};
|
||||
layer_shell_window.sync();
|
||||
layer_shell_window
|
||||
@@ -42,8 +48,12 @@ impl LayerShellWindow {
|
||||
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(400, 400);
|
||||
if let LayerShellWindowSize::Fixed(width, height) = self.layer_shell_settings.size {
|
||||
self.layer_surface.set_size(width, height);
|
||||
} else {
|
||||
self.layer_surface
|
||||
.set_size(self.window_size.0, self.window_size.1);
|
||||
}
|
||||
|
||||
let (top, right, bottom, left) = self.layer_shell_settings.margin;
|
||||
self.layer_surface.set_margin(top, right, bottom, left);
|
||||
@@ -59,6 +69,13 @@ impl LayerShellWindow {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Clone, Debug)]
|
||||
pub enum LayerShellWindowSize {
|
||||
#[default]
|
||||
Inherit,
|
||||
Fixed(u32, u32),
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LayerShellSettings {
|
||||
/// Defines where the layer surface should be anchored to the screen.
|
||||
@@ -66,7 +83,7 @@ pub struct LayerShellSettings {
|
||||
/// 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),
|
||||
pub size: LayerShellWindowSize,
|
||||
/// 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
|
||||
@@ -104,7 +121,10 @@ impl Default for LayerShellSettings {
|
||||
pub struct LayerShellPlugin;
|
||||
impl Plugin for LayerShellPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PreUpdate, assign_layer_shell_role.after(create_windows))
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
assign_layer_shell_role.after(SurfaceHandlerSystems::CreateWindows),
|
||||
)
|
||||
.add_systems(Update, update_layer_shell_settings)
|
||||
.insert_non_send_resource(LayerShellWindows::default());
|
||||
}
|
||||
@@ -118,7 +138,7 @@ fn assign_layer_shell_role(
|
||||
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
|
||||
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
|
||||
) {
|
||||
for (entity, _window, layer_shell_settings) in &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!")
|
||||
@@ -136,7 +156,11 @@ fn assign_layer_shell_role(
|
||||
|
||||
let _ = layer_shell_windows.insert(
|
||||
entity,
|
||||
LayerShellWindow::new(layer, layer_shell_settings.clone()),
|
||||
LayerShellWindow::new(
|
||||
layer,
|
||||
layer_shell_settings.clone(),
|
||||
(window.width() as u32, window.height() as u32),
|
||||
),
|
||||
);
|
||||
|
||||
commands.entity(entity).insert(SurfaceConfigured);
|
||||
@@ -147,8 +171,10 @@ 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 {
|
||||
for (entity, window, layer_shell_settings) in &windows {
|
||||
let layer_shell_window = layer_shell_windows.get_mut(&entity).unwrap();
|
||||
let window_size = (window.width() as u32, window.height() as u32);
|
||||
layer_shell_window.window_size = window_size;
|
||||
layer_shell_window.set_settings(layer_shell_settings.clone());
|
||||
}
|
||||
}
|
||||
|
||||
+54
-10
@@ -1,40 +1,77 @@
|
||||
use std::{
|
||||
sync::mpsc::SendError,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use bevy::{app::PluginsState, prelude::*};
|
||||
use smithay_client_toolkit::{
|
||||
delegate_registry,
|
||||
output::OutputState,
|
||||
reexports::{
|
||||
calloop::EventLoop,
|
||||
calloop::{self, EventLoop, channel::Sender},
|
||||
calloop_wayland_source::WaylandSource,
|
||||
client::{globals::registry_queue_init, Connection},
|
||||
client::{Connection, globals::registry_queue_init},
|
||||
},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
registry_handlers,
|
||||
seat::SeatState,
|
||||
};
|
||||
|
||||
pub mod foreign_toplevel_manager;
|
||||
mod input_handler;
|
||||
pub mod input_region;
|
||||
pub mod layer_shell;
|
||||
mod output_handler;
|
||||
pub mod session_lock;
|
||||
mod surface_handler;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::WaylandPlugin;
|
||||
pub use crate::input_region::InputRegion;
|
||||
pub use crate::layer_shell::{LayerShellSettings, LayerShellWindowSize};
|
||||
pub use smithay_client_toolkit::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
|
||||
}
|
||||
|
||||
pub struct Tick;
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct ExternalEventDispatcher(Sender<Tick>);
|
||||
impl ExternalEventDispatcher {
|
||||
fn new(tx: Sender<Tick>) -> Self {
|
||||
Self(tx)
|
||||
}
|
||||
|
||||
pub fn dispatch(&self) -> Result<(), SendError<Tick>> {
|
||||
self.0.send(Tick)
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct WaylandPlugin;
|
||||
impl Plugin for WaylandPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let connection =
|
||||
Connection::connect_to_env().expect("failed to connect to wayland socket!");
|
||||
Connection::connect_to_env().expect("Failed to connect to wayland socket!");
|
||||
let event_loop =
|
||||
EventLoop::<WaylandState>::try_new().expect("failed to create event_loop!");
|
||||
EventLoop::<WaylandState>::try_new().expect("Failed to create event_loop!");
|
||||
let (globals, event_queue) = registry_queue_init::<WaylandState>(&connection)
|
||||
.expect("failed to init registry queue");
|
||||
.expect("Failed to init registry queue");
|
||||
|
||||
let qh = event_queue.handle();
|
||||
let loop_handle = event_loop.handle();
|
||||
WaylandSource::new(connection.clone(), event_queue)
|
||||
.insert(loop_handle.clone())
|
||||
.expect("failed to insert wayland source to event loop");
|
||||
.expect("Failed to insert wayland source to event loop");
|
||||
|
||||
let (tx, rx) = calloop::channel::channel::<Tick>();
|
||||
loop_handle
|
||||
.insert_source(rx, |_, _, state| {
|
||||
info!("External event was received!");
|
||||
if state.plugins_state() == PluginsState::Cleaned {
|
||||
state.update();
|
||||
}
|
||||
})
|
||||
.expect("Failed to insert external tick channel!");
|
||||
|
||||
app.insert_resource(ExternalEventDispatcher::new(tx));
|
||||
app.insert_non_send_resource(RegistryState::new(&globals));
|
||||
app.insert_non_send_resource(connection.clone());
|
||||
app.insert_non_send_resource(globals);
|
||||
@@ -42,10 +79,12 @@ impl Plugin for WaylandPlugin {
|
||||
|
||||
app.add_plugins((
|
||||
output_handler::OutputHandlerPlugin,
|
||||
input_handler::InputHandlerPlugin,
|
||||
surface_handler::SurfaceHandlerPlugin,
|
||||
input_handler::InputHandlerPlugin,
|
||||
layer_shell::LayerShellPlugin,
|
||||
session_lock::SessionLockPlugin,
|
||||
input_region::InputRegionPlugin,
|
||||
foreign_toplevel_manager::ForeignToplevelManagerPlugin,
|
||||
));
|
||||
app.set_runner(|app| runner(app, event_loop));
|
||||
}
|
||||
@@ -56,14 +95,19 @@ pub fn runner(mut app: App, mut event_loop: EventLoop<'_, WaylandState>) -> AppE
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
}
|
||||
|
||||
let mut state = WaylandState(app);
|
||||
loop {
|
||||
// TODO: Error handling
|
||||
let frame_start = Instant::now();
|
||||
let _ = event_loop.dispatch(Duration::from_millis(5000), &mut state);
|
||||
if state.plugins_state() == PluginsState::Cleaned {
|
||||
state.update();
|
||||
}
|
||||
let _ = event_loop.dispatch(None, &mut state);
|
||||
let _ = event_loop.dispatch(Duration::from_millis(0), &mut state);
|
||||
// TODO: Poll until delta time is greater than target frame time.
|
||||
if Instant::now() - frame_start < Duration::from_millis(16) {
|
||||
std::thread::sleep(Duration::from_millis(16) - (frame_start - Instant::now()));
|
||||
}
|
||||
let _ = event_loop.dispatch(Duration::from_millis(0), &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
use bevy::{platform::collections::HashMap, prelude::*};
|
||||
use smithay_client_toolkit::{
|
||||
delegate_session_lock,
|
||||
output::OutputState,
|
||||
reexports::client::{globals::GlobalList, protocol::wl_output::WlOutput, QueueHandle},
|
||||
session_lock::{SessionLock, SessionLockHandler, SessionLockState, SessionLockSurface},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
surface_handler::{SurfaceConfigured, SurfaceHandlerSystems, WaylandSurfaces},
|
||||
WaylandState,
|
||||
};
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
struct SessionLockWindows(HashMap<Entity, SessionLockWindowInternal>);
|
||||
struct SessionLockWindowInternal {
|
||||
_session_lock_surface: SessionLockSurface,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SessionLockWindow;
|
||||
|
||||
#[derive(Component)]
|
||||
struct SessionLockUnconfiguredWindow {
|
||||
output: WlOutput,
|
||||
}
|
||||
impl SessionLockUnconfiguredWindow {
|
||||
pub fn new(output: WlOutput) -> Self {
|
||||
Self { output }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Message)]
|
||||
pub enum SessionLockEvent {
|
||||
Lock,
|
||||
Unlock,
|
||||
}
|
||||
|
||||
pub struct SessionLockPlugin;
|
||||
impl Plugin for SessionLockPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let globals = app.world().non_send_resource::<GlobalList>();
|
||||
let queue_handle = app.world().non_send_resource::<QueueHandle<WaylandState>>();
|
||||
let session_lock_state = SessionLockState::new(globals, queue_handle);
|
||||
|
||||
app.insert_non_send_resource(session_lock_state);
|
||||
app.insert_non_send_resource(SessionLockWindows::default());
|
||||
app.insert_non_send_resource(SessionLockWrapper::default());
|
||||
app.add_message::<SessionLockEvent>();
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
(
|
||||
session_lock_event_handler.before(SurfaceHandlerSystems::CreateWindows),
|
||||
configure_lock_surfaces.after(SurfaceHandlerSystems::CreateWindows),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Default)]
|
||||
struct SessionLockWrapper(Option<SessionLock>);
|
||||
fn session_lock_event_handler(
|
||||
mut commands: Commands,
|
||||
mut session_lock_event_reader: MessageReader<SessionLockEvent>,
|
||||
session_lock_state: NonSend<SessionLockState>,
|
||||
mut session_lock_wrapper: NonSendMut<SessionLockWrapper>,
|
||||
queue_handle: NonSend<QueueHandle<WaylandState>>,
|
||||
output_state: NonSend<OutputState>,
|
||||
) {
|
||||
for session_lock_event in session_lock_event_reader.read() {
|
||||
match session_lock_event {
|
||||
SessionLockEvent::Lock => {
|
||||
if session_lock_wrapper.is_some() {
|
||||
error!("Lock was called even if it was already aquired");
|
||||
return;
|
||||
}
|
||||
let session_lock = session_lock_state
|
||||
.lock(&queue_handle)
|
||||
.expect("Unable to aquire session lock");
|
||||
let _ = session_lock_wrapper.insert(session_lock);
|
||||
|
||||
for output in output_state.outputs() {
|
||||
commands.spawn((
|
||||
Window::default(),
|
||||
SessionLockUnconfiguredWindow::new(output),
|
||||
));
|
||||
}
|
||||
}
|
||||
SessionLockEvent::Unlock => {
|
||||
if let Some(session_lock) = &**session_lock_wrapper {
|
||||
session_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_lock_surfaces(
|
||||
mut commands: Commands,
|
||||
mut session_lock_windows: NonSendMut<SessionLockWindows>,
|
||||
session_lock_wrapper: NonSend<SessionLockWrapper>,
|
||||
wayland_surfaces: NonSend<WaylandSurfaces>,
|
||||
qh: NonSend<QueueHandle<WaylandState>>,
|
||||
unconfigured_windows: Query<(Entity, &SessionLockUnconfiguredWindow)>,
|
||||
) {
|
||||
if let Some(session_lock) = &**session_lock_wrapper {
|
||||
for (entity, unconfigured_window) in &unconfigured_windows {
|
||||
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
|
||||
let surface = window_wrapper
|
||||
.expect("tried to assign role before creating surface!")
|
||||
.wl_surface();
|
||||
let _session_lock_surface =
|
||||
session_lock.create_lock_surface(surface.clone(), &unconfigured_window.output, &qh);
|
||||
|
||||
let session_lock_window = SessionLockWindowInternal {
|
||||
_session_lock_surface,
|
||||
};
|
||||
|
||||
session_lock_windows.insert(entity, session_lock_window);
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(SurfaceConfigured)
|
||||
.insert(SessionLockWindow)
|
||||
.remove::<SessionLockUnconfiguredWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionLockHandler for WaylandState {
|
||||
fn locked(
|
||||
&mut self,
|
||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||
_session_lock: smithay_client_toolkit::session_lock::SessionLock,
|
||||
) {
|
||||
}
|
||||
|
||||
fn finished(
|
||||
&mut self,
|
||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||
_session_lock: smithay_client_toolkit::session_lock::SessionLock,
|
||||
) {
|
||||
}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_conn: &smithay_client_toolkit::reexports::client::Connection,
|
||||
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
|
||||
_surface: smithay_client_toolkit::session_lock::SessionLockSurface,
|
||||
_configure: smithay_client_toolkit::session_lock::SessionLockSurfaceConfigure,
|
||||
_serial: u32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
delegate_session_lock!(WaylandState);
|
||||
+12
-3
@@ -13,7 +13,7 @@ use smithay_client_toolkit::{
|
||||
compositor::{CompositorHandler, CompositorState},
|
||||
delegate_compositor,
|
||||
reexports::client::{
|
||||
backend::ObjectId, protocol::wl_surface::WlSurface, Connection, Proxy, QueueHandle,
|
||||
Connection, Proxy, QueueHandle, backend::ObjectId, protocol::wl_surface::WlSurface,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,6 +21,12 @@ use crate::WaylandState;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SurfaceConfigured;
|
||||
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SurfaceHandlerSystems {
|
||||
CreateWindows,
|
||||
}
|
||||
|
||||
pub struct SurfaceHandlerPlugin;
|
||||
impl Plugin for SurfaceHandlerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
@@ -30,7 +36,10 @@ impl Plugin for SurfaceHandlerPlugin {
|
||||
CompositorState::bind(globals, queue_handle).expect("failed to bind compositor!"),
|
||||
);
|
||||
app.insert_non_send_resource(WaylandSurfaces::default());
|
||||
app.add_systems(PreUpdate, create_windows);
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
create_windows.in_set(SurfaceHandlerSystems::CreateWindows),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,7 @@ pub fn create_windows(
|
||||
connection: NonSend<Connection>,
|
||||
queue_handle: NonSend<QueueHandle<WaylandState>>,
|
||||
bevy_windows: Query<(Entity, Option<&RawHandleWrapperHolder>), With<Window>>,
|
||||
mut window_created_event: EventWriter<WindowCreated>,
|
||||
mut window_created_event: MessageWriter<WindowCreated>,
|
||||
) {
|
||||
for (entity, handle_holder) in &bevy_windows {
|
||||
if wayland_surfaces.get_window_wrapper(entity).is_some() {
|
||||
|
||||
Reference in New Issue
Block a user