use dpi::{LogicalPosition, LogicalSize};
use winit_core::window::{
WindowAnchor, WindowConstraintAdjustment, WindowGravity, WindowPositioner,
};
fn anchor_fraction(anchor: WindowAnchor) -> (f64, f64) {
match anchor {
WindowAnchor::Center => (0.5, 0.5),
WindowAnchor::Top => (0.5, 0.0),
WindowAnchor::Bottom => (0.5, 1.0),
WindowAnchor::Left => (0.0, 0.5),
WindowAnchor::Right => (1.0, 0.5),
WindowAnchor::TopLeft => (0.0, 0.0),
WindowAnchor::BottomLeft => (0.0, 1.0),
WindowAnchor::TopRight => (1.0, 0.0),
WindowAnchor::BottomRight => (1.0, 1.0),
_ => (0.5, 0.5),
}
}
fn gravity_fraction(gravity: WindowGravity) -> (f64, f64) {
match gravity {
WindowGravity::Center => (-0.5, -0.5),
WindowGravity::Top => (-0.5, -1.0),
WindowGravity::Bottom => (-0.5, 0.0),
WindowGravity::Left => (-1.0, -0.5),
WindowGravity::Right => (0.0, -0.5),
WindowGravity::TopLeft => (-1.0, -1.0),
WindowGravity::BottomLeft => (-1.0, 0.0),
WindowGravity::TopRight => (0.0, -1.0),
WindowGravity::BottomRight => (0.0, 0.0),
_ => (-0.5, -0.5),
}
}
fn constrain_axis(
origin: f64,
extent: f64,
(clip_min, clip_max): (f64, f64),
flipped_origin: f64,
(flip, slide, resize): (bool, bool, bool),
) -> (f64, f64) {
let fits = |o: f64| o >= clip_min && o + extent <= clip_max;
let mut origin = origin;
if !fits(origin) && flip && fits(flipped_origin) {
origin = flipped_origin;
}
if !fits(origin) && slide {
let slide_max = (clip_max - extent).max(clip_min);
origin = origin.clamp(clip_min, slide_max);
}
let mut extent = extent;
if resize && !fits(origin) {
let clamped_origin = origin.max(clip_min);
extent = (clip_max.min(origin + extent) - clamped_origin).max(0.0);
origin = clamped_origin;
}
(origin, extent)
}
pub fn place_window(
positioner: &WindowPositioner,
scale_factor: f64,
window_size: LogicalSize<f64>,
(clip_position, clip_size): (LogicalPosition<f64>, LogicalSize<f64>),
) -> (LogicalPosition<f64>, LogicalSize<f64>) {
let anchor = positioner.anchor;
let gravity = positioner.gravity;
let constraint_adjustment = positioner.constraint_adjustment;
let (anchor_position, anchor_size) = positioner.anchor_rect;
let anchor_position = anchor_position.to_logical::<f64>(scale_factor);
let anchor_size = anchor_size.to_logical::<f64>(scale_factor);
let offset = positioner.offset.to_logical::<f64>(scale_factor);
let (anchor_fx, anchor_fy) = anchor_fraction(anchor);
let (gravity_fx, gravity_fy) = gravity_fraction(gravity);
let anchor_point_x = anchor_position.x + anchor_size.width * anchor_fx;
let anchor_point_y = anchor_position.y + anchor_size.height * anchor_fy;
let origin_x = anchor_point_x + window_size.width * gravity_fx + offset.x;
let origin_y = anchor_point_y + window_size.height * gravity_fy + offset.y;
let flipped_anchor_point_x = anchor_position.x + anchor_size.width * (1.0 - anchor_fx);
let flipped_anchor_point_y = anchor_position.y + anchor_size.height * (1.0 - anchor_fy);
let flipped_x = flipped_anchor_point_x + window_size.width * (-1.0 - gravity_fx) + offset.x;
let flipped_y = flipped_anchor_point_y + window_size.height * (-1.0 - gravity_fy) + offset.y;
let clip_min_x = clip_position.x;
let clip_max_x = clip_position.x + clip_size.width;
let clip_min_y = clip_position.y;
let clip_max_y = clip_position.y + clip_size.height;
let (x, width) = constrain_axis(
origin_x,
window_size.width,
(clip_min_x, clip_max_x),
flipped_x,
(
constraint_adjustment.contains(WindowConstraintAdjustment::FLIP_X),
constraint_adjustment.contains(WindowConstraintAdjustment::SLIDE_X),
constraint_adjustment.contains(WindowConstraintAdjustment::RESIZE_X),
),
);
let (y, height) = constrain_axis(
origin_y,
window_size.height,
(clip_min_y, clip_max_y),
flipped_y,
(
constraint_adjustment.contains(WindowConstraintAdjustment::FLIP_Y),
constraint_adjustment.contains(WindowConstraintAdjustment::SLIDE_Y),
constraint_adjustment.contains(WindowConstraintAdjustment::RESIZE_Y),
),
);
(LogicalPosition::new(x, y), LogicalSize::new(width, height))
}
#[cfg(test)]
mod tests {
use dpi::{Position, Size};
use super::*;
const GRID_COLS: usize = 60;
const GRID_ROWS: usize = 24;
const DRAW: bool = false;
#[allow(clippy::disallowed_macros)]
fn draw(
label: &str,
clip: (LogicalPosition<f64>, LogicalSize<f64>),
anchor: (LogicalPosition<f64>, LogicalSize<f64>),
popup: (LogicalPosition<f64>, LogicalSize<f64>),
) {
if !DRAW {
return;
}
let (clip_position, clip_size) = clip;
let (anchor_position, anchor_size) = anchor;
let (popup_position, popup_size) = popup;
let has_clip = clip_size.width > 0.0 && clip_size.height > 0.0;
let mut min_x = anchor_position.x.min(popup_position.x);
let mut min_y = anchor_position.y.min(popup_position.y);
let mut max_x =
(anchor_position.x + anchor_size.width).max(popup_position.x + popup_size.width);
let mut max_y =
(anchor_position.y + anchor_size.height).max(popup_position.y + popup_size.height);
if has_clip {
min_x = min_x.min(clip_position.x);
min_y = min_y.min(clip_position.y);
max_x = max_x.max(clip_position.x + clip_size.width);
max_y = max_y.max(clip_position.y + clip_size.height);
}
let margin_x = ((max_x - min_x) * 0.1).max(1.0);
let margin_y = ((max_y - min_y) * 0.1).max(1.0);
min_x -= margin_x;
min_y -= margin_y;
max_x += margin_x;
max_y += margin_y;
let scale =
(GRID_COLS as f64 / (max_x - min_x)).min(2.0 * GRID_ROWS as f64 / (max_y - min_y));
let mut grid = vec![vec![' '; GRID_COLS]; GRID_ROWS];
let mut plot = |position: LogicalPosition<f64>, size: LogicalSize<f64>, ch: char| {
let x0 = ((position.x - min_x) * scale).round() as isize;
let y0 = ((position.y - min_y) * scale / 2.0).round() as isize;
let x1 = ((position.x + size.width - min_x) * scale).round() as isize;
let y1 = ((position.y + size.height - min_y) * scale / 2.0).round() as isize;
for y in y0.max(0)..y1.min(GRID_ROWS as isize) {
for x in x0.max(0)..x1.min(GRID_COLS as isize) {
let cell = &mut grid[y as usize][x as usize];
*cell = if *cell == ' ' || *cell == ch { ch } else { 'X' };
}
}
};
if has_clip {
plot(clip_position, clip_size, '.');
}
plot(anchor_position, anchor_size, 'A');
plot(popup_position, popup_size, 'P');
println!("--- {label} (A = anchor, P = popup, X = overlap, . = clip region) ---");
for row in grid {
println!("{}", row.into_iter().collect::<String>());
}
}
#[test]
fn test_place_popup_gravity() {
let anchor_position = LogicalPosition::new(100., 100.);
let anchor_size = LogicalSize::new(20., 10.);
let popup_size = LogicalSize::new(40., 30.);
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(0., 0.);
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let place = |anchor: WindowAnchor, gravity: WindowGravity| {
let positioner = WindowPositioner::new(
anchor,
(Position::Logical(anchor_position), Size::Logical(anchor_size)),
offset,
gravity,
WindowConstraintAdjustment::empty(),
);
place_window(&positioner, 1.0, popup_size, (clip_position, clip_size))
};
let (origin, size) = place(WindowAnchor::BottomRight, WindowGravity::BottomRight);
draw(
"gravity: BottomRight anchor + BottomRight gravity",
(clip_position, clip_size),
(anchor_position, anchor_size),
(origin, size),
);
assert_eq!(origin, LogicalPosition::new(120., 110.));
assert_eq!(size, popup_size);
let (origin, size) = place(WindowAnchor::TopLeft, WindowGravity::TopLeft);
draw(
"gravity: TopLeft anchor + TopLeft gravity",
(clip_position, clip_size),
(anchor_position, anchor_size),
(origin, size),
);
assert_eq!(origin, LogicalPosition::new(100. - 40., 100. - 30.));
let (origin, size) = place(WindowAnchor::Bottom, WindowGravity::Bottom);
draw(
"gravity: Bottom anchor + Bottom gravity",
(clip_position, clip_size),
(anchor_position, anchor_size),
(origin, size),
);
assert_eq!(
origin,
LogicalPosition::new(100. + anchor_size.width / 2. - popup_size.width / 2., 110.)
);
let (origin, size) = place(WindowAnchor::Center, WindowGravity::Center);
draw(
"gravity: Center anchor + Center gravity",
(clip_position, clip_size),
(anchor_position, anchor_size),
(origin, size),
);
let anchor_center =
LogicalPosition::new(100. + anchor_size.width / 2., 100. + anchor_size.height / 2.);
assert_eq!(
origin,
LogicalPosition::new(
anchor_center.x - popup_size.width / 2.,
anchor_center.y - popup_size.height / 2.
)
);
}
#[test]
fn test_place_popup_flip() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(300., 300.);
let anchor_position = LogicalPosition::new(280., 100.);
let anchor_size = LogicalSize::new(10., 10.);
let popup_size = LogicalSize::new(50., 50.);
let flip_only = WindowConstraintAdjustment::FLIP_X | WindowConstraintAdjustment::FLIP_Y;
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::TopRight,
(Position::Logical(anchor_position), Size::Logical(anchor_size)),
offset,
WindowGravity::BottomRight,
flip_only,
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw("flip", (clip_position, clip_size), (anchor_position, anchor_size), (origin, size));
assert!(origin.x >= 0. && origin.x + size.width <= 300.);
assert_eq!(size, popup_size);
assert_eq!(origin.x, 280. - popup_size.width);
assert_eq!(origin.y, 100.);
}
#[test]
fn test_place_popup_slide() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(300., 300.);
let anchor_position = LogicalPosition::new(280., 280.);
let popup_size = LogicalSize::new(50., 50.);
let slide_only = WindowConstraintAdjustment::SLIDE_X | WindowConstraintAdjustment::SLIDE_Y;
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::BottomRight,
(Position::Logical(anchor_position), Size::Logical(LogicalSize::new(0., 0.))),
offset,
WindowGravity::BottomRight,
slide_only,
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw(
"slide",
(clip_position, clip_size),
(anchor_position, LogicalSize::new(0., 0.)),
(origin, size),
);
assert_eq!(size, popup_size);
assert_eq!(origin, LogicalPosition::new(250., 250.));
}
#[test]
fn test_place_popup_resize() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(300., 300.);
let anchor_position = LogicalPosition::new(0., 0.);
let popup_size = LogicalSize::new(500., 500.);
let resize_only =
WindowConstraintAdjustment::RESIZE_X | WindowConstraintAdjustment::RESIZE_Y;
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::TopLeft,
(Position::Logical(anchor_position), Size::Logical(LogicalSize::new(0., 0.))),
offset,
WindowGravity::BottomRight,
resize_only,
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw(
"resize",
(clip_position, clip_size),
(anchor_position, LogicalSize::new(0., 0.)),
(origin, size),
);
assert_eq!(origin, clip_position);
assert_eq!(size, clip_size);
}
#[test]
fn test_place_popup_resize_leading_edge_only() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(300., 300.);
let anchor_position = LogicalPosition::new(-20., 0.);
let popup_size = LogicalSize::new(50., 50.);
let resize_only = WindowConstraintAdjustment::RESIZE_X;
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::TopLeft,
(Position::Logical(anchor_position), Size::Logical(LogicalSize::new(0., 0.))),
offset,
WindowGravity::BottomRight,
resize_only,
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw(
"resize (leading edge only)",
(clip_position, clip_size),
(anchor_position, LogicalSize::new(0., 0.)),
(origin, size),
);
assert_eq!(origin, LogicalPosition::new(0., 0.));
assert_eq!(size, LogicalSize::new(30., 50.));
}
#[test]
fn test_place_popup_no_adjustment() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(100., 100.);
let anchor_position = LogicalPosition::new(90., 90.);
let popup_size = LogicalSize::new(50., 50.);
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::TopLeft,
(Position::Logical(anchor_position), Size::Logical(LogicalSize::new(0., 0.))),
offset,
WindowGravity::BottomRight,
WindowConstraintAdjustment::empty(),
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw(
"no adjustment",
(clip_position, clip_size),
(anchor_position, LogicalSize::new(0., 0.)),
(origin, size),
);
assert_eq!(origin, anchor_position);
assert_eq!(size, popup_size);
}
#[test]
fn test_place_popup_all_adjustment_no_op_when_fits() {
let clip_position = LogicalPosition::new(0., 0.);
let clip_size = LogicalSize::new(300., 300.);
let anchor_position = LogicalPosition::new(100., 100.);
let popup_size = LogicalSize::new(50., 50.);
let offset = Position::Logical(LogicalPosition::new(0., 0.));
let positioner = WindowPositioner::new(
WindowAnchor::TopLeft,
(Position::Logical(anchor_position), Size::Logical(LogicalSize::new(0., 0.))),
offset,
WindowGravity::BottomRight,
WindowConstraintAdjustment::all(),
);
let (origin, size) = place_window(&positioner, 1.0, popup_size, (clip_position, clip_size));
draw(
"all adjustment, already fits",
(clip_position, clip_size),
(anchor_position, LogicalSize::new(0., 0.)),
(origin, size),
);
assert_eq!(origin, anchor_position);
assert_eq!(size, popup_size);
}
}