mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-10-28 14:14:23 +00:00
feat: add initial popup support
This commit is contained in:
parent
87fc46750c
commit
4ea4171eb7
11 changed files with 682 additions and 53 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod egl_context;
|
pub mod egl_context;
|
||||||
pub mod femtovg_window;
|
pub mod femtovg_window;
|
||||||
|
pub mod popup_window;
|
||||||
pub mod slint_platform;
|
pub mod slint_platform;
|
||||||
|
|
|
||||||
97
src/rendering/popup_window.rs
Normal file
97
src/rendering/popup_window.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::errors::{LayerShikaError, Result};
|
||||||
|
use core::ops::Deref;
|
||||||
|
use log::info;
|
||||||
|
use slint::{
|
||||||
|
platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent},
|
||||||
|
PhysicalSize, Window, WindowSize,
|
||||||
|
};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
|
use super::femtovg_window::RenderState;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct PopupWindow {
|
||||||
|
window: Window,
|
||||||
|
renderer: FemtoVGRenderer,
|
||||||
|
render_state: Cell<RenderState>,
|
||||||
|
size: Cell<PhysicalSize>,
|
||||||
|
scale_factor: Cell<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl PopupWindow {
|
||||||
|
pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
|
||||||
|
Rc::new_cyclic(|weak_self| {
|
||||||
|
let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
renderer,
|
||||||
|
render_state: Cell::new(RenderState::Clean),
|
||||||
|
size: Cell::new(PhysicalSize::default()),
|
||||||
|
scale_factor: Cell::new(1.),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_frame_if_dirty(&self) -> Result<()> {
|
||||||
|
if matches!(
|
||||||
|
self.render_state.replace(RenderState::Clean),
|
||||||
|
RenderState::Dirty
|
||||||
|
) {
|
||||||
|
info!(
|
||||||
|
"Rendering popup frame (size: {:?}, scale: {})",
|
||||||
|
self.size.get(),
|
||||||
|
self.scale_factor.get()
|
||||||
|
);
|
||||||
|
self.renderer.render().map_err(|e| {
|
||||||
|
LayerShikaError::Rendering(format!("Error rendering popup frame: {e}"))
|
||||||
|
})?;
|
||||||
|
info!("Popup frame rendered successfully");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scale_factor(&self, scale_factor: f32) {
|
||||||
|
info!("Setting popup scale factor to {scale_factor}");
|
||||||
|
self.scale_factor.set(scale_factor);
|
||||||
|
self.window()
|
||||||
|
.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn scale_factor(&self) -> f32 {
|
||||||
|
self.scale_factor.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowAdapter for PopupWindow {
|
||||||
|
fn window(&self) -> &Window {
|
||||||
|
&self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderer(&self) -> &dyn Renderer {
|
||||||
|
&self.renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_size(&self, size: WindowSize) {
|
||||||
|
self.size.set(size.to_physical(self.scale_factor()));
|
||||||
|
self.window.dispatch_event(WindowEvent::Resized {
|
||||||
|
size: size.to_logical(self.scale_factor()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_redraw(&self) {
|
||||||
|
self.render_state.set(RenderState::Dirty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for PopupWindow {
|
||||||
|
type Target = Window;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,27 +2,49 @@ use slint::{
|
||||||
platform::{Platform, WindowAdapter},
|
platform::{Platform, WindowAdapter},
|
||||||
PlatformError,
|
PlatformError,
|
||||||
};
|
};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
use super::femtovg_window::FemtoVGWindow;
|
use super::femtovg_window::FemtoVGWindow;
|
||||||
|
|
||||||
|
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
|
||||||
|
|
||||||
pub struct CustomSlintPlatform {
|
pub struct CustomSlintPlatform {
|
||||||
window: Weak<FemtoVGWindow>,
|
main_window: Weak<FemtoVGWindow>,
|
||||||
|
popup_creator: RefCell<Option<Rc<PopupCreator>>>,
|
||||||
|
first_call: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomSlintPlatform {
|
impl CustomSlintPlatform {
|
||||||
pub fn new(window: &Rc<FemtoVGWindow>) -> Self {
|
pub fn new(window: &Rc<FemtoVGWindow>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
window: Rc::downgrade(window),
|
main_window: Rc::downgrade(window),
|
||||||
|
popup_creator: RefCell::new(None),
|
||||||
|
first_call: Cell::new(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_popup_creator<F>(&self, creator: F)
|
||||||
|
where
|
||||||
|
F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static,
|
||||||
|
{
|
||||||
|
*self.popup_creator.borrow_mut() = Some(Rc::new(creator));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for CustomSlintPlatform {
|
impl Platform for CustomSlintPlatform {
|
||||||
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter + 'static>, PlatformError> {
|
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter + 'static>, PlatformError> {
|
||||||
self.window
|
if self.first_call.get() {
|
||||||
.upgrade()
|
self.first_call.set(false);
|
||||||
.ok_or(PlatformError::NoPlatform)
|
self.main_window
|
||||||
.map(|w| w as Rc<dyn WindowAdapter>)
|
.upgrade()
|
||||||
|
.ok_or(PlatformError::NoPlatform)
|
||||||
|
.map(|w| w as Rc<dyn WindowAdapter>)
|
||||||
|
} else if let Some(creator) = self.popup_creator.borrow().as_ref() {
|
||||||
|
creator()
|
||||||
|
} else {
|
||||||
|
Err(PlatformError::NoPlatform)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use wayland_client::{
|
||||||
};
|
};
|
||||||
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||||
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
||||||
|
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
|
||||||
|
|
||||||
use super::state::WindowState;
|
use super::state::WindowState;
|
||||||
|
|
||||||
|
|
@ -16,6 +17,8 @@ pub struct GlobalCtx {
|
||||||
pub output: WlOutput,
|
pub output: WlOutput,
|
||||||
pub layer_shell: ZwlrLayerShellV1,
|
pub layer_shell: ZwlrLayerShellV1,
|
||||||
pub seat: WlSeat,
|
pub seat: WlSeat,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub xdg_wm_base: Option<XdgWmBase>,
|
||||||
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
pub viewporter: Option<WpViewporter>,
|
pub viewporter: Option<WpViewporter>,
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +41,10 @@ impl GlobalCtx {
|
||||||
(WlSeat, seat, 1..=9)
|
(WlSeat, seat, 1..=9)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let xdg_wm_base = global_list
|
||||||
|
.bind::<XdgWmBase, _, _>(queue_handle, 1..=6, ())
|
||||||
|
.ok();
|
||||||
|
|
||||||
let fractional_scale_manager = global_list
|
let fractional_scale_manager = global_list
|
||||||
.bind::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
|
.bind::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -46,6 +53,10 @@ impl GlobalCtx {
|
||||||
.bind::<WpViewporter, _, _>(queue_handle, 1..=1, ())
|
.bind::<WpViewporter, _, _>(queue_handle, 1..=1, ())
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
if xdg_wm_base.is_none() {
|
||||||
|
info!("xdg-shell protocol not available, popup support disabled");
|
||||||
|
}
|
||||||
|
|
||||||
if fractional_scale_manager.is_none() {
|
if fractional_scale_manager.is_none() {
|
||||||
info!("Fractional scale protocol not available, using integer scaling");
|
info!("Fractional scale protocol not available, using integer scaling");
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +70,7 @@ impl GlobalCtx {
|
||||||
output,
|
output,
|
||||||
layer_shell,
|
layer_shell,
|
||||||
seat,
|
seat,
|
||||||
|
xdg_wm_base,
|
||||||
fractional_scale_manager,
|
fractional_scale_manager,
|
||||||
viewporter,
|
viewporter,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ pub mod builder;
|
||||||
mod config;
|
mod config;
|
||||||
mod globals;
|
mod globals;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
mod popup;
|
||||||
|
mod popup_manager;
|
||||||
mod proxies;
|
mod proxies;
|
||||||
mod state;
|
mod state;
|
||||||
mod surface;
|
mod surface;
|
||||||
|
|
|
||||||
119
src/windowing/popup.rs
Normal file
119
src/windowing/popup.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
use log::info;
|
||||||
|
use slint::{LogicalPosition, PhysicalSize};
|
||||||
|
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wayland_client::{
|
||||||
|
protocol::{wl_compositor::WlCompositor, wl_seat::WlSeat, wl_surface::WlSurface},
|
||||||
|
QueueHandle,
|
||||||
|
};
|
||||||
|
use wayland_protocols::wp::fractional_scale::v1::client::{
|
||||||
|
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
|
||||||
|
wp_fractional_scale_v1::WpFractionalScaleV1,
|
||||||
|
};
|
||||||
|
use wayland_protocols::wp::viewporter::client::{
|
||||||
|
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
|
||||||
|
};
|
||||||
|
use wayland_protocols::xdg::shell::client::{
|
||||||
|
xdg_popup::XdgPopup,
|
||||||
|
xdg_positioner::{Anchor, ConstraintAdjustment, Gravity, XdgPositioner},
|
||||||
|
xdg_surface::XdgSurface,
|
||||||
|
xdg_wm_base::XdgWmBase,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::state::WindowState;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct PopupSurfaceParams<'a> {
|
||||||
|
pub compositor: &'a WlCompositor,
|
||||||
|
pub xdg_wm_base: &'a XdgWmBase,
|
||||||
|
pub parent_layer_surface: &'a ZwlrLayerSurfaceV1,
|
||||||
|
pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>,
|
||||||
|
pub viewporter: Option<&'a WpViewporter>,
|
||||||
|
pub queue_handle: &'a QueueHandle<WindowState>,
|
||||||
|
pub position: LogicalPosition,
|
||||||
|
pub size: PhysicalSize,
|
||||||
|
pub scale_factor: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct PopupSurface {
|
||||||
|
pub surface: Rc<WlSurface>,
|
||||||
|
pub xdg_surface: Rc<XdgSurface>,
|
||||||
|
pub xdg_popup: Rc<XdgPopup>,
|
||||||
|
pub fractional_scale: Option<Rc<WpFractionalScaleV1>>,
|
||||||
|
pub viewport: Option<Rc<WpViewport>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl PopupSurface {
|
||||||
|
pub fn create(params: &PopupSurfaceParams<'_>) -> Self {
|
||||||
|
let surface = Rc::new(params.compositor.create_surface(params.queue_handle, ()));
|
||||||
|
|
||||||
|
let xdg_surface = Rc::new(params.xdg_wm_base.get_xdg_surface(
|
||||||
|
&surface,
|
||||||
|
params.queue_handle,
|
||||||
|
(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let positioner = Self::create_positioner(params);
|
||||||
|
|
||||||
|
let xdg_popup = Rc::new(xdg_surface.get_popup(None, &positioner, params.queue_handle, ()));
|
||||||
|
|
||||||
|
info!("Attaching popup to layer surface via get_popup");
|
||||||
|
params.parent_layer_surface.get_popup(&xdg_popup);
|
||||||
|
|
||||||
|
let fractional_scale = params.fractional_scale_manager.map(|manager| {
|
||||||
|
info!("Creating fractional scale object for popup surface");
|
||||||
|
Rc::new(manager.get_fractional_scale(&surface, params.queue_handle, ()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let viewport = params.viewporter.map(|vp| {
|
||||||
|
info!("Creating viewport for popup surface");
|
||||||
|
Rc::new(vp.get_viewport(&surface, params.queue_handle, ()))
|
||||||
|
});
|
||||||
|
|
||||||
|
surface.set_buffer_scale(1);
|
||||||
|
surface.commit();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
surface,
|
||||||
|
xdg_surface,
|
||||||
|
xdg_popup,
|
||||||
|
fractional_scale,
|
||||||
|
viewport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
|
fn create_positioner(params: &PopupSurfaceParams<'_>) -> XdgPositioner {
|
||||||
|
let positioner = params
|
||||||
|
.xdg_wm_base
|
||||||
|
.create_positioner(params.queue_handle, ());
|
||||||
|
|
||||||
|
let x = (params.position.x * params.scale_factor) as i32;
|
||||||
|
let y = (params.position.y * params.scale_factor) as i32;
|
||||||
|
let width = params.size.width as i32;
|
||||||
|
let height = params.size.height as i32;
|
||||||
|
|
||||||
|
positioner.set_anchor_rect(x, y, 1, 1);
|
||||||
|
positioner.set_size(width, height);
|
||||||
|
positioner.set_anchor(Anchor::TopLeft);
|
||||||
|
positioner.set_gravity(Gravity::BottomRight);
|
||||||
|
positioner.set_constraint_adjustment(
|
||||||
|
ConstraintAdjustment::SlideX
|
||||||
|
| ConstraintAdjustment::SlideY
|
||||||
|
| ConstraintAdjustment::FlipX
|
||||||
|
| ConstraintAdjustment::FlipY
|
||||||
|
| ConstraintAdjustment::ResizeX
|
||||||
|
| ConstraintAdjustment::ResizeY,
|
||||||
|
);
|
||||||
|
|
||||||
|
positioner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grab(&self, seat: &WlSeat, serial: u32) {
|
||||||
|
info!("Grabbing popup with serial {serial}");
|
||||||
|
self.xdg_popup.grab(seat, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
src/windowing/popup_manager.rs
Normal file
160
src/windowing/popup_manager.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
use crate::errors::{LayerShikaError, Result};
|
||||||
|
use crate::rendering::{egl_context::EGLContext, popup_window::PopupWindow};
|
||||||
|
use log::info;
|
||||||
|
use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize};
|
||||||
|
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wayland_client::{
|
||||||
|
backend::ObjectId,
|
||||||
|
protocol::{wl_compositor::WlCompositor, wl_display::WlDisplay, wl_seat::WlSeat},
|
||||||
|
Connection, Proxy, QueueHandle,
|
||||||
|
};
|
||||||
|
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||||
|
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
||||||
|
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
|
||||||
|
|
||||||
|
use super::{popup::PopupSurface, state::WindowState};
|
||||||
|
|
||||||
|
pub struct PopupContext {
|
||||||
|
compositor: WlCompositor,
|
||||||
|
xdg_wm_base: Option<XdgWmBase>,
|
||||||
|
seat: WlSeat,
|
||||||
|
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
|
viewporter: Option<WpViewporter>,
|
||||||
|
display: WlDisplay,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
connection: Rc<Connection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopupContext {
|
||||||
|
pub const fn new(
|
||||||
|
compositor: WlCompositor,
|
||||||
|
xdg_wm_base: Option<XdgWmBase>,
|
||||||
|
seat: WlSeat,
|
||||||
|
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
|
viewporter: Option<WpViewporter>,
|
||||||
|
display: WlDisplay,
|
||||||
|
connection: Rc<Connection>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
compositor,
|
||||||
|
xdg_wm_base,
|
||||||
|
seat,
|
||||||
|
fractional_scale_manager,
|
||||||
|
viewporter,
|
||||||
|
display,
|
||||||
|
connection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActivePopup {
|
||||||
|
surface: PopupSurface,
|
||||||
|
window: Rc<PopupWindow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopupManager {
|
||||||
|
context: PopupContext,
|
||||||
|
popups: RefCell<Vec<ActivePopup>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopupManager {
|
||||||
|
pub const fn new(context: PopupContext) -> Self {
|
||||||
|
Self {
|
||||||
|
context,
|
||||||
|
popups: RefCell::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_popup(
|
||||||
|
&self,
|
||||||
|
queue_handle: &QueueHandle<WindowState>,
|
||||||
|
parent_layer_surface: &ZwlrLayerSurfaceV1,
|
||||||
|
last_pointer_serial: u32,
|
||||||
|
scale_factor: f32,
|
||||||
|
) -> Result<Rc<PopupWindow>> {
|
||||||
|
let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| {
|
||||||
|
LayerShikaError::WaylandProtocol("xdg-shell not available for popups".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!("Creating popup window");
|
||||||
|
|
||||||
|
let popup_size = PhysicalSize::new(360, 524);
|
||||||
|
|
||||||
|
let popup_surface = PopupSurface::create(&super::popup::PopupSurfaceParams {
|
||||||
|
compositor: &self.context.compositor,
|
||||||
|
xdg_wm_base,
|
||||||
|
parent_layer_surface,
|
||||||
|
fractional_scale_manager: self.context.fractional_scale_manager.as_ref(),
|
||||||
|
viewporter: self.context.viewporter.as_ref(),
|
||||||
|
queue_handle,
|
||||||
|
position: slint::LogicalPosition::new(0.0, 0.0),
|
||||||
|
size: popup_size,
|
||||||
|
scale_factor,
|
||||||
|
});
|
||||||
|
|
||||||
|
popup_surface.grab(&self.context.seat, last_pointer_serial);
|
||||||
|
|
||||||
|
let context = EGLContext::builder()
|
||||||
|
.with_display_id(self.context.display.id())
|
||||||
|
.with_surface_id(popup_surface.surface.id())
|
||||||
|
.with_size(popup_size)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
|
||||||
|
|
||||||
|
let renderer = FemtoVGRenderer::new(context)
|
||||||
|
.map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?;
|
||||||
|
|
||||||
|
let popup_window = PopupWindow::new(renderer);
|
||||||
|
popup_window.set_size(WindowSize::Physical(popup_size));
|
||||||
|
popup_window.set_scale_factor(scale_factor);
|
||||||
|
|
||||||
|
info!("Popup window created successfully");
|
||||||
|
|
||||||
|
self.popups.borrow_mut().push(ActivePopup {
|
||||||
|
surface: popup_surface,
|
||||||
|
window: Rc::clone(&popup_window),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(popup_window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_popups(&self) -> Result<()> {
|
||||||
|
for popup in self.popups.borrow().iter() {
|
||||||
|
popup.window.render_frame_if_dirty()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn has_xdg_shell(&self) -> bool {
|
||||||
|
self.context.xdg_wm_base.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn popup_count(&self) -> usize {
|
||||||
|
self.popups.borrow().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_all_popups_dirty(&self) {
|
||||||
|
for popup in self.popups.borrow().iter() {
|
||||||
|
popup.window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_popup_index_by_surface_id(&self, surface_id: &ObjectId) -> Option<usize> {
|
||||||
|
for (index, popup) in self.popups.borrow().iter().enumerate() {
|
||||||
|
if popup.surface.surface.id() == *surface_id {
|
||||||
|
return Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_popup_window(&self, index: usize) -> Option<Rc<PopupWindow>> {
|
||||||
|
self.popups
|
||||||
|
.borrow()
|
||||||
|
.get(index)
|
||||||
|
.map(|popup| Rc::clone(&popup.window))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use slint::{platform::set_platform, PhysicalSize};
|
use slint::{
|
||||||
|
platform::{set_platform, Platform, WindowAdapter},
|
||||||
|
PhysicalSize, PlatformError,
|
||||||
|
};
|
||||||
use slint_interpreter::ComponentDefinition;
|
use slint_interpreter::ComponentDefinition;
|
||||||
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||||
use wayland_client::{protocol::{wl_output::WlOutput, wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
|
use wayland_client::{protocol::{wl_output::WlOutput, wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
|
||||||
|
|
@ -9,6 +12,15 @@ use crate::{errors::{LayerShikaError, Result}, rendering::{femtovg_window::Femto
|
||||||
|
|
||||||
use super::WindowState;
|
use super::WindowState;
|
||||||
|
|
||||||
|
struct PlatformWrapper(Rc<CustomSlintPlatform>);
|
||||||
|
|
||||||
|
impl Platform for PlatformWrapper {
|
||||||
|
#[allow(clippy::absolute_paths)]
|
||||||
|
fn create_window_adapter(&self) -> std::result::Result<Rc<dyn WindowAdapter>, PlatformError> {
|
||||||
|
self.0.create_window_adapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WindowStateBuilder {
|
pub struct WindowStateBuilder {
|
||||||
pub component_definition: Option<ComponentDefinition>,
|
pub component_definition: Option<ComponentDefinition>,
|
||||||
pub surface: Option<Rc<WlSurface>>,
|
pub surface: Option<Rc<WlSurface>>,
|
||||||
|
|
@ -116,17 +128,17 @@ impl WindowStateBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<WindowState> {
|
pub fn build(self) -> Result<(WindowState, Rc<CustomSlintPlatform>)> {
|
||||||
let platform = CustomSlintPlatform::new(
|
let platform =
|
||||||
self.window
|
Rc::new(CustomSlintPlatform::new(self.window.as_ref().ok_or_else(
|
||||||
.as_ref()
|
|| LayerShikaError::InvalidInput("Window is required".into()),
|
||||||
.ok_or_else(|| LayerShikaError::InvalidInput("Window is required".into()))?,
|
)?));
|
||||||
);
|
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))).map_err(|e| {
|
||||||
set_platform(Box::new(platform)).map_err(|e| {
|
|
||||||
LayerShikaError::PlatformSetup(format!("Failed to set platform: {e:?}"))
|
LayerShikaError::PlatformSetup(format!("Failed to set platform: {e:?}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
WindowState::new(self)
|
let state = WindowState::new(self)?;
|
||||||
|
Ok((state, platform))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::WindowState;
|
||||||
use crate::impl_empty_dispatch;
|
use crate::impl_empty_dispatch;
|
||||||
use log::info;
|
use log::info;
|
||||||
use slint::{
|
use slint::{
|
||||||
|
|
@ -28,8 +29,12 @@ use wayland_protocols::wp::fractional_scale::v1::client::{
|
||||||
use wayland_protocols::wp::viewporter::client::{
|
use wayland_protocols::wp::viewporter::client::{
|
||||||
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
|
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
|
||||||
};
|
};
|
||||||
|
use wayland_protocols::xdg::shell::client::{
|
||||||
use super::WindowState;
|
xdg_popup::{self, XdgPopup},
|
||||||
|
xdg_positioner::XdgPositioner,
|
||||||
|
xdg_surface::{self, XdgSurface},
|
||||||
|
xdg_wm_base::{self, XdgWmBase},
|
||||||
|
};
|
||||||
|
|
||||||
impl Dispatch<ZwlrLayerSurfaceV1, ()> for WindowState {
|
impl Dispatch<ZwlrLayerSurfaceV1, ()> for WindowState {
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
|
@ -154,41 +159,56 @@ impl Dispatch<WlPointer, ()> for WindowState {
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
wl_pointer::Event::Enter {
|
wl_pointer::Event::Enter {
|
||||||
|
serial,
|
||||||
|
surface,
|
||||||
surface_x,
|
surface_x,
|
||||||
surface_y,
|
surface_y,
|
||||||
..
|
} => {
|
||||||
|
info!("Pointer entered surface {:?}", surface.id());
|
||||||
|
state.set_last_pointer_serial(serial);
|
||||||
|
state.set_current_pointer_position(surface_x, surface_y);
|
||||||
|
|
||||||
|
state.find_window_for_surface(&surface);
|
||||||
|
let position = *state.current_pointer_position();
|
||||||
|
|
||||||
|
state.dispatch_to_active_window(WindowEvent::PointerMoved { position });
|
||||||
}
|
}
|
||||||
| wl_pointer::Event::Motion {
|
|
||||||
|
wl_pointer::Event::Motion {
|
||||||
surface_x,
|
surface_x,
|
||||||
surface_y,
|
surface_y,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
state.set_current_pointer_position(surface_x, surface_y);
|
state.set_current_pointer_position(surface_x, surface_y);
|
||||||
let logical_position = state.current_pointer_position();
|
let position = *state.current_pointer_position();
|
||||||
state.window().dispatch_event(WindowEvent::PointerMoved {
|
|
||||||
position: *logical_position,
|
state.dispatch_to_active_window(WindowEvent::PointerMoved { position });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wl_pointer::Event::Leave { .. } => {
|
wl_pointer::Event::Leave { .. } => {
|
||||||
state.window().dispatch_event(WindowEvent::PointerExited);
|
state.dispatch_to_active_window(WindowEvent::PointerExited);
|
||||||
|
state.active_window = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
wl_pointer::Event::Button {
|
wl_pointer::Event::Button {
|
||||||
|
serial,
|
||||||
state: button_state,
|
state: button_state,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
state.set_last_pointer_serial(serial);
|
||||||
|
let position = *state.current_pointer_position();
|
||||||
let event = match button_state {
|
let event = match button_state {
|
||||||
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
|
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
|
||||||
button: PointerEventButton::Left,
|
button: PointerEventButton::Left,
|
||||||
position: *state.current_pointer_position(),
|
position,
|
||||||
},
|
},
|
||||||
_ => WindowEvent::PointerReleased {
|
_ => WindowEvent::PointerReleased {
|
||||||
button: PointerEventButton::Left,
|
button: PointerEventButton::Left,
|
||||||
position: *state.current_pointer_position(),
|
position,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
state.window().dispatch_event(event);
|
|
||||||
|
state.dispatch_to_active_window(event);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +233,72 @@ impl Dispatch<WpFractionalScaleV1, ()> for WindowState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<XdgWmBase, ()> for WindowState {
|
||||||
|
fn event(
|
||||||
|
_state: &mut Self,
|
||||||
|
xdg_wm_base: &XdgWmBase,
|
||||||
|
event: xdg_wm_base::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_qhandle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||||
|
info!("XdgWmBase ping received, sending pong with serial {serial}");
|
||||||
|
xdg_wm_base.pong(serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<XdgPopup, ()> for WindowState {
|
||||||
|
fn event(
|
||||||
|
_state: &mut Self,
|
||||||
|
_xdg_popup: &XdgPopup,
|
||||||
|
event: xdg_popup::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_qhandle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
xdg_popup::Event::Configure {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} => {
|
||||||
|
info!("XdgPopup Configure: position=({x}, {y}), size=({width}x{height})");
|
||||||
|
}
|
||||||
|
xdg_popup::Event::PopupDone => {
|
||||||
|
info!("XdgPopup dismissed");
|
||||||
|
}
|
||||||
|
xdg_popup::Event::Repositioned { token } => {
|
||||||
|
info!("XdgPopup repositioned with token {token}");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<XdgSurface, ()> for WindowState {
|
||||||
|
fn event(
|
||||||
|
state: &mut Self,
|
||||||
|
xdg_surface: &XdgSurface,
|
||||||
|
event: xdg_surface::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_qhandle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
if let xdg_surface::Event::Configure { serial } = event {
|
||||||
|
info!("XdgSurface Configure received, sending ack with serial {serial}");
|
||||||
|
xdg_surface.ack_configure(serial);
|
||||||
|
|
||||||
|
if let Some(popup_manager) = &state.popup_manager {
|
||||||
|
info!("Marking all popups as dirty after Configure");
|
||||||
|
popup_manager.mark_all_popups_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_empty_dispatch!(
|
impl_empty_dispatch!(
|
||||||
(WlRegistry, GlobalListContents),
|
(WlRegistry, GlobalListContents),
|
||||||
(WlCompositor, ()),
|
(WlCompositor, ()),
|
||||||
|
|
@ -221,5 +307,6 @@ impl_empty_dispatch!(
|
||||||
(WlSeat, ()),
|
(WlSeat, ()),
|
||||||
(WpFractionalScaleManagerV1, ()),
|
(WpFractionalScaleManagerV1, ()),
|
||||||
(WpViewporter, ()),
|
(WpViewporter, ()),
|
||||||
(WpViewport, ())
|
(WpViewport, ()),
|
||||||
|
(XdgPositioner, ())
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ use std::rc::Rc;
|
||||||
use builder::WindowStateBuilder;
|
use builder::WindowStateBuilder;
|
||||||
use log::info;
|
use log::info;
|
||||||
use slint::{LogicalPosition, PhysicalSize, ComponentHandle};
|
use slint::{LogicalPosition, PhysicalSize, ComponentHandle};
|
||||||
|
use slint::platform::{WindowAdapter, WindowEvent};
|
||||||
use slint_interpreter::ComponentInstance;
|
use slint_interpreter::ComponentInstance;
|
||||||
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||||
use wayland_client::protocol::{wl_output::WlOutput, wl_surface::WlSurface};
|
use wayland_client::{protocol::{wl_output::WlOutput, wl_surface::WlSurface}, Proxy};
|
||||||
use crate::rendering::femtovg_window::FemtoVGWindow;
|
use crate::rendering::femtovg_window::FemtoVGWindow;
|
||||||
use crate::errors::{LayerShikaError, Result};
|
use crate::errors::{LayerShikaError, Result};
|
||||||
use crate::windowing::surface_dimensions::SurfaceDimensions;
|
use crate::windowing::surface_dimensions::SurfaceDimensions;
|
||||||
|
use crate::windowing::popup_manager::PopupManager;
|
||||||
use crate::windowing::proxies::{
|
use crate::windowing::proxies::{
|
||||||
ManagedWlPointer, ManagedWlSurface, ManagedZwlrLayerSurfaceV1,
|
ManagedWlPointer, ManagedWlSurface, ManagedZwlrLayerSurfaceV1,
|
||||||
ManagedWpFractionalScaleV1, ManagedWpViewport,
|
ManagedWpFractionalScaleV1, ManagedWpViewport,
|
||||||
|
|
@ -23,6 +25,12 @@ enum ScalingMode {
|
||||||
Integer,
|
Integer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum ActiveWindow {
|
||||||
|
Main,
|
||||||
|
Popup(usize),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WindowState {
|
pub struct WindowState {
|
||||||
component_instance: ComponentInstance,
|
component_instance: ComponentInstance,
|
||||||
viewport: Option<ManagedWpViewport>,
|
viewport: Option<ManagedWpViewport>,
|
||||||
|
|
@ -38,9 +46,12 @@ pub struct WindowState {
|
||||||
output_size: PhysicalSize,
|
output_size: PhysicalSize,
|
||||||
window: Rc<FemtoVGWindow>,
|
window: Rc<FemtoVGWindow>,
|
||||||
current_pointer_position: LogicalPosition,
|
current_pointer_position: LogicalPosition,
|
||||||
|
last_pointer_serial: u32,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
height: u32,
|
height: u32,
|
||||||
exclusive_zone: i32,
|
exclusive_zone: i32,
|
||||||
|
popup_manager: Option<Rc<PopupManager>>,
|
||||||
|
active_window: Option<ActiveWindow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowState {
|
impl WindowState {
|
||||||
|
|
@ -100,9 +111,12 @@ impl WindowState {
|
||||||
output_size: builder.output_size.unwrap_or_default(),
|
output_size: builder.output_size.unwrap_or_default(),
|
||||||
window,
|
window,
|
||||||
current_pointer_position: LogicalPosition::default(),
|
current_pointer_position: LogicalPosition::default(),
|
||||||
|
last_pointer_serial: 0,
|
||||||
scale_factor: builder.scale_factor,
|
scale_factor: builder.scale_factor,
|
||||||
height: builder.height,
|
height: builder.height,
|
||||||
exclusive_zone: builder.exclusive_zone,
|
exclusive_zone: builder.exclusive_zone,
|
||||||
|
popup_manager: None,
|
||||||
|
active_window: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,4 +278,50 @@ impl WindowState {
|
||||||
pub const fn scale_factor(&self) -> f32 {
|
pub const fn scale_factor(&self) -> f32 {
|
||||||
self.scale_factor
|
self.scale_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn last_pointer_serial(&self) -> u32 {
|
||||||
|
self.last_pointer_serial
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_last_pointer_serial(&mut self, serial: u32) {
|
||||||
|
self.last_pointer_serial = serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_popup_manager(&mut self, popup_manager: Rc<PopupManager>) {
|
||||||
|
self.popup_manager = Some(popup_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_window_for_surface(&mut self, surface: &WlSurface) {
|
||||||
|
let surface_id = surface.id();
|
||||||
|
|
||||||
|
if (**self.surface.inner()).id() == surface_id {
|
||||||
|
self.active_window = Some(ActiveWindow::Main);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(popup_manager) = &self.popup_manager {
|
||||||
|
if let Some(popup_index) = popup_manager.find_popup_index_by_surface_id(&surface_id) {
|
||||||
|
self.active_window = Some(ActiveWindow::Popup(popup_index));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_window = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_to_active_window(&self, event: WindowEvent) {
|
||||||
|
match self.active_window {
|
||||||
|
Some(ActiveWindow::Main) => {
|
||||||
|
self.window.window().dispatch_event(event);
|
||||||
|
}
|
||||||
|
Some(ActiveWindow::Popup(index)) => {
|
||||||
|
if let Some(popup_manager) = &self.popup_manager {
|
||||||
|
if let Some(popup_window) = popup_manager.get_popup_window(index) {
|
||||||
|
popup_window.dispatch_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
use super::{
|
use super::{
|
||||||
config::{LayerSurfaceParams, WindowConfig},
|
config::{LayerSurfaceParams, WindowConfig},
|
||||||
globals::GlobalCtx,
|
globals::GlobalCtx,
|
||||||
|
popup_manager::{PopupContext, PopupManager},
|
||||||
state::{builder::WindowStateBuilder, WindowState},
|
state::{builder::WindowStateBuilder, WindowState},
|
||||||
surface::{SurfaceCtx, SurfaceSetupParams},
|
surface::{SurfaceCtx, SurfaceSetupParams},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{LayerShikaError, Result},
|
errors::{LayerShikaError, Result},
|
||||||
rendering::{egl_context::EGLContext, femtovg_window::FemtoVGWindow},
|
rendering::{
|
||||||
|
egl_context::EGLContext, femtovg_window::FemtoVGWindow, slint_platform::CustomSlintPlatform,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use slint::{
|
use slint::{
|
||||||
platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations},
|
platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations, WindowAdapter},
|
||||||
LogicalPosition, PhysicalSize,
|
LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
|
||||||
};
|
};
|
||||||
use slint_interpreter::ComponentInstance;
|
use slint_interpreter::ComponentInstance;
|
||||||
use smithay_client_toolkit::reexports::calloop::{
|
use smithay_client_toolkit::reexports::calloop::{
|
||||||
|
|
@ -28,21 +31,43 @@ pub struct WindowingSystem {
|
||||||
connection: Rc<Connection>,
|
connection: Rc<Connection>,
|
||||||
event_queue: EventQueue<WindowState>,
|
event_queue: EventQueue<WindowState>,
|
||||||
event_loop: EventLoop<'static, WindowState>,
|
event_loop: EventLoop<'static, WindowState>,
|
||||||
|
popup_manager: Rc<PopupManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowingSystem {
|
impl WindowingSystem {
|
||||||
pub(super) fn new(config: WindowConfig) -> Result<Self> {
|
pub(super) fn new(config: WindowConfig) -> Result<Self> {
|
||||||
info!("Initializing WindowingSystem");
|
info!("Initializing WindowingSystem");
|
||||||
let (connection, event_queue) = Self::init_wayland_connection()?;
|
let (connection, event_queue) = Self::init_wayland_connection()?;
|
||||||
let state = Self::init_state(config, &connection, &event_queue)?;
|
let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?;
|
||||||
let event_loop =
|
let event_loop =
|
||||||
EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?;
|
EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?;
|
||||||
|
|
||||||
|
let popup_context = PopupContext::new(
|
||||||
|
global_ctx.compositor,
|
||||||
|
global_ctx.xdg_wm_base,
|
||||||
|
global_ctx.seat,
|
||||||
|
global_ctx.fractional_scale_manager,
|
||||||
|
global_ctx.viewporter,
|
||||||
|
connection.display(),
|
||||||
|
Rc::clone(&connection),
|
||||||
|
);
|
||||||
|
|
||||||
|
let popup_manager = Rc::new(PopupManager::new(popup_context));
|
||||||
|
|
||||||
|
Self::setup_popup_creator(&popup_manager, &platform, &state, &event_queue);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state,
|
state,
|
||||||
connection,
|
connection,
|
||||||
event_queue,
|
event_queue,
|
||||||
event_loop,
|
event_loop,
|
||||||
|
popup_manager,
|
||||||
|
})
|
||||||
|
.map(|mut system| {
|
||||||
|
system
|
||||||
|
.state
|
||||||
|
.set_popup_manager(Rc::clone(&system.popup_manager));
|
||||||
|
system
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +82,7 @@ impl WindowingSystem {
|
||||||
config: WindowConfig,
|
config: WindowConfig,
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
event_queue: &EventQueue<WindowState>,
|
event_queue: &EventQueue<WindowState>,
|
||||||
) -> Result<WindowState> {
|
) -> Result<(WindowState, GlobalCtx, Rc<CustomSlintPlatform>)> {
|
||||||
let global_ctx = GlobalCtx::initialize(connection, &event_queue.handle())
|
let global_ctx = GlobalCtx::initialize(connection, &event_queue.handle())
|
||||||
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
|
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
|
||||||
|
|
||||||
|
|
@ -83,7 +108,7 @@ impl WindowingSystem {
|
||||||
let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_params);
|
let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_params);
|
||||||
|
|
||||||
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
|
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
|
||||||
let output = Rc::new(global_ctx.output);
|
let output = Rc::new(global_ctx.output.clone());
|
||||||
let window =
|
let window =
|
||||||
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)
|
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)
|
||||||
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
|
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
|
||||||
|
|
@ -108,9 +133,47 @@ impl WindowingSystem {
|
||||||
builder = builder.with_viewport(Rc::clone(vp));
|
builder = builder.with_viewport(Rc::clone(vp));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
let (state, platform) = builder
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))
|
.map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok((state, global_ctx, platform))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_popup_creator(
|
||||||
|
popup_manager: &Rc<PopupManager>,
|
||||||
|
platform: &Rc<CustomSlintPlatform>,
|
||||||
|
state: &WindowState,
|
||||||
|
event_queue: &EventQueue<WindowState>,
|
||||||
|
) {
|
||||||
|
if !popup_manager.has_xdg_shell() {
|
||||||
|
info!("xdg-shell not available, popups will not be supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Setting up popup creator with xdg-shell support");
|
||||||
|
|
||||||
|
let popup_manager_clone = Rc::clone(popup_manager);
|
||||||
|
let layer_surface = state.layer_surface();
|
||||||
|
let queue_handle = event_queue.handle();
|
||||||
|
let scale_factor = state.scale_factor();
|
||||||
|
let last_serial = state.last_pointer_serial();
|
||||||
|
|
||||||
|
platform.set_popup_creator(move || {
|
||||||
|
info!("Popup creator called! Creating popup window...");
|
||||||
|
|
||||||
|
let result = popup_manager_clone
|
||||||
|
.create_popup(&queue_handle, &layer_surface, last_serial, scale_factor)
|
||||||
|
.map(|w| w as Rc<dyn WindowAdapter>)
|
||||||
|
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")));
|
||||||
|
|
||||||
|
match &result {
|
||||||
|
Ok(_) => info!("Popup created successfully"),
|
||||||
|
Err(e) => info!("Popup creation failed: {e:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_renderer(
|
fn initialize_renderer(
|
||||||
|
|
@ -133,7 +196,7 @@ impl WindowingSystem {
|
||||||
let femtovg_window = FemtoVGWindow::new(renderer);
|
let femtovg_window = FemtoVGWindow::new(renderer);
|
||||||
femtovg_window.set_size(slint::WindowSize::Physical(init_size));
|
femtovg_window.set_size(slint::WindowSize::Physical(init_size));
|
||||||
femtovg_window.set_scale_factor(config.scale_factor);
|
femtovg_window.set_scale_factor(config.scale_factor);
|
||||||
femtovg_window.set_position(LogicalPosition::new(0., 0.));
|
femtovg_window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.)));
|
||||||
|
|
||||||
Ok(femtovg_window)
|
Ok(femtovg_window)
|
||||||
}
|
}
|
||||||
|
|
@ -145,10 +208,6 @@ impl WindowingSystem {
|
||||||
pub fn run(&mut self) -> Result<()> {
|
pub fn run(&mut self) -> Result<()> {
|
||||||
info!("Starting WindowingSystem main loop");
|
info!("Starting WindowingSystem main loop");
|
||||||
|
|
||||||
// Process all initial configuration events by repeatedly dispatching until queue is empty.
|
|
||||||
// After each batch, flush and render to allow compositor to respond with additional
|
|
||||||
// configure events (e.g., after fractional scale is applied).
|
|
||||||
// This ensures proper initialization.
|
|
||||||
info!("Processing initial Wayland configuration events");
|
info!("Processing initial Wayland configuration events");
|
||||||
while self
|
while self
|
||||||
.event_queue
|
.event_queue
|
||||||
|
|
@ -156,12 +215,10 @@ impl WindowingSystem {
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?
|
||||||
> 0
|
> 0
|
||||||
{
|
{
|
||||||
// Flush requests to compositor after processing each event batch
|
|
||||||
self.connection
|
self.connection
|
||||||
.flush()
|
.flush()
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
||||||
|
|
||||||
// Render if window was marked dirty by the configure events
|
|
||||||
update_timers_and_animations();
|
update_timers_and_animations();
|
||||||
self.state
|
self.state
|
||||||
.window()
|
.window()
|
||||||
|
|
@ -169,17 +226,17 @@ impl WindowingSystem {
|
||||||
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup Wayland file descriptor as event source for ongoing events
|
|
||||||
self.setup_wayland_event_source()?;
|
self.setup_wayland_event_source()?;
|
||||||
|
|
||||||
let event_queue = &mut self.event_queue;
|
let event_queue = &mut self.event_queue;
|
||||||
let connection = &self.connection;
|
let connection = &self.connection;
|
||||||
|
let popup_manager = Rc::clone(&self.popup_manager);
|
||||||
|
|
||||||
// Enter the main event loop with consistent event processing:
|
|
||||||
// read -> dispatch -> animate -> render -> flush
|
|
||||||
self.event_loop
|
self.event_loop
|
||||||
.run(None, &mut self.state, move |shared_data| {
|
.run(None, &mut self.state, move |shared_data| {
|
||||||
if let Err(e) = Self::process_events(connection, event_queue, shared_data) {
|
if let Err(e) =
|
||||||
|
Self::process_events(connection, event_queue, shared_data, &popup_manager)
|
||||||
|
{
|
||||||
error!("Error processing events: {e}");
|
error!("Error processing events: {e}");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -204,29 +261,29 @@ impl WindowingSystem {
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
event_queue: &mut EventQueue<WindowState>,
|
event_queue: &mut EventQueue<WindowState>,
|
||||||
shared_data: &mut WindowState,
|
shared_data: &mut WindowState,
|
||||||
|
popup_manager: &PopupManager,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// 1. READ: Read pending events from Wayland socket if available
|
|
||||||
if let Some(guard) = event_queue.prepare_read() {
|
if let Some(guard) = event_queue.prepare_read() {
|
||||||
guard
|
guard
|
||||||
.read()
|
.read()
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. DISPATCH: Process all queued Wayland events
|
|
||||||
event_queue
|
event_queue
|
||||||
.dispatch_pending(shared_data)
|
.dispatch_pending(shared_data)
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
||||||
|
|
||||||
// 3. ANIMATE: Update Slint timers and animations
|
|
||||||
update_timers_and_animations();
|
update_timers_and_animations();
|
||||||
|
|
||||||
// 4. RENDER: Render frame if window was marked dirty
|
|
||||||
shared_data
|
shared_data
|
||||||
.window()
|
.window()
|
||||||
.render_frame_if_dirty()
|
.render_frame_if_dirty()
|
||||||
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
||||||
|
|
||||||
// 5. FLUSH: Send buffered Wayland requests to compositor
|
popup_manager
|
||||||
|
.render_popups()
|
||||||
|
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.flush()
|
.flush()
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue