summaryrefslogtreecommitdiffhomepage
path: root/packages/ext-window-field/src/geometry.hpp
blob: 39265f747099959184f7bd671feb31243b30d273 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#pragma once

// Pure decision core: floating-window geometry math. No wlroots, no kernel, no
// RmlUi — just integers in, integers out, so it is heavily doctest-covered
// (tests/test_geometry.cpp). The glue (extension.cpp) holds the per-window Box
// as bound STATE and feeds drag deltas through these functions; RCSS APPLIES the
// result as style (the contract: C++ owns interactive geometry STATE, RCSS owns
// rendering/animation; "gesture recognition: pure input->output" per AGENTS.md).

namespace unbox::ext_window_field::geom {

// A window's on-screen box in field (= output) px. Top-left origin.
struct Box {
    int x = 0;
    int y = 0;
    int w = 0;
    int h = 0;
};

inline auto operator==(const Box& a, const Box& b) -> bool {
    return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
}

// Which chrome control is being dragged. `move` = the titlebar (translate the
// whole box); the two resize grips live at the BOTTOM corners (the top edge is
// fixed because the titlebar lives there):
//   resize_bl — bottom-LEFT grip: the left edge + bottom edge follow the pointer
//               (x and w change, the right edge x+w stays put; h changes).
//   resize_br — bottom-RIGHT grip: the right edge + bottom edge follow (w and h
//               change; x and y stay put).
enum class Handle { move, resize_bl, resize_br };

// Minimum window size (px). A resize never shrinks below this; the opposite edge
// stays anchored so the window does not jump when it hits the floor.
struct Limits {
    int min_w = 240;
    int min_h = 160;
};

// Apply a drag to the box captured at drag-START. `dx,dy` is the CUMULATIVE
// pointer delta since the drag began (pointer_now - pointer_at_start). Pure: the
// same inputs always give the same Box, so the glue just records the start box +
// start pointer once and calls this every move/end phase. Honors `lim` (min
// size with the anchored-opposite-edge rule); does NOT clamp to the field — see
// clamp_to_field for that (kept separate so move and resize share one min-size
// core and the field bound is applied once at the end).
[[nodiscard]] auto apply_drag(Box start, Handle handle, int dx, int dy, Limits lim) -> Box;

// Clamp a box's ORIGIN so the whole window stays within a field of field_w ×
// field_h px (a window larger than the field is pinned to the top-left). Keeps
// the titlebar reachable — a window can never be dragged entirely off-screen.
// Size is never changed here. A non-positive field dimension is treated as "no
// bound on that axis" (returns the box unclamped on that axis).
[[nodiscard]] auto clamp_to_field(Box b, int field_w, int field_h) -> Box;

} // namespace unbox::ext_window_field::geom