refactor: make outputs a first-class concept

This commit is contained in:
drendog 2025-11-17 00:46:29 +01:00
parent 8cd94cd13e
commit e194682785
Signed by: dwenya
GPG key ID: 8DD77074645332D0
9 changed files with 187 additions and 53 deletions

View file

@ -138,7 +138,7 @@ impl Dispatch<WlPointer, ()> for AppState {
if let Some(window) = state.get_output_by_surface_mut(&surface_id) {
window.handle_pointer_enter(serial, &surface, surface_x, surface_y);
if let Some(key) = state.get_key_by_surface(&surface_id).cloned() {
if let Some(key) = state.get_key_by_surface(&surface_id) {
state.set_active_output(Some(key));
}
} else {
@ -158,7 +158,7 @@ impl Dispatch<WlPointer, ()> for AppState {
surface_y,
..
} => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(output_key) = state.active_output() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.handle_pointer_motion(surface_x, surface_y);
}
@ -166,7 +166,7 @@ impl Dispatch<WlPointer, ()> for AppState {
}
wl_pointer::Event::Leave { .. } => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(output_key) = state.active_output() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.handle_pointer_leave();
}
@ -179,7 +179,7 @@ impl Dispatch<WlPointer, ()> for AppState {
state: button_state,
..
} => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(output_key) = state.active_output() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.handle_pointer_button(serial, button_state);
}

View file

