d6201c59b4
Desktop: - Replace Remote checkbox with T: dest_spin (0=Off, 1-9=tablet) on all widgets - dest_id persisted in get_state/set_state, broadcast in tablet snapshot - dest_spin.valueChanged triggers save + broadcast to iPad - _on_widget_visibility_from_tablet updates dest_spin from iPad hide event - _broadcast_preset_update shared helper; ws_server.has_clients guard - label editingFinished triggers live label sync to iPad iOS: - VC-Arrange/VC-Logging/Widgets-Reusable-UI file reorganisation - ArrangeObjects/ArrangeView rename, 7-bucket Arrange-Functions split - myTabletId (UserDefaults) + tabletIdField next to port in toolbar - Preset filter: destId == myTabletId instead of visible bool - BaseWidget activity ring: long press gesture (touch) + final update() flash - Context menu long press (arrange mode): hide widget, save layout, send dest_id:0 - Same-UUID preset check skips Preset Switched alert on visibility/label updates - autoSwitchEnabled, toolbarStatusLabel, presetLabel, statusToken Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
2.9 KiB
Swift
89 lines
2.9 KiB
Swift
import UIKit
|
|
|
|
class FaderWidget: BaseWidget {
|
|
|
|
static let size = CGSize(width: 140, height: 360)
|
|
|
|
static func defaultFrame(idx: Int) -> CGRect {
|
|
let col = idx % 3
|
|
let row = idx / 3
|
|
return CGRect(x: 16 + CGFloat(col) * 170, y: 48 + CGFloat(row) * 380, width: size.width, height: size.height)
|
|
}
|
|
|
|
// MARK: - Properties
|
|
let color: UIColor
|
|
private var sliderWidthConstraint: NSLayoutConstraint?
|
|
|
|
// MARK: - Subviews
|
|
lazy var nameLabel: UILabel = {
|
|
let lbl = UILabel()
|
|
lbl.translatesAutoresizingMaskIntoConstraints = false
|
|
lbl.textColor = UIColor(white: 0.53, alpha: 1)
|
|
lbl.font = .systemFont(ofSize: 14)
|
|
lbl.textAlignment = .center
|
|
return lbl
|
|
}()
|
|
|
|
lazy var slider: UISlider = {
|
|
let s = UISlider()
|
|
s.translatesAutoresizingMaskIntoConstraints = false
|
|
s.minimumValue = 0
|
|
s.maximumValue = 127
|
|
s.transform = CGAffineTransform(rotationAngle: -.pi / 2)
|
|
return s
|
|
}()
|
|
|
|
// MARK: - Init
|
|
init(uid: String, label: String, value: Int, color: UIColor, frame: CGRect) {
|
|
self.color = color
|
|
super.init(uid: uid, frame: frame)
|
|
self.displayName = label
|
|
self.restingBorderColor = color.cgColor
|
|
self.layer.borderColor = color.cgColor
|
|
self.nameLabel.text = label
|
|
self.slider.value = Float(value)
|
|
self.slider.minimumTrackTintColor = color
|
|
setupUI()
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError() }
|
|
|
|
// MARK: - Layout
|
|
func setupUI() {
|
|
self.addSubview(self.nameLabel)
|
|
self.nameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
|
|
self.nameLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 4).isActive = true
|
|
self.nameLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -4).isActive = true
|
|
|
|
self.addSubview(self.slider)
|
|
self.slider.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
|
|
self.slider.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 10).isActive = true
|
|
let wc = self.slider.widthAnchor.constraint(equalToConstant: 216)
|
|
wc.isActive = true
|
|
self.sliderWidthConstraint = wc
|
|
|
|
self.slider.addTarget(self, action: #selector(self.sliderChanged), for: .valueChanged)
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
self.sliderWidthConstraint?.constant = bounds.height * 0.6
|
|
}
|
|
|
|
// MARK: - Update
|
|
override func applyValue(_ value: Int) {
|
|
self.slider.value = Float(value)
|
|
}
|
|
|
|
// MARK: - Arrange Mode
|
|
override func setArrangeMode(_ enabled: Bool) {
|
|
self.slider.isUserInteractionEnabled = (enabled == false)
|
|
}
|
|
|
|
// MARK: - Action
|
|
@objc private func sliderChanged() {
|
|
self.onSend?(self.uid, Int(self.slider.value))
|
|
}
|
|
|
|
}
|