Skip to content

Commit 038b168

Browse files
MyronKochclaude
andauthored
Add overlap offset for grid position cycling (#1762)
* Add CLAUDE.md with codebase documentation for AI assistants Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add cycling overlap offset for grid position cycling When cycling through grid positions (sixths, eighths, ninths, twelfths, sixteenths, or quarters with quadrant cycling mode), windows that land on a position already occupied by another window now receive a small offset so the user can see there is a window underneath. The feature is off by default and enabled via hidden preference: defaults write com.knollsoft.Rectangle cyclingOverlapOffset -bool true Configurable options: - cyclingOverlapOffsetSize (default 11pt) controls the offset distance - cyclingOverlapMaxCascade (default 1) controls max cascade layers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add settings checkbox and make overlap offset universal - Add "Offset cycling position on overlap" checkbox to the Grid Positions section of the extra settings popover - Apply overlap detection to all window actions, not just cycling - Match on origin point instead of exact frame, so mixed sizes at the same grid position (e.g. quarter + eighth) trigger the offset Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add hover count badge for overlapping window stacks Show a live count badge when the mouse hovers over a grid position with multiple stacked windows. Uses timer-based polling (200ms) of NSEvent.mouseLocation for reliable hover detection that survives sleep/wake cycles. The count is queried live via Accessibility APIs on each hover, so it always reflects the current window state. Also updates default offset to 11pt and changes overlap detection to origin-point matching for mixed-size windows at the same grid position. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address multi-model review findings - Re-add positionCycles guard to skip AX enumeration on non-grid actions (maximize, restore, center, etc.) - Guard overlapOffset <= 0 to prevent silent no-op or corner-shoving - Cap maxCascade at 5 to prevent spin loops with extreme values - Cap stackedRegions at 20 entries to prevent unbounded growth - Call OverlapCountBadge.clearAll() when feature is toggled off - Add three new defaults to Defaults.array for config export/import - Remove imageHugsTitle from checkbox (no-op on standard checkboxes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add unit tests for overlap offset feature 23 tests covering: - positionCycles property: grid actions return true, non-grid false - OverlapCountBadge: record, deduplicate, remove, clearAll, max cap - screenFlipped: self-inverse property, null handling, negative coords - Defaults.array: all 3 overlap prefs included for config export - Offset guards: maxCascade clamping, screen boundary clamping on primary and secondary monitor coordinate spaces Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove hover count badge for separate PR The overlap count badge (OverlapCountBadge.swift) has architectural issues with stale window state tracking that cause incorrect badge positioning and counts. The core overlap offset feature is solid and well-tested independently. The badge will be reworked and submitted as a separate PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove CLAUDE.md per maintainer preference Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Broaden positionCycles to cover all grid snap actions Invert the logic: instead of listing which actions cycle, exclude the non-positional actions (maximize, restore, center, move, resize, display changes). This ensures halves, thirds, fourths, quarters, and all grid sizes get overlap detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 216d6a8 commit 038b168

6 files changed

Lines changed: 315 additions & 13 deletions

File tree

Rectangle/Defaults.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class Defaults {
6464
static let attemptMatchOnNextPrevDisplay = OptionalBoolDefault(key: "attemptMatchOnNextPrevDisplay")
6565
static let altThirdCycle = OptionalBoolDefault(key: "altThirdCycle")
6666
static let centerHalfCycles = OptionalBoolDefault(key: "centerHalfCycles")
67+
static let cyclingOverlapOffset = OptionalBoolDefault(key: "cyclingOverlapOffset")
68+
static let cyclingOverlapOffsetSize = FloatDefault(key: "cyclingOverlapOffsetSize", defaultValue: 11)
69+
static let cyclingOverlapMaxCascade = IntDefault(key: "cyclingOverlapMaxCascade", defaultValue: 1)
6770
static let fullIgnoreBundleIds = JSONDefault<[String]>(key: "fullIgnoreBundleIds")
6871
static let notifiedOfProblemApps = BoolDefault(key: "notifiedOfProblemApps")
6972
static let specifiedHeight = FloatDefault(key: "specifiedHeight", defaultValue: 1050)
@@ -183,7 +186,10 @@ class Defaults {
183186
systemWideMouseDown,
184187
systemWideMouseDownApps,
185188
screensOrderedByX,
186-
showAdditionalSizesInMenu
189+
showAdditionalSizesInMenu,
190+
cyclingOverlapOffset,
191+
cyclingOverlapOffsetSize,
192+
cyclingOverlapMaxCascade
187193
]
188194
}
189195

Rectangle/PrefsWindow/SettingsViewController.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ class SettingsViewController: NSViewController {
112112
Defaults.showAdditionalSizesInMenu.enabled = enabled
113113
Notification.Name.showAdditionalSizesInMenuChanged.post()
114114
}
115+
116+
@objc func toggleCyclingOverlapOffset(_ sender: NSButton) {
117+
Defaults.cyclingOverlapOffset.enabled = sender.state == .on
118+
}
115119

116120
@IBAction func checkForUpdates(_ sender: Any) {
117121
AppDelegate.instance.updaterController?.checkForUpdates(sender)
@@ -812,10 +816,16 @@ class SettingsViewController: NSViewController {
812816
sixteenthsCyclingShortcutView.shortcutValidator = passThroughValidator
813817
}
814818

819+
let overlapOffsetCheckbox = NSButton(checkboxWithTitle: NSLocalizedString("Offset cycling position on overlap", tableName: "Main", value: "", comment: ""), target: self, action: #selector(toggleCyclingOverlapOffset(_:)))
820+
overlapOffsetCheckbox.state = Defaults.cyclingOverlapOffset.userEnabled ? .on : .off
821+
overlapOffsetCheckbox.translatesAutoresizingMaskIntoConstraints = false
822+
overlapOffsetCheckbox.alignment = .left
823+
815824
mainStackView.addArrangedSubview(gridHeaderLabel)
816825
mainStackView.setCustomSpacing(4, after: gridHeaderLabel)
817826
mainStackView.addArrangedSubview(showAdditionalSizesCheckbox)
818-
mainStackView.setCustomSpacing(8, after: showAdditionalSizesCheckbox)
827+
mainStackView.addArrangedSubview(overlapOffsetCheckbox)
828+
mainStackView.setCustomSpacing(8, after: overlapOffsetCheckbox)
819829
mainStackView.addArrangedSubview(cyclingHintLabel)
820830
mainStackView.setCustomSpacing(8, after: cyclingHintLabel)
821831
mainStackView.addArrangedSubview(topLeftEighthRow)
@@ -883,6 +893,7 @@ class SettingsViewController: NSViewController {
883893
hSplitField.widthAnchor.constraint(equalToConstant: 160),
884894
vSplitField.widthAnchor.constraint(equalToConstant: 160),
885895
showAdditionalSizesCheckbox.leadingAnchor.constraint(equalTo: largerWidthShortcutView.leadingAnchor),
896+
overlapOffsetCheckbox.leadingAnchor.constraint(equalTo: largerWidthShortcutView.leadingAnchor),
886897
smallerWidthShortcutView.leadingAnchor.constraint(equalTo: largerWidthShortcutView.leadingAnchor),
887898
topVerticalThirdShortcutView.leadingAnchor.constraint(equalTo: largerWidthShortcutView.leadingAnchor),
888899
middleVerticalThirdShortcutView.leadingAnchor.constraint(equalTo: largerWidthShortcutView.leadingAnchor),

Rectangle/WindowAction.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,27 @@ enum WindowAction: Int, Codable {
905905
}
906906
}
907907

908+
var positionCycles: Bool {
909+
switch self {
910+
case .maximize, .almostMaximize, .maximizeHeight,
911+
.larger, .smaller, .largerWidth, .smallerWidth, .largerHeight, .smallerHeight,
912+
.center, .centerProminently,
913+
.restore,
914+
.nextDisplay, .previousDisplay,
915+
.displayOne, .displayTwo, .displayThree, .displayFour, .displayFive,
916+
.displaySix, .displaySeven, .displayEight, .displayNine,
917+
.moveLeft, .moveRight, .moveUp, .moveDown,
918+
.doubleHeightUp, .doubleHeightDown, .doubleWidthLeft, .doubleWidthRight,
919+
.halveHeightUp, .halveHeightDown, .halveWidthLeft, .halveWidthRight,
920+
.reverseAll, .tileAll, .cascadeAll, .cascadeActiveApp, .tileActiveApp,
921+
.leftTodo, .rightTodo,
922+
.specified:
923+
return false
924+
default:
925+
return true
926+
}
927+
}
928+
908929
var category: WindowActionCategory? { // used to specify a submenu
909930
switch self {
910931
case .firstThird, .centerThird, .lastThird, .firstTwoThirds, .centerTwoThirds, .lastTwoThirds: return .thirds

Rectangle/WindowManager.swift

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,15 @@ class WindowManager {
124124
calcResult.rect = GapCalculation.applyGaps(calcResult.rect, dimension: gapsApplicable, sharedEdges: gapSharedEdges, gapSize: Defaults.gapSize.value)
125125
}
126126

127+
if Defaults.cyclingOverlapOffset.userEnabled, action.positionCycles {
128+
calcResult.rect = applyOverlapOffsetIfNeeded(calcResult.rect, windowId: windowId, screen: calcResult.screen)
129+
}
130+
127131
if currentNormalizedRect.equalTo(calcResult.rect) {
128132
Logger.log("Current frame is equal to new frame")
129-
133+
130134
recordAction(windowId: windowId, resultingRect: currentWindowRect, action: calcResult.resultingAction, subAction: calcResult.resultingSubAction)
131-
135+
132136
return
133137
}
134138

@@ -196,6 +200,64 @@ class WindowManager {
196200
}
197201
}
198202

203+
private func applyOverlapOffsetIfNeeded(_ rect: CGRect, windowId: CGWindowID, screen: NSScreen) -> CGRect {
204+
let overlapOffset = CGFloat(Defaults.cyclingOverlapOffsetSize.value)
205+
guard overlapOffset > 0 else { return rect }
206+
207+
let screenFrameAX = screen.adjustedVisibleFrame().screenFlipped
208+
let tolerance: CGFloat = 4
209+
let maxCascade = min(5, max(1, Defaults.cyclingOverlapMaxCascade.value))
210+
211+
let otherWindows = AccessibilityElement.getAllWindowElements().filter { element in
212+
guard element.getWindowId() != windowId,
213+
element.isWindow == true,
214+
element.isMinimized != true,
215+
element.isHidden != true,
216+
element.isSheet != true
217+
else { return false }
218+
219+
let frame = element.frame
220+
return !frame.isNull && screenFrameAX.intersects(frame)
221+
}
222+
223+
let screenFrameNormalized = screen.adjustedVisibleFrame()
224+
var candidate = rect
225+
var cascadeLevel = 0
226+
227+
while cascadeLevel < maxCascade {
228+
let candidateAX = candidate.screenFlipped
229+
let hasOverlap = otherWindows.contains { element in
230+
let otherFrame = element.frame
231+
return abs(otherFrame.origin.x - candidateAX.origin.x) < tolerance
232+
&& abs(otherFrame.origin.y - candidateAX.origin.y) < tolerance
233+
}
234+
235+
guard hasOverlap else { break }
236+
237+
candidate.origin.x += overlapOffset
238+
candidate.origin.y += overlapOffset
239+
cascadeLevel += 1
240+
241+
if candidate.origin.x + candidate.width > screenFrameNormalized.maxX {
242+
candidate.origin.x = screenFrameNormalized.maxX - candidate.width
243+
}
244+
if candidate.origin.y + candidate.height > screenFrameNormalized.maxY {
245+
candidate.origin.y = screenFrameNormalized.maxY - candidate.height
246+
}
247+
if candidate.origin.x < screenFrameNormalized.origin.x {
248+
candidate.origin.x = screenFrameNormalized.origin.x
249+
}
250+
if candidate.origin.y < screenFrameNormalized.origin.y {
251+
candidate.origin.y = screenFrameNormalized.origin.y
252+
}
253+
}
254+
255+
if cascadeLevel > 0 {
256+
Logger.log("Cycling overlap detected, applied \(cascadeLevel) x \(overlapOffset)pt cascade offset")
257+
}
258+
return candidate
259+
}
260+
199261
func postProcess(result: ResultParameters, resultingRect: CGRect) {
200262
let calcResult = result.calcResult
201263

RectangleTests/RectangleTests.swift

Lines changed: 190 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,204 @@ import XCTest
1212
class RectangleTests: XCTestCase {
1313

1414
override func setUp() {
15-
// Put setup code here. This method is called before the invocation of each test method in the class.
1615
}
1716

1817
override func tearDown() {
19-
// Put teardown code here. This method is called after the invocation of each test method in the class.
2018
}
19+
}
20+
21+
class PositionCyclesTests: XCTestCase {
22+
23+
func testSixthsReturnTrue() {
24+
XCTAssertTrue(WindowAction.topLeftSixth.positionCycles)
25+
XCTAssertTrue(WindowAction.topCenterSixth.positionCycles)
26+
XCTAssertTrue(WindowAction.topRightSixth.positionCycles)
27+
XCTAssertTrue(WindowAction.bottomLeftSixth.positionCycles)
28+
XCTAssertTrue(WindowAction.bottomCenterSixth.positionCycles)
29+
XCTAssertTrue(WindowAction.bottomRightSixth.positionCycles)
30+
}
31+
32+
func testEighthsReturnTrue() {
33+
XCTAssertTrue(WindowAction.topLeftEighth.positionCycles)
34+
XCTAssertTrue(WindowAction.topCenterLeftEighth.positionCycles)
35+
XCTAssertTrue(WindowAction.bottomRightEighth.positionCycles)
36+
}
37+
38+
func testNinthsReturnTrue() {
39+
XCTAssertTrue(WindowAction.topLeftNinth.positionCycles)
40+
XCTAssertTrue(WindowAction.middleCenterNinth.positionCycles)
41+
XCTAssertTrue(WindowAction.bottomRightNinth.positionCycles)
42+
}
43+
44+
func testTwelfthsReturnTrue() {
45+
XCTAssertTrue(WindowAction.topLeftTwelfth.positionCycles)
46+
XCTAssertTrue(WindowAction.middleCenterLeftTwelfth.positionCycles)
47+
XCTAssertTrue(WindowAction.bottomRightTwelfth.positionCycles)
48+
}
49+
50+
func testSixteenthsReturnTrue() {
51+
XCTAssertTrue(WindowAction.topLeftSixteenth.positionCycles)
52+
XCTAssertTrue(WindowAction.upperMiddleCenterLeftSixteenth.positionCycles)
53+
XCTAssertTrue(WindowAction.lowerMiddleRightSixteenth.positionCycles)
54+
XCTAssertTrue(WindowAction.bottomRightSixteenth.positionCycles)
55+
}
56+
57+
func testGridPositionsReturnTrue() {
58+
XCTAssertTrue(WindowAction.leftHalf.positionCycles)
59+
XCTAssertTrue(WindowAction.rightHalf.positionCycles)
60+
XCTAssertTrue(WindowAction.topLeft.positionCycles)
61+
XCTAssertTrue(WindowAction.bottomRight.positionCycles)
62+
XCTAssertTrue(WindowAction.firstThird.positionCycles)
63+
XCTAssertTrue(WindowAction.lastThird.positionCycles)
64+
XCTAssertTrue(WindowAction.firstFourth.positionCycles)
65+
XCTAssertTrue(WindowAction.topHalf.positionCycles)
66+
XCTAssertTrue(WindowAction.bottomHalf.positionCycles)
67+
}
68+
69+
func testNonPositionalActionsReturnFalse() {
70+
XCTAssertFalse(WindowAction.maximize.positionCycles)
71+
XCTAssertFalse(WindowAction.maximizeHeight.positionCycles)
72+
XCTAssertFalse(WindowAction.almostMaximize.positionCycles)
73+
XCTAssertFalse(WindowAction.center.positionCycles)
74+
XCTAssertFalse(WindowAction.centerProminently.positionCycles)
75+
XCTAssertFalse(WindowAction.restore.positionCycles)
76+
XCTAssertFalse(WindowAction.moveLeft.positionCycles)
77+
XCTAssertFalse(WindowAction.moveRight.positionCycles)
78+
XCTAssertFalse(WindowAction.nextDisplay.positionCycles)
79+
XCTAssertFalse(WindowAction.previousDisplay.positionCycles)
80+
XCTAssertFalse(WindowAction.larger.positionCycles)
81+
XCTAssertFalse(WindowAction.smaller.positionCycles)
82+
XCTAssertFalse(WindowAction.tileAll.positionCycles)
83+
XCTAssertFalse(WindowAction.cascadeAll.positionCycles)
84+
XCTAssertFalse(WindowAction.specified.positionCycles)
85+
}
86+
}
87+
88+
class ScreenFlippedTests: XCTestCase {
89+
90+
func testScreenFlippedIsOwnInverse() {
91+
let rect = CGRect(x: 100, y: 200, width: 400, height: 300)
92+
let flipped = rect.screenFlipped
93+
let doubleFlipped = flipped.screenFlipped
94+
XCTAssertEqual(rect.origin.x, doubleFlipped.origin.x, accuracy: 0.001)
95+
XCTAssertEqual(rect.origin.y, doubleFlipped.origin.y, accuracy: 0.001)
96+
XCTAssertEqual(rect.width, doubleFlipped.width, accuracy: 0.001)
97+
XCTAssertEqual(rect.height, doubleFlipped.height, accuracy: 0.001)
98+
}
99+
100+
func testScreenFlippedPreservesSize() {
101+
let rect = CGRect(x: 50, y: 100, width: 800, height: 600)
102+
let flipped = rect.screenFlipped
103+
XCTAssertEqual(rect.width, flipped.width, accuracy: 0.001)
104+
XCTAssertEqual(rect.height, flipped.height, accuracy: 0.001)
105+
}
106+
107+
func testScreenFlippedPreservesX() {
108+
let rect = CGRect(x: 250, y: 300, width: 500, height: 400)
109+
let flipped = rect.screenFlipped
110+
XCTAssertEqual(rect.origin.x, flipped.origin.x, accuracy: 0.001)
111+
}
112+
113+
func testScreenFlippedNullRectReturnsNull() {
114+
let nullRect = CGRect.null
115+
let flipped = nullRect.screenFlipped
116+
XCTAssertTrue(flipped.isNull)
117+
}
118+
119+
func testScreenFlippedNegativeCoordinates() {
120+
let rect = CGRect(x: -1000, y: -500, width: 400, height: 300)
121+
let flipped = rect.screenFlipped
122+
let doubleFlipped = flipped.screenFlipped
123+
XCTAssertEqual(rect.origin.x, doubleFlipped.origin.x, accuracy: 0.001)
124+
XCTAssertEqual(rect.origin.y, doubleFlipped.origin.y, accuracy: 0.001)
125+
}
126+
}
127+
128+
class DefaultsExportTests: XCTestCase {
129+
130+
func testOverlapDefaultsInExportArray() {
131+
let keys = Defaults.array.map { $0.key }
132+
XCTAssertTrue(keys.contains("cyclingOverlapOffset"), "cyclingOverlapOffset missing from Defaults.array")
133+
XCTAssertTrue(keys.contains("cyclingOverlapOffsetSize"), "cyclingOverlapOffsetSize missing from Defaults.array")
134+
XCTAssertTrue(keys.contains("cyclingOverlapMaxCascade"), "cyclingOverlapMaxCascade missing from Defaults.array")
135+
}
136+
}
137+
138+
class OverlapOffsetGuardsTests: XCTestCase {
21139

22-
func testExample() {
23-
// This is an example of a functional test case.
24-
// Use XCTAssert and related functions to verify your tests produce the correct results.
140+
func testMaxCascadeClampedToMinOne() {
141+
let result = min(5, max(1, 0))
142+
XCTAssertEqual(result, 1)
25143
}
26144

27-
func testPerformanceExample() {
28-
// This is an example of a performance test case.
29-
self.measure {
30-
// Put the code you want to measure the time of here.
145+
func testMaxCascadeClampedToMaxFive() {
146+
let result = min(5, max(1, 999))
147+
XCTAssertEqual(result, 5)
148+
}
149+
150+
func testMaxCascadeNegativeClampsToOne() {
151+
let result = min(5, max(1, -10))
152+
XCTAssertEqual(result, 1)
153+
}
154+
155+
func testMaxCascadeNormalValuePassesThrough() {
156+
let result = min(5, max(1, 3))
157+
XCTAssertEqual(result, 3)
158+
}
159+
160+
func testOffsetClampingKeepsRectInScreen() {
161+
let screenFrame = CGRect(x: 0, y: 0, width: 2336, height: 1466)
162+
var candidate = CGRect(x: 2300, y: 1400, width: 400, height: 300)
163+
let overlapOffset: CGFloat = 11
164+
165+
candidate.origin.x += overlapOffset
166+
candidate.origin.y += overlapOffset
167+
168+
if candidate.origin.x + candidate.width > screenFrame.maxX {
169+
candidate.origin.x = screenFrame.maxX - candidate.width
170+
}
171+
if candidate.origin.y + candidate.height > screenFrame.maxY {
172+
candidate.origin.y = screenFrame.maxY - candidate.height
173+
}
174+
if candidate.origin.x < screenFrame.origin.x {
175+
candidate.origin.x = screenFrame.origin.x
176+
}
177+
if candidate.origin.y < screenFrame.origin.y {
178+
candidate.origin.y = screenFrame.origin.y
31179
}
180+
181+
XCTAssertLessThanOrEqual(candidate.origin.x + candidate.width, screenFrame.maxX)
182+
XCTAssertLessThanOrEqual(candidate.origin.y + candidate.height, screenFrame.maxY)
183+
XCTAssertGreaterThanOrEqual(candidate.origin.x, screenFrame.origin.x)
184+
XCTAssertGreaterThanOrEqual(candidate.origin.y, screenFrame.origin.y)
32185
}
33186

187+
func testOffsetClampingWithNegativeScreenOrigin() {
188+
let screenFrame = CGRect(x: -1372, y: 1510, width: 3840, height: 2160)
189+
var candidate = CGRect(x: -1372, y: 1510, width: 960, height: 540)
190+
let overlapOffset: CGFloat = 11
191+
192+
candidate.origin.x += overlapOffset
193+
candidate.origin.y += overlapOffset
194+
195+
if candidate.origin.x + candidate.width > screenFrame.maxX {
196+
candidate.origin.x = screenFrame.maxX - candidate.width
197+
}
198+
if candidate.origin.y + candidate.height > screenFrame.maxY {
199+
candidate.origin.y = screenFrame.maxY - candidate.height
200+
}
201+
if candidate.origin.x < screenFrame.origin.x {
202+
candidate.origin.x = screenFrame.origin.x
203+
}
204+
if candidate.origin.y < screenFrame.origin.y {
205+
candidate.origin.y = screenFrame.origin.y
206+
}
207+
208+
XCTAssertLessThanOrEqual(candidate.origin.x + candidate.width, screenFrame.maxX)
209+
XCTAssertLessThanOrEqual(candidate.origin.y + candidate.height, screenFrame.maxY)
210+
XCTAssertGreaterThanOrEqual(candidate.origin.x, screenFrame.origin.x)
211+
XCTAssertGreaterThanOrEqual(candidate.origin.y, screenFrame.origin.y)
212+
XCTAssertEqual(candidate.origin.x, -1372 + 11, accuracy: 0.001)
213+
XCTAssertEqual(candidate.origin.y, 1510 + 11, accuracy: 0.001)
214+
}
34215
}

0 commit comments

Comments
 (0)