@ -1,20 +1,42 @@
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use wayland_client::backend::ObjectId;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OutputKey(ObjectId);
impl OutputKey {
pub const fn new(id: ObjectId) -> Self {
Self(id)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OutputKey {
handle: OutputHandle,
_object_id_hash: u64,
}
pub const fn id(&self) -> &ObjectId {
&self.0
impl OutputKey {
pub fn new(object_id: &ObjectId) -> Self {
let mut hasher = DefaultHasher::new();
object_id.hash(&mut hasher);
let object_id_hash = hasher.finish();
Self {
handle: OutputHandle::new(),
_object_id_hash: object_id_hash,
}
}
pub const fn handle(&self) -> OutputHandle {
self.handle
}
}
impl From<ObjectId> for OutputKey {
fn from(id: ObjectId) -> Self {
Self::new(id)
Self::new(&id)
}
}
impl From<OutputHandle> for OutputKey {
fn from(handle: OutputHandle) -> Self {
Self {
handle,
_object_id_hash: 0,
}
}
}

View file

@ -1,21 +1,23 @@
use super::surface_state::WindowState;
use super::event_context::SharedPointerSerial;
use super::surface_state::WindowState;
use crate::wayland::managed_proxies::ManagedWlPointer;
use crate::wayland::outputs::OutputKey;
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use std::collections::HashMap;
use std::rc::Rc;
use wayland_client::backend::ObjectId;
use wayland_client::Proxy;
use wayland_client::backend::ObjectId;
pub type PerOutputWindow = WindowState;
pub struct AppState {
outputs: HashMap<OutputKey, PerOutputWindow>,
surface_to_output: HashMap<ObjectId, OutputKey>,
output_to_key: HashMap<ObjectId, OutputKey>,
outputs: HashMap<OutputHandle, PerOutputWindow>,
surface_to_output: HashMap<ObjectId, OutputHandle>,
output_to_handle: HashMap<ObjectId, OutputHandle>,
_pointer: ManagedWlPointer,
shared_pointer_serial: Rc<SharedPointerSerial>,
active_output: Option<OutputKey>,
active_output: Option<OutputHandle>,
primary_output: Option<OutputHandle>,
}
impl AppState {
@ -23,10 +25,11 @@ impl AppState {
Self {
outputs: HashMap::new(),
surface_to_output: HashMap::new(),
output_to_key: HashMap::new(),
output_to_handle: HashMap::new(),
_pointer: pointer,
shared_pointer_serial: shared_serial,
active_output: None,
primary_output: None,
}
}
@ -36,40 +39,56 @@ impl AppState {
main_surface_id: ObjectId,
window: PerOutputWindow,
) {
let key = OutputKey::new(output_id.clone());
self.output_to_key.insert(output_id, key.clone());
self.surface_to_output
.insert(main_surface_id, key.clone());
self.outputs.insert(key, window);
let key = OutputKey::new(&output_id);
let handle = key.handle();
self.output_to_handle.insert(output_id, handle);
self.surface_to_output.insert(main_surface_id, handle);
if self.primary_output.is_none() {
self.primary_output = Some(handle);
}
self.outputs.insert(handle, window);
}
pub fn get_output_by_key(&self, key: &OutputKey) -> Option<&PerOutputWindow> {
self.outputs.get(key)
self.outputs.get(&key.handle())
}
pub fn get_output_by_key_mut(&mut self, key: &OutputKey) -> Option<&mut PerOutputWindow> {
self.outputs.get_mut(key)
self.outputs.get_mut(&key.handle())
}
pub fn get_output_by_handle(&self, handle: OutputHandle) -> Option<&PerOutputWindow> {
self.outputs.get(&handle)
}
pub fn get_output_by_handle_mut(
&mut self,
handle: OutputHandle,
) -> Option<&mut PerOutputWindow> {
self.outputs.get_mut(&handle)
}
pub fn get_output_by_output_id(&self, output_id: &ObjectId) -> Option<&PerOutputWindow> {
self.output_to_key
self.output_to_handle
.get(output_id)
.and_then(|key| self.outputs.get(key))
.and_then(|handle| self.outputs.get(handle))
}
pub fn get_output_by_output_id_mut(
&mut self,
output_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.output_to_key
self.output_to_handle
.get(output_id)
.and_then(|key| self.outputs.get_mut(key))
.and_then(|handle| self.outputs.get_mut(handle))
}
pub fn get_output_by_surface(&self, surface_id: &ObjectId) -> Option<&PerOutputWindow> {
self.surface_to_output
.get(surface_id)
.and_then(|key| self.outputs.get(key))
.and_then(|handle| self.outputs.get(handle))
}
pub fn get_output_by_surface_mut(
@ -78,40 +97,66 @@ impl AppState {
) -> Option<&mut PerOutputWindow> {
self.surface_to_output
.get(surface_id)
.and_then(|key| self.outputs.get_mut(key))
.and_then(|handle| self.outputs.get_mut(handle))
}
pub fn get_output_by_layer_surface_mut(
&mut self,
layer_surface_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.outputs.values_mut().find(|window| {
window.layer_surface().as_ref().id() == *layer_surface_id
})
self.outputs
.values_mut()
.find(|window| window.layer_surface().as_ref().id() == *layer_surface_id)
}
pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&OutputKey> {
self.surface_to_output.get(surface_id)
pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<OutputKey> {
self.surface_to_output
.get(surface_id)
.map(|&handle| OutputKey::from(handle))
}
pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option<&OutputKey> {
self.output_to_key.get(output_id)
pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option<OutputKey> {
self.output_to_handle
.get(output_id)
.map(|&handle| OutputKey::from(handle))
}
pub fn get_handle_by_surface(&self, surface_id: &ObjectId) -> Option<OutputHandle> {
self.surface_to_output.get(surface_id).copied()
}
pub fn get_handle_by_output_id(&self, output_id: &ObjectId) -> Option<OutputHandle> {
self.output_to_handle.get(output_id).copied()
}
pub fn register_popup_surface(&mut self, popup_surface_id: ObjectId, output_key: OutputKey) {
self.surface_to_output.insert(popup_surface_id, output_key);
self.surface_to_output
.insert(popup_surface_id, output_key.handle());
}
pub fn set_active_output(&mut self, key: Option<OutputKey>) {
self.active_output = key;
self.active_output = key.map(|k| k.handle());
}
pub const fn active_output(&self) -> Option<&OutputKey> {
self.active_output.as_ref()
pub fn set_active_output_handle(&mut self, handle: Option<OutputHandle>) {
self.active_output = handle;
}
pub fn active_output(&self) -> Option<OutputKey> {
self.active_output.map(OutputKey::from)
}
pub fn active_output_handle(&self) -> Option<OutputHandle> {
self.active_output
}
pub fn primary_output(&self) -> Option<&PerOutputWindow> {
self.outputs.values().next()
self.primary_output
.and_then(|handle| self.outputs.get(&handle))
}
pub fn primary_output_handle(&self) -> Option<OutputHandle> {
self.primary_output
}
pub fn all_outputs(&self) -> impl Iterator<Item = &PerOutputWindow> {
@ -122,6 +167,12 @@ impl AppState {
self.outputs.values_mut()
}
pub fn outputs_with_handles(&self) -> impl Iterator<Item = (OutputHandle, &PerOutputWindow)> {
self.outputs
.iter()
.map(|(&handle, window)| (handle, window))
}
pub const fn shared_pointer_serial(&self) -> &Rc<SharedPointerSerial> {
&self.shared_pointer_serial
}
@ -150,12 +201,22 @@ impl AppState {
}
pub fn get_key_by_popup(&self, popup_surface_id: &ObjectId) -> Option<OutputKey> {
self.outputs.iter().find_map(|(key, window)| {
self.outputs.iter().find_map(|(&handle, window)| {
window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_surface(popup_surface_id))
.map(|_| key.clone())
.map(|_| OutputKey::from(handle))
})
}
pub fn get_handle_by_popup(&self, popup_surface_id: &ObjectId) -> Option<OutputHandle> {
self.outputs.iter().find_map(|(&handle, window)| {
window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_surface(popup_surface_id))
.map(|_| handle)
})
}
}

View file

@ -14,6 +14,7 @@ pub use layer_shika_adapters::platform::{slint, slint_interpreter};
pub use layer_shika_domain::value_objects::anchor::AnchorEdges;
pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity;
pub use layer_shika_domain::value_objects::layer::Layer;
pub use layer_shika_domain::value_objects::output_handle::OutputHandle;
pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
pub use layer_shika_domain::value_objects::popup_request::{
PopupAt, PopupHandle, PopupRequest, PopupSize,

View file

@ -14,6 +14,7 @@ use layer_shika_adapters::{
};
use layer_shika_domain::config::WindowConfig;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use layer_shika_domain::value_objects::popup_request::{PopupHandle, PopupRequest, PopupSize};
use std::cell::Cell;
@ -139,10 +140,32 @@ impl RuntimeState<'_> {
.map(WindowState::component_instance)
}
#[must_use]
pub fn primary_output_handle(&self) -> Option<OutputHandle> {
self.app_state.primary_output_handle()
}
#[must_use]
pub fn active_output_handle(&self) -> Option<OutputHandle> {
self.app_state.active_output_handle()
}
pub fn outputs(&self) -> impl Iterator<Item = (OutputHandle, &ComponentInstance)> {
self.app_state
.outputs_with_handles()
.map(|(handle, window)| (handle, window.component_instance()))
}
pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> {
self.app_state
.get_output_by_handle(handle)
.map(WindowState::component_instance)
}
fn active_or_primary_output(&self) -> Option<&WindowState> {
self.app_state
.active_output()
.and_then(|key| self.app_state.get_output_by_key(key))
.and_then(|key| self.app_state.get_output_by_key(&key))
.or_else(|| self.app_state.primary_output())
}

View file

@ -3,6 +3,7 @@ pub mod dimensions;
pub mod keyboard_interactivity;
pub mod layer;
pub mod margins;
pub mod output_handle;
pub mod popup_config;
pub mod popup_positioning_mode;
pub mod popup_request;

View file

@ -0,0 +1,26 @@
use std::sync::atomic::{AtomicUsize, Ordering};
static NEXT_OUTPUT_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OutputHandle {
id: usize,
}
impl OutputHandle {
pub fn new() -> Self {
Self {
id: NEXT_OUTPUT_ID.fetch_add(1, Ordering::Relaxed),
}
}
pub const fn id(&self) -> usize {
self.id
}
}
impl Default for OutputHandle {
fn default() -> Self {
Self::new()
}
}

View file

@ -44,9 +44,9 @@
pub mod prelude;
pub use layer_shika_composition::{
AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, PopupAt,
PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, RuntimeState,
SlintCallbackContract, SlintCallbackNames, WindowingSystem,
AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, OutputHandle,
PopupAt, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result,
RuntimeState, SlintCallbackContract, SlintCallbackNames, WindowingSystem,
};
pub use layer_shika_composition::{slint, slint_interpreter};

View file

@ -10,14 +10,14 @@
// Core API types
pub use crate::{
Error, EventLoopHandle, LayerShika, PopupWindow, Result, RuntimeState,
SlintCallbackContract, SlintCallbackNames, WindowingSystem,
Error, EventLoopHandle, LayerShika, PopupWindow, Result, RuntimeState, SlintCallbackContract,
SlintCallbackNames, WindowingSystem,
};
// Domain value objects
pub use crate::{
AnchorEdges, KeyboardInteractivity, Layer, PopupAt, PopupHandle, PopupPositioningMode,
PopupRequest, PopupSize,
AnchorEdges, KeyboardInteractivity, Layer, OutputHandle, PopupAt, PopupHandle,
PopupPositioningMode, PopupRequest, PopupSize,
};
// Event loop types