0c7b6b4442
Desktop:
- New dedicated MIDI Controller In port (separate from MIDI In), same CC learn/bind pipeline but never passes notes through; positioned first in the IO column order
- Fixed MIDI In "Not Connected" not persisting across restart (missing save_io_config call)
- Fixed MIDI Out/Transport Out mutual-exclusion incorrectly flagging "Not Connected" as an in-use port
- Renamed "Remote" checkbox to "App" on Fader/Toggle/Transport widgets
- FocusFaderWidget/FocusToggleWidget: new Hardware section (Learn + CC/CH bind) so a physical controller can drive a Channel Focus widget alongside the app/OSC, plus an independent Feedback checkbox that sends translated CC out (for motorized fader / LED sync)
- JL Cooper touch-sense profile ("No Profile" / "MIDI JL Cooper CC Mode") on FaderWidget/FocusFaderWidget: touch channel (value channel - 1, same CC) is filtered out of the value and gates incoming OSC/DAW feedback while touching
- FocusFeedbackWidget: "Custom" checkbox to show a manually-typed static string instead of live OSC feedback, disabling Learn/OSC input and suppressing the iPad broadcast while active
- New daw-config-reaper/ folder with reaper-osc-config-paul-custom.ReaperOSC
iOS:
- New FocusToggleWidget (dedicated Channel Focus toggle, split out of shared ToggleWidget) with colorName-aware darken/border and isMomentary handling — keeps Channel-Focus-only behavior off the regular ToggleWidget
- Arrange mode long-press menu: text alignment (left/center/right) and a 10-color named-palette Text Color picker for TitleWidget/FocusFeedbackWidget, persisted in the saved layout
- FocusFeedbackWidget: Reset Size action, monospaced-digit font fix for timestamp/counter jitter
- New "Cancel Changes" button in Arrange mode — reverts layout/hides/aligns to the state at Arrange-mode entry and restores desktop-side Remote/dest assignments
- Grid line brightness increased for visibility
- Bonjour discovery for desktop auto-connect
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
6.9 KiB
Swift
99 lines
6.9 KiB
Swift
import UIKit
|
|
|
|
extension ArrangeObjects {
|
|
|
|
func setupUI() {
|
|
DispatchQueue.main.async {
|
|
|
|
self.view.backgroundColor = .black
|
|
|
|
// containerView
|
|
self.view.addSubview(self.containerView)
|
|
self.containerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
|
|
self.containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
|
|
self.containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
|
|
self.containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
|
|
|
|
// grid overlay — pinned to containerView, behind all widgets
|
|
self.containerView.addSubview(self.gridOverlay)
|
|
self.gridOverlay.topAnchor.constraint(equalTo: self.containerView.topAnchor).isActive = true
|
|
self.gridOverlay.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor).isActive = true
|
|
self.gridOverlay.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor).isActive = true
|
|
self.gridOverlay.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor).isActive = true
|
|
self.gridOverlay.isHidden = true
|
|
|
|
// toolbar buttons (top-right)
|
|
self.view.addSubview(self.clearLayoutsButton)
|
|
self.clearLayoutsButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.clearLayoutsButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
|
|
|
|
// status and preset label — stacked below Clear Layouts
|
|
self.view.addSubview(self.statusLabel)
|
|
self.statusLabel.topAnchor.constraint(equalTo: self.clearLayoutsButton.bottomAnchor, constant: 6).isActive = true
|
|
self.statusLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
|
|
|
|
self.view.addSubview(self.presetLabel)
|
|
self.presetLabel.topAnchor.constraint(equalTo: self.statusLabel.bottomAnchor, constant: 2).isActive = true
|
|
self.presetLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
|
|
|
|
self.view.addSubview(self.showLoggingButton)
|
|
self.showLoggingButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.showLoggingButton.trailingAnchor.constraint(equalTo: self.clearLayoutsButton.leadingAnchor, constant: -6).isActive = true
|
|
|
|
self.view.addSubview(self.toolbarStatusLabel)
|
|
self.toolbarStatusLabel.topAnchor.constraint(equalTo: self.showLoggingButton.bottomAnchor, constant: 4).isActive = true
|
|
self.toolbarStatusLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
|
|
|
|
self.view.addSubview(self.connectButton)
|
|
self.connectButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.connectButton.trailingAnchor.constraint(equalTo: self.showLoggingButton.leadingAnchor, constant: -6).isActive = true
|
|
|
|
self.view.addSubview(self.tabletIdField)
|
|
self.tabletIdField.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.tabletIdField.trailingAnchor.constraint(equalTo: self.connectButton.leadingAnchor, constant: -6).isActive = true
|
|
self.tabletIdField.widthAnchor.constraint(equalToConstant: 40).isActive = true
|
|
self.tabletIdField.heightAnchor.constraint(equalTo: self.connectButton.heightAnchor).isActive = true
|
|
self.tabletIdField.text = "\(self.myTabletId)"
|
|
if let lastIp = UserDefaults.standard.string(forKey: "last_ip") { self.ipField.text = lastIp }
|
|
if let lastPort = UserDefaults.standard.string(forKey: "last_port") { self.portField.text = lastPort }
|
|
self.tabletIdField.addTarget(self, action: #selector(self.tabletIdFieldChanged), for: .editingChanged)
|
|
|
|
self.view.addSubview(self.portField)
|
|
self.portField.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.portField.trailingAnchor.constraint(equalTo: self.tabletIdField.leadingAnchor, constant: -6).isActive = true
|
|
self.portField.widthAnchor.constraint(equalToConstant: 60).isActive = true
|
|
self.portField.heightAnchor.constraint(equalTo: self.connectButton.heightAnchor).isActive = true
|
|
|
|
self.view.addSubview(self.ipField)
|
|
self.ipField.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.ipField.trailingAnchor.constraint(equalTo: self.portField.leadingAnchor, constant: -6).isActive = true
|
|
self.ipField.widthAnchor.constraint(equalToConstant: 120).isActive = true
|
|
self.ipField.heightAnchor.constraint(equalTo: self.connectButton.heightAnchor).isActive = true
|
|
|
|
self.view.addSubview(self.autoSwitchButton)
|
|
self.autoSwitchButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.autoSwitchButton.trailingAnchor.constraint(equalTo: self.ipField.leadingAnchor, constant: -6).isActive = true
|
|
|
|
self.view.addSubview(self.gridButton)
|
|
self.gridButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
|
|
self.view.addSubview(self.cancelChangesButton)
|
|
self.cancelChangesButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
|
|
self.view.addSubview(self.arrangeButton)
|
|
self.arrangeButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.arrangeButton.trailingAnchor.constraint(equalTo: self.autoSwitchButton.leadingAnchor, constant: -6).isActive = true
|
|
self.cancelChangesButton.trailingAnchor.constraint(equalTo: self.arrangeButton.leadingAnchor, constant: -6).isActive = true
|
|
self.gridButton.trailingAnchor.constraint(equalTo: self.cancelChangesButton.leadingAnchor, constant: -6).isActive = true
|
|
|
|
self.view.addSubview(self.channelFocusButton)
|
|
self.channelFocusButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
|
|
self.channelFocusButton.trailingAnchor.constraint(equalTo: self.gridButton.leadingAnchor, constant: -6).isActive = true
|
|
|
|
self.containerView.addSubview(self.logWidget)
|
|
self.addDragGesture(to: self.logWidget)
|
|
self.logWidget.attach(to: self, loggingView: self.loggingView)
|
|
}
|
|
}
|
|
}
|