detached 251cbdf

refactor(graphics): share rectangle arithmetic

commit
251cbdf88c66a7952b5405a45e150782ca802bf6
author
brice · · 2026-08-23 16:17 UTC
parent
78f404d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +56 -52
M graphics/rect.go +56 -52
@@ -24,6 +24,14 @@ type CellRect struct {
2424 Width, Height int64
2525 }
2626
27+type rect struct {
28+ x, y int64
29+ width, height int64
30+}
31+
32+func pixelRect(r PixelRect) rect { return rect{x: r.X, y: r.Y, width: r.Width, height: r.Height} }
33+func cellRect(r CellRect) rect { return rect{x: r.X, y: r.Y, width: r.Width, height: r.Height} }
34+
2735 // NewPixelRect constructs a checked pixel rectangle.
2836 func NewPixelRect(x, y, width, height int64) (PixelRect, error) {
2937 r := PixelRect{X: x, Y: y, Width: width, Height: height}
@@ -42,92 +50,88 @@ func NewCellRect(x, y, width, height int64) (CellRect, error) {
4250 return r, nil
4351 }
4452
53+func (r rect) valid() bool {
54+ return r.width >= 0 && r.height >= 0 && edge(r.x, r.width) && edge(r.y, r.height)
55+}
56+
57+func (r rect) empty() bool { return r.width == 0 || r.height == 0 }
58+
59+func (r rect) right() (int64, bool) { return checkedEdge(r.x, r.width) }
60+
61+func (r rect) bottom() (int64, bool) { return checkedEdge(r.y, r.height) }
62+
63+func (r rect) contains(x, y int64) bool {
64+ right, okRight := r.right()
65+ bottom, okBottom := r.bottom()
66+ return okRight && okBottom && x >= r.x && x < right && y >= r.y && y < bottom
67+}
68+
69+func (r rect) intersect(other rect) (rect, bool) {
70+ if !r.valid() || !other.valid() || r.empty() || other.empty() {
71+ return rect{}, false
72+ }
73+ right, _ := r.right()
74+ otherRight, _ := other.right()
75+ bottom, _ := r.bottom()
76+ otherBottom, _ := other.bottom()
77+ left := max(r.x, other.x)
78+ top := max(r.y, other.y)
79+ right = min(right, otherRight)
80+ bottom = min(bottom, otherBottom)
81+ if right <= left || bottom <= top {
82+ return rect{}, false
83+ }
84+ return rect{x: left, y: top, width: right - left, height: bottom - top}, true
85+}
86+
4587 // Valid reports whether the rectangle's dimensions and exclusive edges are
4688 // representable without signed overflow.
47-func (r PixelRect) Valid() bool {
48- return r.Width >= 0 && r.Height >= 0 && edge(r.X, r.Width) && edge(r.Y, r.Height)
49-}
89+func (r PixelRect) Valid() bool { return pixelRect(r).valid() }
5090
5191 // Valid reports whether the rectangle's dimensions and exclusive edges are
5292 // representable without signed overflow.
53-func (r CellRect) Valid() bool {
54- return r.Width >= 0 && r.Height >= 0 && edge(r.X, r.Width) && edge(r.Y, r.Height)
55-}
93+func (r CellRect) Valid() bool { return cellRect(r).valid() }
5694
5795 // Empty reports whether the rectangle has no area.
58-func (r PixelRect) Empty() bool { return r.Width == 0 || r.Height == 0 }
96+func (r PixelRect) Empty() bool { return pixelRect(r).empty() }
5997
6098 // Empty reports whether the rectangle has no area.
61-func (r CellRect) Empty() bool { return r.Width == 0 || r.Height == 0 }
99+func (r CellRect) Empty() bool { return cellRect(r).empty() }
62100
63101 // Right returns the exclusive right edge. The boolean is false for an
64102 // invalid rectangle; it is never produced by arithmetic that wraps.
65-func (r PixelRect) Right() (int64, bool) { return checkedEdge(r.X, r.Width) }
103+func (r PixelRect) Right() (int64, bool) { return pixelRect(r).right() }
66104
67105 // Bottom returns the exclusive bottom edge. The boolean is false for an
68106 // invalid rectangle; it is never produced by arithmetic that wraps.
69-func (r PixelRect) Bottom() (int64, bool) { return checkedEdge(r.Y, r.Height) }
107+func (r PixelRect) Bottom() (int64, bool) { return pixelRect(r).bottom() }
70108
71109 // Right returns the exclusive right edge. The boolean is false for an
72110 // invalid rectangle; it is never produced by arithmetic that wraps.
73-func (r CellRect) Right() (int64, bool) { return checkedEdge(r.X, r.Width) }
111+func (r CellRect) Right() (int64, bool) { return cellRect(r).right() }
74112
75113 // Bottom returns the exclusive bottom edge. The boolean is false for an
76114 // invalid rectangle; it is never produced by arithmetic that wraps.
77-func (r CellRect) Bottom() (int64, bool) { return checkedEdge(r.Y, r.Height) }
115+func (r CellRect) Bottom() (int64, bool) { return cellRect(r).bottom() }
78116
79117 // Contains reports whether a point is inside the half-open rectangle.
80-func (r PixelRect) Contains(x, y int64) bool {
81- right, okRight := r.Right()
82- bottom, okBottom := r.Bottom()
83- return okRight && okBottom && x >= r.X && x < right && y >= r.Y && y < bottom
84-}
118+func (r PixelRect) Contains(x, y int64) bool { return pixelRect(r).contains(x, y) }
85119
86120 // Contains reports whether a point is inside the half-open rectangle.
87-func (r CellRect) Contains(x, y int64) bool {
88- right, okRight := r.Right()
89- bottom, okBottom := r.Bottom()
90- return okRight && okBottom && x >= r.X && x < right && y >= r.Y && y < bottom
91-}
121+func (r CellRect) Contains(x, y int64) bool { return cellRect(r).contains(x, y) }
92122
93123 // Intersect returns the non-empty intersection of two rectangles. Invalid or
94124 // empty rectangles do not intersect. The result is safe even when the input
95125 // edges are close to the int64 limits.
96126 func (r PixelRect) Intersect(other PixelRect) (PixelRect, bool) {
97- if !r.Valid() || !other.Valid() || r.Empty() || other.Empty() {
98- return PixelRect{}, false
99- }
100- right, _ := r.Right()
101- otherRight, _ := other.Right()
102- bottom, _ := r.Bottom()
103- otherBottom, _ := other.Bottom()
104- left := max(r.X, other.X)
105- top := max(r.Y, other.Y)
106- right = min(right, otherRight)
107- bottom = min(bottom, otherBottom)
108- if right <= left || bottom <= top {
109- return PixelRect{}, false
110- }
111- return PixelRect{X: left, Y: top, Width: right - left, Height: bottom - top}, true
127+ intersect, ok := pixelRect(r).intersect(pixelRect(other))
128+ return PixelRect{X: intersect.x, Y: intersect.y, Width: intersect.width, Height: intersect.height}, ok
112129 }
113130
114131 // Intersect returns the non-empty intersection of two rectangles.
115132 func (r CellRect) Intersect(other CellRect) (CellRect, bool) {
116- if !r.Valid() || !other.Valid() || r.Empty() || other.Empty() {
117- return CellRect{}, false
118- }
119- right, _ := r.Right()
120- otherRight, _ := other.Right()
121- bottom, _ := r.Bottom()
122- otherBottom, _ := other.Bottom()
123- left := max(r.X, other.X)
124- top := max(r.Y, other.Y)
125- right = min(right, otherRight)
126- bottom = min(bottom, otherBottom)
127- if right <= left || bottom <= top {
128- return CellRect{}, false
129- }
130- return CellRect{X: left, Y: top, Width: right - left, Height: bottom - top}, true
133+ intersect, ok := cellRect(r).intersect(cellRect(other))
134+ return CellRect{X: intersect.x, Y: intersect.y, Width: intersect.width, Height: intersect.height}, ok
131135 }
132136
133137 func edge(start, length int64) bool {
@@ -24,6 +24,14 @@ type CellRect struct {
24 Width, Height int6424 Width, Height int64
25 }25 }
26 26
27+type rect struct {
28+ x, y int64
29+ width, height int64
30+}
31+
32+func pixelRect(r PixelRect) rect { return rect{x: r.X, y: r.Y, width: r.Width, height: r.Height} }
33+func cellRect(r CellRect) rect { return rect{x: r.X, y: r.Y, width: r.Width, height: r.Height} }
34+
27 // NewPixelRect constructs a checked pixel rectangle.35 // NewPixelRect constructs a checked pixel rectangle.
28 func NewPixelRect(x, y, width, height int64) (PixelRect, error) {36 func NewPixelRect(x, y, width, height int64) (PixelRect, error) {
29 r := PixelRect{X: x, Y: y, Width: width, Height: height}37 r := PixelRect{X: x, Y: y, Width: width, Height: height}
@@ -42,92 +50,88 @@ func NewCellRect(x, y, width, height int64) (CellRect, error) {
42 return r, nil50 return r, nil
43 }51 }
44 52
53+func (r rect) valid() bool {
54+ return r.width >= 0 && r.height >= 0 && edge(r.x, r.width) && edge(r.y, r.height)
55+}
56+
57+func (r rect) empty() bool { return r.width == 0 || r.height == 0 }
58+
59+func (r rect) right() (int64, bool) { return checkedEdge(r.x, r.width) }
60+
61+func (r rect) bottom() (int64, bool) { return checkedEdge(r.y, r.height) }
62+
63+func (r rect) contains(x, y int64) bool {
64+ right, okRight := r.right()
65+ bottom, okBottom := r.bottom()
66+ return okRight && okBottom && x >= r.x && x < right && y >= r.y && y < bottom
67+}
68+
69+func (r rect) intersect(other rect) (rect, bool) {
70+ if !r.valid() || !other.valid() || r.empty() || other.empty() {
71+ return rect{}, false
72+ }
73+ right, _ := r.right()
74+ otherRight, _ := other.right()
75+ bottom, _ := r.bottom()
76+ otherBottom, _ := other.bottom()
77+ left := max(r.x, other.x)
78+ top := max(r.y, other.y)
79+ right = min(right, otherRight)
80+ bottom = min(bottom, otherBottom)
81+ if right <= left || bottom <= top {
82+ return rect{}, false
83+ }
84+ return rect{x: left, y: top, width: right - left, height: bottom - top}, true
85+}
86+
45 // Valid reports whether the rectangle's dimensions and exclusive edges are87 // Valid reports whether the rectangle's dimensions and exclusive edges are
46 // representable without signed overflow.88 // representable without signed overflow.
47-func (r PixelRect) Valid() bool {89+func (r PixelRect) Valid() bool { return pixelRect(r).valid() }
48- return r.Width >= 0 && r.Height >= 0 && edge(r.X, r.Width) && edge(r.Y, r.Height)
49-}
50 90
51 // Valid reports whether the rectangle's dimensions and exclusive edges are91 // Valid reports whether the rectangle's dimensions and exclusive edges are
52 // representable without signed overflow.92 // representable without signed overflow.
53-func (r CellRect) Valid() bool {93+func (r CellRect) Valid() bool { return cellRect(r).valid() }
54- return r.Width >= 0 && r.Height >= 0 && edge(r.X, r.Width) && edge(r.Y, r.Height)
55-}
56 94
57 // Empty reports whether the rectangle has no area.95 // Empty reports whether the rectangle has no area.
58-func (r PixelRect) Empty() bool { return r.Width == 0 || r.Height == 0 }96+func (r PixelRect) Empty() bool { return pixelRect(r).empty() }
59 97
60 // Empty reports whether the rectangle has no area.98 // Empty reports whether the rectangle has no area.
61-func (r CellRect) Empty() bool { return r.Width == 0 || r.Height == 0 }99+func (r CellRect) Empty() bool { return cellRect(r).empty() }
62 100
63 // Right returns the exclusive right edge. The boolean is false for an101 // Right returns the exclusive right edge. The boolean is false for an
64 // invalid rectangle; it is never produced by arithmetic that wraps.102 // invalid rectangle; it is never produced by arithmetic that wraps.
65-func (r PixelRect) Right() (int64, bool) { return checkedEdge(r.X, r.Width) }103+func (r PixelRect) Right() (int64, bool) { return pixelRect(r).right() }
66 104
67 // Bottom returns the exclusive bottom edge. The boolean is false for an105 // Bottom returns the exclusive bottom edge. The boolean is false for an
68 // invalid rectangle; it is never produced by arithmetic that wraps.106 // invalid rectangle; it is never produced by arithmetic that wraps.
69-func (r PixelRect) Bottom() (int64, bool) { return checkedEdge(r.Y, r.Height) }107+func (r PixelRect) Bottom() (int64, bool) { return pixelRect(r).bottom() }
70 108
71 // Right returns the exclusive right edge. The boolean is false for an109 // Right returns the exclusive right edge. The boolean is false for an
72 // invalid rectangle; it is never produced by arithmetic that wraps.110 // invalid rectangle; it is never produced by arithmetic that wraps.
73-func (r CellRect) Right() (int64, bool) { return checkedEdge(r.X, r.Width) }111+func (r CellRect) Right() (int64, bool) { return cellRect(r).right() }
74 112
75 // Bottom returns the exclusive bottom edge. The boolean is false for an113 // Bottom returns the exclusive bottom edge. The boolean is false for an
76 // invalid rectangle; it is never produced by arithmetic that wraps.114 // invalid rectangle; it is never produced by arithmetic that wraps.
77-func (r CellRect) Bottom() (int64, bool) { return checkedEdge(r.Y, r.Height) }115+func (r CellRect) Bottom() (int64, bool) { return cellRect(r).bottom() }
78 116
79 // Contains reports whether a point is inside the half-open rectangle.117 // Contains reports whether a point is inside the half-open rectangle.
80-func (r PixelRect) Contains(x, y int64) bool {118+func (r PixelRect) Contains(x, y int64) bool { return pixelRect(r).contains(x, y) }
81- right, okRight := r.Right()
82- bottom, okBottom := r.Bottom()
83- return okRight && okBottom && x >= r.X && x < right && y >= r.Y && y < bottom
84-}
85 119
86 // Contains reports whether a point is inside the half-open rectangle.120 // Contains reports whether a point is inside the half-open rectangle.
87-func (r CellRect) Contains(x, y int64) bool {121+func (r CellRect) Contains(x, y int64) bool { return cellRect(r).contains(x, y) }
88- right, okRight := r.Right()
89- bottom, okBottom := r.Bottom()
90- return okRight && okBottom && x >= r.X && x < right && y >= r.Y && y < bottom
91-}
92 122
93 // Intersect returns the non-empty intersection of two rectangles. Invalid or123 // Intersect returns the non-empty intersection of two rectangles. Invalid or
94 // empty rectangles do not intersect. The result is safe even when the input124 // empty rectangles do not intersect. The result is safe even when the input
95 // edges are close to the int64 limits.125 // edges are close to the int64 limits.
96 func (r PixelRect) Intersect(other PixelRect) (PixelRect, bool) {126 func (r PixelRect) Intersect(other PixelRect) (PixelRect, bool) {
97- if !r.Valid() || !other.Valid() || r.Empty() || other.Empty() {127+ intersect, ok := pixelRect(r).intersect(pixelRect(other))
98- return PixelRect{}, false128+ return PixelRect{X: intersect.x, Y: intersect.y, Width: intersect.width, Height: intersect.height}, ok
99- }
100- right, _ := r.Right()
101- otherRight, _ := other.Right()
102- bottom, _ := r.Bottom()
103- otherBottom, _ := other.Bottom()
104- left := max(r.X, other.X)
105- top := max(r.Y, other.Y)
106- right = min(right, otherRight)
107- bottom = min(bottom, otherBottom)
108- if right <= left || bottom <= top {
109- return PixelRect{}, false
110- }
111- return PixelRect{X: left, Y: top, Width: right - left, Height: bottom - top}, true
112 }129 }
113 130
114 // Intersect returns the non-empty intersection of two rectangles.131 // Intersect returns the non-empty intersection of two rectangles.
115 func (r CellRect) Intersect(other CellRect) (CellRect, bool) {132 func (r CellRect) Intersect(other CellRect) (CellRect, bool) {
116- if !r.Valid() || !other.Valid() || r.Empty() || other.Empty() {133+ intersect, ok := cellRect(r).intersect(cellRect(other))
117- return CellRect{}, false134+ return CellRect{X: intersect.x, Y: intersect.y, Width: intersect.width, Height: intersect.height}, ok
118- }
119- right, _ := r.Right()
120- otherRight, _ := other.Right()
121- bottom, _ := r.Bottom()
122- otherBottom, _ := other.Bottom()
123- left := max(r.X, other.X)
124- top := max(r.Y, other.Y)
125- right = min(right, otherRight)
126- bottom = min(bottom, otherBottom)
127- if right <= left || bottom <= top {
128- return CellRect{}, false
129- }
130- return CellRect{X: left, Y: top, Width: right - left, Height: bottom - top}, true
131 }135 }
132 136
133 func edge(start, length int64) bool {137 func edge(start, length int64) bool {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close