197 lines
5.9 KiB
Rust
197 lines
5.9 KiB
Rust
use {
|
|
crate::{
|
|
client::{Client, ClientError},
|
|
ifs::{
|
|
wl_seat::text_input::zwp_input_method_v2::ZwpInputMethodV2,
|
|
wl_surface::{SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError},
|
|
},
|
|
leaks::Tracker,
|
|
object::{Object, Version},
|
|
rect::Rect,
|
|
state::State,
|
|
tree::NodeLayerLink,
|
|
wire::{WlSurfaceId, ZwpInputPopupSurfaceV2Id, zwp_input_popup_surface_v2::*},
|
|
},
|
|
std::{cell::Cell, rc::Rc},
|
|
thiserror::Error,
|
|
};
|
|
|
|
pub struct ZwpInputPopupSurfaceV2 {
|
|
pub id: ZwpInputPopupSurfaceV2Id,
|
|
pub client: Rc<Client>,
|
|
pub input_method: Rc<ZwpInputMethodV2>,
|
|
pub surface: Rc<WlSurface>,
|
|
pub version: Version,
|
|
pub tracker: Tracker<Self>,
|
|
pub positioning_scheduled: Cell<bool>,
|
|
pub was_on_screen: Cell<bool>,
|
|
}
|
|
|
|
impl SurfaceExt for ZwpInputPopupSurfaceV2 {
|
|
fn node_layer(&self) -> NodeLayerLink {
|
|
NodeLayerLink::InputMethod
|
|
}
|
|
|
|
fn after_apply_commit(self: Rc<Self>) {
|
|
self.update_visible();
|
|
if self.surface.visible.get() {
|
|
self.schedule_positioning();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn input_popup_positioning(state: Rc<State>) {
|
|
loop {
|
|
let popup = state.pending_input_popup_positioning.pop().await;
|
|
if popup.positioning_scheduled.get() {
|
|
popup.position();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ZwpInputPopupSurfaceV2 {
|
|
fn damage(&self) {
|
|
let (x, y) = self.surface.buffer_abs_pos.get().position();
|
|
let extents = self.surface.extents.get();
|
|
self.client.state.damage(extents.move_(x, y));
|
|
}
|
|
|
|
pub fn update_visible(self: &Rc<Self>) {
|
|
let was_visible = self.surface.visible.get();
|
|
let is_visible = self.surface.buffer.is_some()
|
|
&& self.input_method.connection.is_some()
|
|
&& self.client.state.root_visible();
|
|
self.surface.set_visible(is_visible);
|
|
if was_visible != is_visible {
|
|
self.was_on_screen.set(false);
|
|
if is_visible {
|
|
self.schedule_positioning();
|
|
} else {
|
|
self.damage();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn schedule_positioning(self: &Rc<Self>) {
|
|
if self.surface.visible.get() {
|
|
if !self.positioning_scheduled.replace(true) {
|
|
self.client
|
|
.state
|
|
.pending_input_popup_positioning
|
|
.push(self.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn position(&self) {
|
|
self.positioning_scheduled.set(false);
|
|
if !self.surface.visible.get() {
|
|
return;
|
|
}
|
|
let Some(con) = self.input_method.connection.get() else {
|
|
log::warn!("Popup has no connection but is visible");
|
|
return;
|
|
};
|
|
let output = con.surface.output.get().global.pos.get();
|
|
let surface_rect = con.surface.buffer_abs_pos.get();
|
|
let cursor_rect = con
|
|
.text_input
|
|
.cursor_rect()
|
|
.move_(surface_rect.x1(), surface_rect.y1());
|
|
let extents = self.surface.extents.get();
|
|
let mut rect = extents.at_point(cursor_rect.x1(), cursor_rect.y2());
|
|
let overflow = output.get_overflow(&rect);
|
|
if overflow.right > 0 {
|
|
let dx = -overflow.right.min(rect.width());
|
|
let rect2 = rect.move_(dx, 0);
|
|
if !output.get_overflow(&rect2).x_overflow() {
|
|
rect = rect2;
|
|
}
|
|
}
|
|
if overflow.bottom > 0 {
|
|
let rect2 = rect.move_(0, -(cursor_rect.height() + rect.height()));
|
|
if !output.get_overflow(&rect2).y_overflow() {
|
|
rect = rect2;
|
|
}
|
|
}
|
|
let old = self.surface.buffer_abs_pos.get();
|
|
let new = old.at_point(rect.x1() - extents.x1(), rect.y1() - extents.y1());
|
|
if self.was_on_screen.get() && new != old {
|
|
self.damage();
|
|
}
|
|
self.surface.buffer_abs_pos.set(new);
|
|
if !self.was_on_screen.get() || new != old {
|
|
self.damage();
|
|
}
|
|
self.was_on_screen.set(true);
|
|
}
|
|
|
|
pub fn install(self: &Rc<Self>) -> Result<(), ZwpInputPopupSurfaceV2Error> {
|
|
self.surface.set_role(SurfaceRole::InputPopup)?;
|
|
if self.surface.ext.get().is_some() {
|
|
return Err(ZwpInputPopupSurfaceV2Error::AlreadyAttached(
|
|
self.surface.id,
|
|
));
|
|
}
|
|
self.surface.ext.set(self.clone());
|
|
self.input_method.popups.insert(self.id, self.clone());
|
|
Ok(())
|
|
}
|
|
|
|
#[expect(dead_code)]
|
|
pub fn send_text_input_rectangle(&self, rect: Rect) {
|
|
self.client.event(TextInputRectangle {
|
|
self_id: self.id,
|
|
x: rect.x1(),
|
|
y: rect.y1(),
|
|
width: rect.width(),
|
|
height: rect.height(),
|
|
});
|
|
}
|
|
|
|
fn detach(&self) {
|
|
if self.surface.visible.get() {
|
|
self.damage();
|
|
}
|
|
self.surface.destroy_node();
|
|
self.surface.unset_ext();
|
|
self.input_method.popups.remove(&self.id);
|
|
}
|
|
}
|
|
|
|
impl ZwpInputPopupSurfaceV2RequestHandler for ZwpInputPopupSurfaceV2 {
|
|
type Error = ZwpInputPopupSurfaceV2Error;
|
|
|
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
|
self.detach();
|
|
self.client.remove_obj(self)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
object_base! {
|
|
self = ZwpInputPopupSurfaceV2;
|
|
version = self.version;
|
|
}
|
|
|
|
impl Object for ZwpInputPopupSurfaceV2 {
|
|
fn break_loops(&self) {
|
|
self.detach();
|
|
}
|
|
}
|
|
|
|
simple_add_obj!(ZwpInputPopupSurfaceV2);
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ZwpInputPopupSurfaceV2Error {
|
|
#[error(transparent)]
|
|
ClientError(Box<ClientError>),
|
|
#[error(transparent)]
|
|
WlSurfaceError(Box<WlSurfaceError>),
|
|
#[error(
|
|
"Surface {0} cannot be turned into a zwp_input_popup_surface_v2 because it already has an attached zwp_input_popup_surface_v2"
|
|
)]
|
|
AlreadyAttached(WlSurfaceId),
|
|
}
|
|
efrom!(ZwpInputPopupSurfaceV2Error, WlSurfaceError);
|
|
efrom!(ZwpInputPopupSurfaceV2Error, ClientError);
|