mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Start simplifying popup logic
So uh when did Smithay have all these popup helpers, did I just not read the docs hard enough
This commit is contained in:
parent
220be6d505
commit
97efdb854c
1 changed files with 25 additions and 498 deletions
|
@ -12,7 +12,7 @@ use smithay::{
|
|||
xdg_toplevel::{self, ResizeEdge},
|
||||
},
|
||||
wayland_server::{
|
||||
protocol::{wl_output::WlOutput, wl_seat::WlSeat},
|
||||
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
|
||||
Resource,
|
||||
},
|
||||
},
|
||||
|
@ -99,512 +99,39 @@ impl XdgShellHandler for State {
|
|||
}
|
||||
|
||||
// this is 500 lines there has to be a shorter way to do this
|
||||
fn new_popup(&mut self, surface: PopupSurface, mut positioner: PositionerState) {
|
||||
tracing::debug!(?positioner.constraint_adjustment, ?positioner.gravity);
|
||||
let output_rect = self
|
||||
.focused_output()
|
||||
.and_then(|op| self.space.output_geometry(op));
|
||||
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
|
||||
tracing::info!("XdgShellHandler::new_popup");
|
||||
|
||||
/// Horizontal direction
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DirH {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
let popup_geo = (|| -> Option<Rectangle<i32, Logical>> {
|
||||
let root = find_popup_root_surface(&PopupKind::Xdg(surface.clone())).ok()?;
|
||||
let parent = surface.get_parent_surface()?;
|
||||
|
||||
/// Vertical direction
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DirV {
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
let mut is_subpopup = false;
|
||||
|
||||
// We have to go through this really verbose way of getting the location of the popup in
|
||||
// the global space.
|
||||
//
|
||||
// The location from PopupSurface.with_pending_state's state.geometry.loc is relative to
|
||||
// its parent. When its parent is a window, we can simply add the window's location to the
|
||||
// popup's to get its global location.
|
||||
//
|
||||
// However, if its parent is another popup, we need to step upwards and grab the location
|
||||
// from each popup, aggregating them into one global space location.
|
||||
let global_loc = {
|
||||
let mut surf = surface.get_parent_surface();
|
||||
let mut loc = surface.with_pending_state(|state| state.geometry.loc);
|
||||
tracing::debug!(?loc);
|
||||
|
||||
while let Some(s) = &surf {
|
||||
if let Some(popup) = self.popup_manager.find_popup(s) {
|
||||
// popup.geometry() doesn't return the right location, so we dive into the
|
||||
// PopupSurface's state to grab it.
|
||||
let PopupKind::Xdg(popup_surf) = &popup else { return }; // TODO:
|
||||
let l = popup_surf.with_pending_state(|state| state.geometry.loc);
|
||||
tracing::debug!(loc = ?l, "parent is popup");
|
||||
loc += l;
|
||||
is_subpopup = true;
|
||||
surf = popup_surf.get_parent_surface();
|
||||
} else if let Some(win) = self.window_for_surface(s) {
|
||||
// Once we reach a window, we can stop as windows are already in the global space.
|
||||
tracing::debug!("parent is window");
|
||||
loc += self
|
||||
.space
|
||||
.element_location(&win)
|
||||
.unwrap_or_else(|| (0, 0).into());
|
||||
tracing::debug!(?loc);
|
||||
break;
|
||||
} else {
|
||||
// Or a layer
|
||||
let layer_and_op = self.space.outputs().find_map(|op| {
|
||||
let layer_map = layer_map_for_output(op);
|
||||
|
||||
let ret = layer_map
|
||||
.layers()
|
||||
.find(|l| l.wl_surface() == s)
|
||||
.cloned()
|
||||
.map(|layer| {
|
||||
(
|
||||
layer_map.layer_geometry(&layer).unwrap_or_default(),
|
||||
op.clone(),
|
||||
)
|
||||
});
|
||||
|
||||
ret
|
||||
});
|
||||
|
||||
if let Some((layer_geo, op)) = layer_and_op {
|
||||
let op_loc = self.space.output_geometry(&op).unwrap_or_default().loc;
|
||||
loc += layer_geo.loc + op_loc;
|
||||
break;
|
||||
let win = self.window_for_surface(&root)?;
|
||||
let win_loc = self.space.element_geometry(&win)?.loc;
|
||||
let parent_loc = if root == parent {
|
||||
win_loc
|
||||
} else {
|
||||
match self.popup_manager.find_popup(&parent)? {
|
||||
PopupKind::Xdg(surf) => {
|
||||
surf.with_pending_state(|state| state.geometry.loc) + win_loc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loc
|
||||
};
|
||||
|
||||
let mut popup_rect_global = {
|
||||
let mut rect = positioner.get_geometry();
|
||||
rect.loc = global_loc;
|
||||
rect
|
||||
};
|
||||
|
||||
tracing::debug!(?global_loc);
|
||||
|
||||
// The final rectangle that the popup is set to needs to be relative to its parent,
|
||||
// so we store its old location here and subtract it at the end.
|
||||
let loc_diff = global_loc - surface.with_pending_state(|state| state.geometry.loc);
|
||||
|
||||
if let Some(output_rect) = output_rect {
|
||||
// Check if the rect is constrained horizontally, and if so, which side.
|
||||
let constrained_x = |rect: Rectangle<i32, Logical>| -> Option<DirH> {
|
||||
tracing::debug!(?rect, ?output_rect);
|
||||
|
||||
if rect.loc.x < output_rect.loc.x {
|
||||
Some(DirH::Left)
|
||||
} else if rect.loc.x + rect.size.w > output_rect.loc.x + output_rect.size.w {
|
||||
Some(DirH::Right)
|
||||
} else {
|
||||
None
|
||||
PopupKind::InputMethod(_) => return None,
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the rect is constrained vertically, and if so, which side.
|
||||
let constrained_y = |rect: Rectangle<i32, Logical>| -> Option<DirV> {
|
||||
tracing::debug!(?rect, ?output_rect);
|
||||
let mut output_geo = win
|
||||
.output(self)
|
||||
.and_then(|op| self.space.output_geometry(&op))?;
|
||||
|
||||
if rect.loc.y < output_rect.loc.y {
|
||||
Some(DirV::Top)
|
||||
} else if rect.loc.y + rect.size.h > output_rect.loc.y + output_rect.size.h {
|
||||
Some(DirV::Bottom)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
// Make local to parent
|
||||
output_geo.loc -= dbg!(parent_loc);
|
||||
Some(positioner.get_unconstrained_geometry(output_geo))
|
||||
})()
|
||||
.unwrap_or_else(|| positioner.get_geometry());
|
||||
|
||||
// We're going to need to position popups such that they stay fully onscreen.
|
||||
// We can use the provided `positioner.constraint_adjustment` to get hints on how
|
||||
// the popups want to be relocated.
|
||||
dbg!(popup_geo);
|
||||
|
||||
let output_left_x = output_rect.loc.x;
|
||||
let output_right_x = output_rect.loc.x + output_rect.size.w;
|
||||
let output_top_y = output_rect.loc.y;
|
||||
let output_bottom_y = output_rect.loc.y + output_rect.size.h;
|
||||
|
||||
// The popup is flowing offscreen in the horizontal direction.
|
||||
if let Some(constrain_dir) = constrained_x(popup_rect_global) {
|
||||
tracing::debug!("Popup was constrained on the x axis, repositioning");
|
||||
let gravity = match positioner.gravity {
|
||||
Gravity::Left | Gravity::TopLeft | Gravity::BottomLeft => DirH::Left,
|
||||
_ => DirH::Right,
|
||||
};
|
||||
|
||||
tracing::debug!(?gravity);
|
||||
tracing::debug!(?constrain_dir);
|
||||
|
||||
'block: {
|
||||
// If the constraint_adjustment has SlideX, we attempt to slide the popup
|
||||
// towards the direction specified by positioner.gravity until TODO:
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::SlideX)
|
||||
&& !is_subpopup
|
||||
// If it's a subpopup, flip instead of slide. This makes
|
||||
// stuff like Firefox nested dropdowns more intuitive.
|
||||
{
|
||||
tracing::debug!("Attempting to slide popup X");
|
||||
// Slide towards the gravity until the opposite edge is unconstrained or the
|
||||
// same edge is constrained
|
||||
match (&constrain_dir, &gravity) {
|
||||
(DirH::Left, DirH::Right) => {
|
||||
let len_until_same_edge_constrained = output_right_x
|
||||
- (popup_rect_global.loc.x + popup_rect_global.size.w);
|
||||
let len_until_opp_edge_unconstrained =
|
||||
output_left_x - popup_rect_global.loc.x;
|
||||
|
||||
popup_rect_global.loc.x += i32::min(
|
||||
len_until_same_edge_constrained,
|
||||
len_until_opp_edge_unconstrained,
|
||||
)
|
||||
.max(0);
|
||||
tracing::debug!(
|
||||
?popup_rect_global,
|
||||
"Constrained SlideX left right"
|
||||
);
|
||||
}
|
||||
(DirH::Right, DirH::Left) => {
|
||||
let len_until_same_edge_constrained =
|
||||
popup_rect_global.loc.x - output_left_x;
|
||||
let len_until_opp_edge_unconstrained = (popup_rect_global.loc.x
|
||||
+ popup_rect_global.size.w)
|
||||
- output_right_x;
|
||||
|
||||
popup_rect_global.loc.x -= i32::min(
|
||||
len_until_opp_edge_unconstrained,
|
||||
len_until_same_edge_constrained,
|
||||
)
|
||||
.max(0);
|
||||
tracing::debug!(
|
||||
?popup_rect_global,
|
||||
"Constrained SlideX right left"
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if constrained_x(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
|
||||
// Do the same but in the other direction
|
||||
match (constrain_dir, gravity) {
|
||||
(DirH::Right, DirH::Right) => {
|
||||
let len_until_same_edge_unconstrained =
|
||||
popup_rect_global.loc.x - output_left_x;
|
||||
let len_until_opp_edge_constrained = (popup_rect_global.loc.x
|
||||
+ popup_rect_global.size.w)
|
||||
- output_right_x;
|
||||
|
||||
popup_rect_global.loc.x -= i32::min(
|
||||
len_until_opp_edge_constrained,
|
||||
len_until_same_edge_unconstrained,
|
||||
)
|
||||
.max(0);
|
||||
tracing::debug!(
|
||||
?popup_rect_global,
|
||||
"Constrained SlideX right right"
|
||||
);
|
||||
}
|
||||
(DirH::Left, DirH::Left) => {
|
||||
let len_until_same_edge_unconstrained = output_right_x
|
||||
- (popup_rect_global.loc.x + popup_rect_global.size.w);
|
||||
let len_until_opp_edge_constrained =
|
||||
output_left_x - popup_rect_global.loc.x;
|
||||
|
||||
popup_rect_global.loc.x += i32::min(
|
||||
len_until_same_edge_unconstrained,
|
||||
len_until_opp_edge_constrained,
|
||||
)
|
||||
.max(0);
|
||||
tracing::debug!(?popup_rect_global, "Constrained SlideX left left");
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if constrained_x(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
|
||||
// If the above didn't bring the popup onscreen or if it's a nested popup, flip it.
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::FlipX)
|
||||
{
|
||||
tracing::debug!("Attempting to flip popup X");
|
||||
let old_gravity = positioner.gravity;
|
||||
positioner.gravity = match positioner.gravity {
|
||||
Gravity::Left => Gravity::Right,
|
||||
Gravity::Right => Gravity::Left,
|
||||
Gravity::TopLeft => Gravity::TopRight,
|
||||
Gravity::BottomLeft => Gravity::BottomRight,
|
||||
Gravity::TopRight => Gravity::TopLeft,
|
||||
Gravity::BottomRight => Gravity::BottomLeft,
|
||||
rest => rest,
|
||||
};
|
||||
|
||||
let old_anchor = positioner.anchor_edges;
|
||||
positioner.anchor_edges = match positioner.anchor_edges {
|
||||
Anchor::Left => Anchor::Right,
|
||||
Anchor::Right => Anchor::Left,
|
||||
Anchor::TopLeft => Anchor::TopRight,
|
||||
Anchor::BottomLeft => Anchor::BottomRight,
|
||||
Anchor::TopRight => Anchor::TopLeft,
|
||||
Anchor::BottomRight => Anchor::BottomLeft,
|
||||
rest => rest,
|
||||
};
|
||||
|
||||
let mut relative_geo = positioner.get_geometry();
|
||||
relative_geo.loc += loc_diff;
|
||||
tracing::debug!(?relative_geo, "FlipX");
|
||||
if constrained_x(relative_geo).is_none() {
|
||||
popup_rect_global = relative_geo;
|
||||
break 'block;
|
||||
}
|
||||
|
||||
// The protocol states that if flipping it didn't bring it onscreen,
|
||||
// then it should just stay at its unflipped state.
|
||||
positioner.gravity = old_gravity;
|
||||
positioner.anchor_edges = old_anchor;
|
||||
}
|
||||
|
||||
// Finally, if flipping it failed, resize it to fit.
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::ResizeX)
|
||||
{
|
||||
tracing::debug!("Resizing popup X");
|
||||
// Slice off the left side
|
||||
if popup_rect_global.loc.x < output_left_x {
|
||||
let len_to_slice = output_left_x - popup_rect_global.loc.x;
|
||||
let new_top_left: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x + len_to_slice,
|
||||
popup_rect_global.loc.y,
|
||||
)
|
||||
.into();
|
||||
let bottom_right: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x + popup_rect_global.size.w,
|
||||
popup_rect_global.loc.y + popup_rect_global.size.h,
|
||||
)
|
||||
.into();
|
||||
popup_rect_global =
|
||||
Rectangle::from_extemities(new_top_left, bottom_right);
|
||||
}
|
||||
|
||||
// Slice off the right side
|
||||
if popup_rect_global.loc.x + popup_rect_global.size.w > output_right_x {
|
||||
let len_to_slice = (popup_rect_global.loc.x + popup_rect_global.size.w)
|
||||
- output_right_x;
|
||||
let top_left = popup_rect_global.loc;
|
||||
let new_bottom_right: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x + popup_rect_global.size.w - len_to_slice,
|
||||
popup_rect_global.loc.y + popup_rect_global.size.h,
|
||||
)
|
||||
.into();
|
||||
popup_rect_global =
|
||||
Rectangle::from_extemities(top_left, new_bottom_right);
|
||||
}
|
||||
|
||||
if constrained_x(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The popup is flowing offscreen in the vertical direction.
|
||||
if let Some(constrain_dir) = constrained_y(popup_rect_global) {
|
||||
tracing::debug!("Popup was constrained on the y axis, repositioning");
|
||||
let gravity = match positioner.gravity {
|
||||
Gravity::Top | Gravity::TopLeft | Gravity::TopRight => DirV::Top,
|
||||
_ => DirV::Bottom,
|
||||
};
|
||||
|
||||
tracing::debug!(?gravity);
|
||||
tracing::debug!(?constrain_dir);
|
||||
|
||||
'block: {
|
||||
// If the constraint_adjustment has SlideY, we attempt to slide the popup
|
||||
// towards the direction specified by positioner.gravity until TODO:
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::SlideY)
|
||||
&& !is_subpopup
|
||||
// If it's a subpopup, flip instead of slide. This makes
|
||||
// stuff like Firefox nested dropdowns more intuitive.
|
||||
{
|
||||
// Slide towards the gravity until the opposite edge is unconstrained or the
|
||||
// same edge is constrained
|
||||
match (&constrain_dir, &gravity) {
|
||||
(DirV::Top, DirV::Bottom) => {
|
||||
let len_until_same_edge_constrained = output_bottom_y
|
||||
- (popup_rect_global.loc.y + popup_rect_global.size.h);
|
||||
let len_until_opp_edge_unconstrained =
|
||||
output_top_y - popup_rect_global.loc.y;
|
||||
|
||||
popup_rect_global.loc.y += i32::min(
|
||||
len_until_same_edge_constrained,
|
||||
len_until_opp_edge_unconstrained,
|
||||
)
|
||||
.max(0);
|
||||
}
|
||||
(DirV::Bottom, DirV::Top) => {
|
||||
let len_until_same_edge_constrained =
|
||||
popup_rect_global.loc.y - output_top_y;
|
||||
let len_until_opp_edge_unconstrained = (popup_rect_global.loc.y
|
||||
+ popup_rect_global.size.h)
|
||||
- output_bottom_y;
|
||||
|
||||
popup_rect_global.loc.y -= i32::min(
|
||||
len_until_opp_edge_unconstrained,
|
||||
len_until_same_edge_constrained,
|
||||
)
|
||||
.max(0);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if constrained_y(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
|
||||
// Do the same but in the other direction
|
||||
match (constrain_dir, gravity) {
|
||||
(DirV::Bottom, DirV::Bottom) => {
|
||||
let len_until_same_edge_unconstrained =
|
||||
popup_rect_global.loc.y - output_top_y;
|
||||
let len_until_opp_edge_constrained = (popup_rect_global.loc.y
|
||||
+ popup_rect_global.size.h)
|
||||
- output_bottom_y;
|
||||
|
||||
popup_rect_global.loc.y -= i32::min(
|
||||
len_until_opp_edge_constrained,
|
||||
len_until_same_edge_unconstrained,
|
||||
)
|
||||
.max(0);
|
||||
}
|
||||
(DirV::Top, DirV::Top) => {
|
||||
let len_until_same_edge_unconstrained = output_bottom_y
|
||||
- (popup_rect_global.loc.y + popup_rect_global.size.h);
|
||||
let len_until_opp_edge_constrained =
|
||||
output_top_y - popup_rect_global.loc.y;
|
||||
|
||||
popup_rect_global.loc.y += i32::min(
|
||||
len_until_same_edge_unconstrained,
|
||||
len_until_opp_edge_constrained,
|
||||
)
|
||||
.max(0);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if constrained_y(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
|
||||
// If the above didn't bring the popup onscreen or if it's a nested popup, flip it.
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::FlipY)
|
||||
{
|
||||
let old_gravity = positioner.gravity;
|
||||
positioner.gravity = match positioner.gravity {
|
||||
Gravity::Top => Gravity::Bottom,
|
||||
Gravity::Bottom => Gravity::Top,
|
||||
Gravity::TopLeft => Gravity::BottomLeft,
|
||||
Gravity::BottomLeft => Gravity::TopLeft,
|
||||
Gravity::TopRight => Gravity::BottomRight,
|
||||
Gravity::BottomRight => Gravity::TopRight,
|
||||
rest => rest,
|
||||
};
|
||||
|
||||
let old_anchor = positioner.anchor_edges;
|
||||
positioner.anchor_edges = match positioner.anchor_edges {
|
||||
Anchor::Top => Anchor::Bottom,
|
||||
Anchor::Bottom => Anchor::Top,
|
||||
Anchor::TopLeft => Anchor::BottomLeft,
|
||||
Anchor::BottomLeft => Anchor::TopLeft,
|
||||
Anchor::TopRight => Anchor::BottomRight,
|
||||
Anchor::BottomRight => Anchor::TopRight,
|
||||
rest => rest,
|
||||
};
|
||||
|
||||
let mut geo = positioner.get_geometry();
|
||||
tracing::debug!(?geo, "Flipped Y geo");
|
||||
geo.loc += loc_diff;
|
||||
geo.loc.x = popup_rect_global.loc.x;
|
||||
tracing::debug!(?geo, "Flipped Y geo global");
|
||||
if constrained_y(geo).is_none() {
|
||||
popup_rect_global = geo;
|
||||
break 'block;
|
||||
}
|
||||
|
||||
// The protocol states that if flipping it didn't bring it onscreen,
|
||||
// then it should just stay at its unflipped state.
|
||||
positioner.gravity = old_gravity;
|
||||
positioner.anchor_edges = old_anchor;
|
||||
}
|
||||
|
||||
// Finally, if flipping it failed, resize it to fit.
|
||||
if positioner
|
||||
.constraint_adjustment
|
||||
.contains(ConstraintAdjustment::ResizeY)
|
||||
{
|
||||
// Slice off the top side
|
||||
if popup_rect_global.loc.y < output_top_y {
|
||||
let len_to_slice = output_top_y - popup_rect_global.loc.y;
|
||||
let new_top_left: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x,
|
||||
popup_rect_global.loc.y + len_to_slice,
|
||||
)
|
||||
.into();
|
||||
let bottom_right: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x + popup_rect_global.size.w,
|
||||
popup_rect_global.loc.y + popup_rect_global.size.h,
|
||||
)
|
||||
.into();
|
||||
popup_rect_global =
|
||||
Rectangle::from_extemities(new_top_left, bottom_right);
|
||||
}
|
||||
|
||||
// Slice off the right side
|
||||
if popup_rect_global.loc.y + popup_rect_global.size.h > output_bottom_y {
|
||||
let len_to_slice = (popup_rect_global.loc.y + popup_rect_global.size.h)
|
||||
- output_bottom_y;
|
||||
let top_left = popup_rect_global.loc;
|
||||
let new_bottom_right: Point<i32, Logical> = (
|
||||
popup_rect_global.loc.x + popup_rect_global.size.w,
|
||||
popup_rect_global.loc.y + popup_rect_global.size.h - len_to_slice,
|
||||
)
|
||||
.into();
|
||||
popup_rect_global =
|
||||
Rectangle::from_extemities(top_left, new_bottom_right);
|
||||
}
|
||||
|
||||
if constrained_y(popup_rect_global).is_none() {
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!(?popup_rect_global, "New popup");
|
||||
popup_rect_global.loc -= loc_diff;
|
||||
|
||||
surface.with_pending_state(|state| state.geometry = popup_rect_global);
|
||||
surface.with_pending_state(|state| state.geometry = popup_geo);
|
||||
|
||||
if let Err(err) = self.popup_manager.track_popup(PopupKind::from(surface)) {
|
||||
tracing::warn!("failed to track popup: {}", err);
|
||||
|
|
Loading…
Reference in a new issue