8000 Implement horizontal resizing for OnitPanel by devin-ai-integration[bot] · Pull Request #184 · synth-inc/onit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement horizontal resizing for OnitPanel #184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "corner-resize.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
2 changes: 1 addition & 1 deletion macos/Onit/Data/Persistence/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ extension Defaults.Keys {
static let isTavilyAPITokenValidated = Key<Bool>("tavilyAPITokenValidated", default: false)

// Window state
static let panelWidth = Key<Double?>("panelWidth", default: nil)
static let panelWidth = Key<Double>("panelWidth", default: 400)
static let panelPosition = Key<PanelPosition>("panelPosition", default: .topRight)

// General settings
Expand Down
1 change: 0 additions & 1 deletion macos/Onit/Tethered/TetherAppsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class TetherAppsManager: ObservableObject {
private var regularAppCancellable: AnyCancellable?
private var skipFirstRegularAppUpdate: Bool = true

static let minOnitWidth: CGFloat = ContentView.idealWidth
static let spaceBetweenWindows: CGFloat = -(TetheredButton.width / 2)

var targetInitialFrames: [AXUIElement: CGRect] = [:]
Expand Down
65 changes: 65 additions & 0 deletions macos/Onit/UI/Components/ResizeHandle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// ResizeHandle.swift
// Onit
//
// Created by Devin AI on 4/29/25.
//

import SwiftUI

// NSViewRepresentable that prevents mouseDown from moving the window
struct NonDraggableNSView: NSViewRepresentable {

func makeNSView(context: Self.Context) -> NSView {
let view = NonDraggableView()
return view
}
func updateNSView(_ nsView: NSView, context: Self.Context) {
}

class NonDraggableView: NSView {
override var mouseDownCanMoveWindow: Bool { false }
}
}

struct ResizeHandle: View {
var size: CGFloat = 24
var onDrag: (CGFloat) -> Void
var onDragEnded: (() -> Void)?

var body: some View {
ZStack {
Image(.cornerResize)
.foregroundColor(.gray300)
.padding(8)
// Overlay the non-draggable NSView
NonDraggableNSView()
.frame(width: size, height: size)
.allowsHitTesting(true)
.background(Color.clear)
}
.frame(width: size, height: size)
.highPriorityGesture(
DragGesture(minimumDistance: 1, coordinateSpace: .local)
.onChanged { value in
onDrag(value.translation.width)
}
.onEnded { _ in
onDragEnded?()
}
)
.contentShape(Rectangle()) // Ensure the entire area is tappable
.allowsHitTesting(true) // Make sure the view intercepts all events
}
}

#if DEBUG
struct ResizeHandle_Previews: PreviewProvider {
static var previews: some View {
ResizeHandle(onDrag: { _ in })
.previewLayout(.sizeThatFits)
.padding()
.background(Color.black)
}
}
#endif
4 changes: 2 additions & 2 deletions macos/Onit/UI/Content/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ struct ContentView: View {
@ObservedObject private var accessibilityPermissionManager = AccessibilityPermissionManager.shared
@Default(.showOnboarding) var showOnboarding

static let idealWidth: CGFloat = 400
static let bottomPadding: CGFloat = 0

private var showFileImporterBinding: Binding<Bool> {
Expand Down Expand Up @@ -44,7 +43,7 @@ struct ContentView: View {
Spacer()
}
}
.frame(width: TetherAppsManager.minOnitWidth)
.frame(width: panelWidth)
.frame(maxHeight: .infinity)
.background(Color.black)
} else {
Expand Down Expand Up @@ -93,6 +92,7 @@ struct ContentView: View {
.onEnded { value in
if let panel = state.panel {
panelWidth = panel.frame.width
state.panelWidth = panel.frame.width
}
}
)
Expand Down
12 changes: 3 additions & 9 deletions macos/Onit/UI/Panels/OnitAccessoryPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class OnitAccessoryPanel: NSPanel {
AE27 return true
}

private let width = ContentView.idealWidth
private var width: CGFloat = 400 // Default width, will be updated from state

var dragDetails: PanelDraggingDetails = .init()
var isAnimating: Bool = false
Expand All @@ -24,12 +24,9 @@ class OnitAccessoryPanel: NSPanel {
var resizedApplication: Bool = false

init(state: OnitPanelState) {
self.width = state.panelWidth
var windowWidth = width

if let savedWidth = Defaults[.panelWidth] {
windowWidth = savedWidth
}

super.init(
contentRect: NSRect(x: 0, y: 0, width: windowWidth, height: 0),
styleMask: [.nonactivatingPanel, .resizable, .fullSizeContentView],
Expand Down Expand Up @@ -158,10 +155,7 @@ extension OnitAccessoryPanel: OnitPanel {

func show() {
var windowWidth = frame.width

if let savedWidth = Defaults[.panelWidth] {
windowWidth = savedWidth
}
windowWidth = Defaults[.panelWidth]

if let newFrame = calculatePanelFrame(windowWidth: windowWidth) {
setFrame(newFrame, display: true, animate: false)
Expand Down
32 changes: 32 additions & 0 deletions macos/Onit/UI/Panels/OnitRegularPanel+Move.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,38 @@ extension OnitRegularPanel {
}
}

func panelResizeEnded(originalPanelWidth: CGFloat) {
guard let activeWindow = state.trackedWindow?.element,
let activeWindowFrame = activeWindow.getFrame(),
wasAnimated, !isAnimating, dragDetails.isDragging, !state.isWindowDragging else { return }

// Adjust the active window's width to match the panel's new width
let deltaWidth = self.width - originalPanelWidth
if deltaWidth != 0 {
// There's an edge case where window's are non-resizable (for example VoiceOver Utility)
// We need to check if the window is resizable before attempting to resize it
var isSettable: DarwinBoolean = false
let settableResult = AXUIElementIsAttributeSettable(activeWindow, kAXSizeAttribute as CFString, &isSettable)

if isSettable.boolValue {
// If the window is resizeable then we should resize it
let newWidth = round(activeWindowFrame.width - deltaWidth)
let newFrame = NSRect(
x: activeWindowFrame.origin.x,
y: activeWindowFrame.origin.y,
width: newWidth,
height: activeWindowFrame.height
)
_ = activeWindow.setFrame(newFrame)
} else {
// If the window is not resizeable then we should move it's position but not change it's size.
let newX = round(activeWindowFrame.origin.x - deltaWidth)
let newPosition = NSPoint(x: newX, y: activeWindowFrame.origin.y)
_ = activeWindow.setPosition(newPosition)
}
}
}

private func adjustPanelIfBeyondRightScreenEdge() -> CGPoint {
guard let rightmostScreen = NSScreen.rightmostScreen,
frame.origin.x + frame.width > rightmostScreen.visibleFrame.maxX else {
Expand Down
68 changes: 66 additions & 2 deletions macos/Onit/UI/Panels/OnitRegularPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,22 @@ class OnitRegularPanel: NSPanel {
}

let state: OnitPanelState
private let width = ContentView.idealWidth

var width: CGFloat
static let minWidth: CGFloat = 320 // Minimum width constraint
static let minAppWidth = 500.0

var dragDetails: PanelDraggingDetails = .init()
var isAnimating: Bool = false
var wasAnimated: Bool = false
var animatedFromLeft: Bool = false
var resizedApplication: Bool = false
var isResizing: Bool = false
var onitContentView: ContentView?
var originalFrame : NSRect = .zero

init(state: OnitPanelState) {
self.state = state
self.width = state.panelWidth

super.init(
contentRect: NSRect(x: 0, y: 0, width: width, height: 0),
Expand All @@ -47,6 +52,7 @@ class OnitRegularPanel: NSPanel {
level = .floating
titleVisibility = .hidden
titlebarAppearsTransparent = true
// isMovable = false
isMovableByWindowBackground = true
delegate = state
isFloatingPanel = false
Expand All @@ -71,6 +77,39 @@ class OnitRegularPanel: NSPanel {
self.contentView = hostingView
self.contentView?.setFrameOrigin(NSPoint(x: 0, y: 0))

let resizeOverlay = NSHostingView(rootView:
ZStack(alignment: .bottomLeading) {
Color.clear // Transparent background
ResizeHandle(
onDrag: { [weak self] deltaX in
guard let self = self else { return }
self.isResizing = true
if self.originalFrame == .zero {
self.originalFrame = NSRect(origin: frame.origin, size: frame.size)
print("Original panel width: \(self.originalFrame.width)")
}
self.resizePanel(byWidth: deltaX)
},
onDragEnded: { [weak self] in
guard let self = self else { return }
Defaults[.panelWidth] = self.width
self.panelResizeEnded(originalPanelWidth: self.originalFrame.width)
self.originalFrame = .zero
self.isResizing = false
}
)
.padding(.leading, TetheredButton.width / 2)
}
.allowsHitTesting(true) // Ensure the ZStack intercepts all events
.contentShape(Rectangle()) // Make the entire area respond to gestures
)
resizeOverlay.wantsLayer = true
resizeOverlay.layer?.backgroundColor = CGColor.clear
resizeOverlay.frame = hostingView.bounds
hostingView.addSubview(resizeOverlay)
resizeOverlay.autoresizingMask = [.width, .height]


NotificationCenter.default.addObserver(
self,
selector: #selector(windowDidMove),
Expand Down Expand Up @@ -104,6 +143,7 @@ class OnitRegularPanel: NSPanel {
}

@objc private func windowWillMove(_ notification: Notification) {

dragDetails.isDragging = true
}

Expand Down Expand Up @@ -154,6 +194,30 @@ extension OnitRegularPanel: OnitPanel {

func updatePosition() { }

func resizePanel(byWidth deltaWidth: CGFloat) {
guard !isAnimating else { return }

let newWidth = width - deltaWidth
let newX = frame.origin.x - deltaWidth

if (newWidth >= OnitRegularPanel.minWidth) {
width = newWidth

// Always use the original position to calculate the new frame
// This prevents accumulated drift during resize

let newFrame = NSRect(
x: originalFrame.maxX - newWidth,
y: frame.origin.y,
width: newWidth,
height: frame.height
)

setFrame(newFrame, display: true)
state.panelWidth = newWidth // Update state property
}
}

func show() {
makeKeyAndOrderFront(nil)
setupFrame()
Expand Down
13 changes: 6 additions & 7 deletions macos/Onit/UI/Panels/State/OnitPanelState+Position.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension OnitPanelState {
if window.isDesktopFinder {
if let mouseScreen = NSScreen.mouse {
let screenFrame = mouseScreen.frame
let class="x x-first x-last">TetherAppsManager.minOnitWidth
let class="x x-first x-last">self.panelWidth
let - ContentView.bottomPadding
let - onitHeight
let - onitWidth
Expand Down Expand Up @@ -64,7 +64,7 @@ extension OnitPanelState {
let fullTop = primaryScreenFrame.height - screenFrame.height - visibleFrame.minY + activeScreenInset
let windowDistanceFromTop = windowFrame.minY - fullTop

let class="x x-first x-last">TetherAppsManager.minOnitWidth
let class="x x-first x-last">self.panelWidth
let screenFrame.height - ContentView.bottomPadding)
let + (visibleFrame.height - windowFrame.height) - windowDistanceFromTop

Expand All @@ -76,11 +76,10 @@ extension OnitPanelState {
if (action == .move && !isOnRightmostScreen) || hasEnoughSpace {
self.movePanel(screenFrame: screenFrame, onitWidth: onitWidth, onitHeight: onitHeight, onitY: onitY)
} else {
let minAppWidth = 500.0

let maxAvailableWidth = screenFrame.maxX - windowFrame.origin.x - onitWidth - TetherAppsManager.spaceBetweenWindows

if maxAvailableWidth >= minAppWidth {
if maxAvailableWidth >= OnitRegularPanel.minAppWidth {
resizeWindowAndMovePanel(onitWidth: onitWidth, onitHeight: onitHeight, onitY: onitY, maxAvailableWidth: maxAvailableWidth)
} else {
moveWindowAndPanel(screenFrame: screenFrame, onitWidth: onitWidth, onitHeight: onitHeight, onitY: onitY)
Expand Down Expand Up @@ -179,9 +178,9 @@ extension OnitPanelState {
height: screen.visibleFrame.height
)
let newFrame = NSRect(
x: screen.visibleFrame.maxX - TetherAppsManager.minOnitWidth,
x: screen.visibleFrame.maxX - self.panelWidth,
y: screen.visibleFrame.minY,
width: TetherAppsManager.minOnitWidth,
width: self.panelWidth,
height: screen.visibleFrame.height
)

Expand Down Expand Up @@ -494,4 +493,4 @@ extension OnitPanelState {
return value
}
}
}
}
5 changes: 5 additions & 0 deletions macos/Onit/UI/Panels/State/OnitPanelState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import ApplicationServices
import Combine
import Defaults
import SwiftData
import SwiftUI

Expand Down Expand Up @@ -83,6 +84,8 @@ class OnitPanelState: NSObject {

var tetheredButtonYPosition: CGFloat?

var panelWidth: CGFloat

var currentChat: Chat?
var currentPrompts: [Prompt]?

Expand Down Expand Up @@ -136,13 +139,15 @@ class OnitPanelState: NSObject {

init(trackedWindow: TrackedWindow?) {
self.trackedWindow = trackedWindow
self.panelWidth = Defaults[.panelWidth]
super.init()

self.promptSuggestionService = SystemPromptSuggestionService(state: self)
}

init(trackedScreen: TrackedScreen?) {
self.trackedScreen = trackedScreen
self.panelWidth = Defaults[.panelWidth]
super.init()

self.promptSuggestionService = SystemPromptSuggestionService(state: self)
Expand Down
Loading
0