Skip to content

Commit

Permalink
Fix RTL language causes misalignment of slider labels, #4904
Browse files Browse the repository at this point in the history
This commit will:
- Add outlets for speed slider labels and other components to the
  QuickSettingViewController class
- Add outlets for constraints controlling the position of speed slider
  labels to the QuickSettingViewController class
- Add methods awakeFromNib, calculateSliderLabelMultiplier and
  viewWillLayout to the QuickSettingViewController class to replace
  constraints when in a right to left layout
- Add a convertSpeedToSliderValue method to the
  QuickSettingViewController class to eliminate duplication of a
  formula

These changes cause IINA to replace the layout constraints that control
the position of the labels under the speed slider in the video panel
that identify the speed associated with particular slider tick marks
with constraints that properly position the slider labels when the user
interface layout direction is right to left.
  • Loading branch information
low-batt committed May 8, 2024
1 parent 73c9608 commit c5110ac
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 4 deletions.
10 changes: 10 additions & 0 deletions iina/Base.lproj/QuickSettingViewController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,17 @@
<outlet property="saturationSlider" destination="VlS-Jo-4C0" id="rJV-p9-OVz"/>
<outlet property="secSubTableView" destination="Jve-qX-Agy" id="0ia-bh-sbu"/>
<outlet property="speedSlider" destination="UWh-M9-5Nw" id="UXr-dD-8K1"/>
<outlet property="speedSlider0_25xLabel" destination="Nhb-Co-xbZ" id="16j-wm-Ajt"/>
<outlet property="speedSlider16xLabel" destination="mjX-Jf-925" id="4P4-YN-VLh"/>
<outlet property="speedSlider16xLabelPrevLabelConstraint" destination="Aep-cH-5hZ" id="oqZ-YH-RdM"/>
<outlet property="speedSlider1xLabel" destination="wBg-Dk-cUX" id="3KH-oh-WBJ"/>
<outlet property="speedSlider1xLabelCenterXConstraint" destination="hlk-KQ-DJ7" id="Sja-O3-JF3"/>
<outlet property="speedSlider1xLabelPrevLabelConstraint" destination="85g-vN-0eC" id="2Au-Nf-kCX"/>
<outlet property="speedSlider4xLabel" destination="Njq-za-TIf" id="xtC-vt-JRw"/>
<outlet property="speedSlider4xLabelCenterXConstraint" destination="H0i-c4-qHi" id="kd4-J7-2GM"/>
<outlet property="speedSlider4xLabelPrevLabelConstraint" destination="0og-bi-e6K" id="mxE-fT-iec"/>
<outlet property="speedSliderConstraint" destination="qTf-o6-Mef" id="mB3-5X-SOE"/>
<outlet property="speedSliderContainerView" destination="5v4-Te-950" id="TvR-R1-5kh"/>
<outlet property="speedSliderIndicator" destination="3EN-WD-QNI" id="vDD-eE-x6w"/>
<outlet property="subDelaySlider" destination="dXz-x4-sm5" id="VX5-8O-mf4"/>
<outlet property="subDelaySliderConstraint" destination="7t1-v0-J55" id="ypG-K0-i1x"/>
Expand Down
111 changes: 107 additions & 4 deletions iina/QuickSettingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ class QuickSettingViewController: NSViewController, NSTableViewDataSource, NSTab
@IBOutlet weak var speedSlider: NSSlider!
@IBOutlet weak var speedSliderIndicator: NSTextField!
@IBOutlet weak var speedSliderConstraint: NSLayoutConstraint!
@IBOutlet weak var speedSliderContainerView: NSView!

@IBOutlet weak var speedSlider0_25xLabel: NSTextField!
@IBOutlet weak var speedSlider1xLabel: NSTextField!
@IBOutlet weak var speedSlider4xLabel: NSTextField!
@IBOutlet weak var speedSlider16xLabel: NSTextField!
@IBOutlet var speedSlider1xLabelCenterXConstraint: NSLayoutConstraint!
@IBOutlet var speedSlider4xLabelCenterXConstraint: NSLayoutConstraint!
@IBOutlet var speedSlider1xLabelPrevLabelConstraint: NSLayoutConstraint!
@IBOutlet var speedSlider4xLabelPrevLabelConstraint: NSLayoutConstraint!
@IBOutlet var speedSlider16xLabelPrevLabelConstraint: NSLayoutConstraint!

@IBOutlet weak var customSpeedTextField: NSTextField!
@IBOutlet weak var switchHorizontalLine: NSBox!
@IBOutlet weak var switchHorizontalLine2: NSBox!
Expand Down Expand Up @@ -213,6 +225,92 @@ class QuickSettingViewController: NSViewController, NSTableViewDataSource, NSTab
}
}

// MARK: - Right to Left Constraints

/// Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
///
/// If the user interface layout direction is right to left then certain layout constraints that assume a left to right layout will need to be
/// replaced. That will be handled by the `viewWillLayout` method. This method will disable these constraints to avoid triggering
/// constraint errors before the constraints can be replaced.
override func awakeFromNib() {
super.awakeFromNib()
guard speedSlider.userInterfaceLayoutDirection == .rightToLeft else { return }
NSLayoutConstraint.deactivate([
speedSlider1xLabelCenterXConstraint,
speedSlider4xLabelCenterXConstraint,
speedSlider1xLabelPrevLabelConstraint,
speedSlider4xLabelPrevLabelConstraint,
speedSlider16xLabelPrevLabelConstraint])
}

/// Calculate the constraint multiplier for a speed slider label.
///
/// This method calculates the appropriate multiplier to use in a
/// [centerX](https://developer.apple.com/documentation/uikit/nslayoutconstraint/attribute/centerx)
/// constraint for a text field that sits under the speed slider and displays the speed associated with a particular tick mark.
/// - Parameter speed: Playback speed the label indicates.
/// - Returns: Multiplier to use in the constraint.
private func calculateSliderLabelMultiplier(speed: Double) -> CGFloat {
let tickIndex = Int(convertSpeedToSliderValue(speedSlider.closestTickMarkValue(toValue: speed)))
let tickRect = speedSlider.rectOfTickMark(at: tickIndex)
let tickCenterX = tickRect.origin.x + tickRect.width / 2
let containerViewX = speedSlider.frame.origin.x + tickCenterX
return containerViewX / speedSliderContainerView.frame.width
}

/// Called just before the `layout()` method of the view controller's view is called.
///
/// If the user interface layout direction is right to left then this method will replace certain layout constraints with ones that properly
/// position the reversed views.
override func viewWillLayout() {
// When the layout is right to left the first time this method is called the views will not have
// been reversed. Once the views have been repositioned this method will be called again. Must
// wait for that to happen before adjusting constraints to avoid triggering constraint errors.
// Detect this based on the order of the speed slider labels.
guard speedSliderContainerView.userInterfaceLayoutDirection == .rightToLeft,
speedSlider16xLabel.frame.origin.x < speedSlider0_25xLabel.frame.origin.x else {
super.viewWillLayout()
return
}

// Deactive the layout constraints that will be replaced.
NSLayoutConstraint.deactivate([
speedSlider1xLabelCenterXConstraint,
speedSlider4xLabelCenterXConstraint,
speedSlider1xLabelPrevLabelConstraint,
speedSlider4xLabelPrevLabelConstraint,
speedSlider16xLabelPrevLabelConstraint])

// The multiplier in the constraints that position the 1x and 4x labels must be changed to
// reflect the reversed views.
speedSlider1xLabelCenterXConstraint = NSLayoutConstraint(
item: speedSlider1xLabel as Any, attribute: .centerX, relatedBy: .equal, toItem: speedSlider,
attribute: .right, multiplier: calculateSliderLabelMultiplier(speed: 1), constant: 0)
speedSlider4xLabelCenterXConstraint = NSLayoutConstraint(
item: speedSlider4xLabel as Any, attribute: .centerX, relatedBy: .equal, toItem: speedSlider,
attribute: .right, multiplier: calculateSliderLabelMultiplier(speed: 4), constant: 0)

// The constraints that impose an order on the labels must be changed to reflect the reversed
// views.
speedSlider1xLabelPrevLabelConstraint = NSLayoutConstraint(
item: speedSlider1xLabel as Any, attribute: .right, relatedBy: .lessThanOrEqual,
toItem: speedSlider0_25xLabel, attribute: .left, multiplier: 1, constant: 0)
speedSlider4xLabelPrevLabelConstraint = NSLayoutConstraint(
item: speedSlider4xLabel as Any, attribute: .right, relatedBy: .lessThanOrEqual,
toItem: speedSlider1xLabel, attribute: .left, multiplier: 1, constant: 0)
speedSlider16xLabelPrevLabelConstraint = NSLayoutConstraint(
item: speedSlider16xLabel as Any, attribute: .right, relatedBy: .lessThanOrEqual,
toItem: speedSlider4xLabel, attribute: .left, multiplier: 1, constant: 0)

NSLayoutConstraint.activate([
speedSlider1xLabelCenterXConstraint,
speedSlider4xLabelCenterXConstraint,
speedSlider1xLabelPrevLabelConstraint,
speedSlider4xLabelPrevLabelConstraint,
speedSlider16xLabelPrevLabelConstraint])
super.viewWillLayout()
}

// MARK: - Validate UI

/** Do synchronization*/
Expand All @@ -236,6 +334,13 @@ class QuickSettingViewController: NSViewController, NSTableViewDataSource, NSTab
updateAudioEqState()
}

/// Return the slider value that represents the given playback speed.
/// - Parameter speed: Playback speed.
/// - Returns: Appropriate slider value.
private func convertSpeedToSliderValue(_ speed: Double) -> Double {
log(speed / AppData.minSpeed) / log(AppData.maxSpeed / AppData.minSpeed) * sliderSteps
}

private func updateVideoTabControl() {
if let index = AppData.aspectsInPanel.firstIndex(of: player.info.unsureAspect) {
aspectSegment.selectedSegment = index
Expand Down Expand Up @@ -269,8 +374,7 @@ class QuickSettingViewController: NSViewController, NSTableViewDataSource, NSTab

let speed = player.mpv.getDouble(MPVOption.PlaybackControl.speed)
customSpeedTextField.doubleValue = speed
let sliderValue = log(speed / AppData.minSpeed) / log(AppData.maxSpeed / AppData.minSpeed) * sliderSteps
speedSlider.doubleValue = sliderValue
speedSlider.doubleValue = convertSpeedToSliderValue(speed)
redraw(indicator: speedSliderIndicator, constraint: speedSliderConstraint, slider: speedSlider, value: "\(customSpeedTextField.stringValue)x")
}

Expand Down Expand Up @@ -601,8 +705,7 @@ class QuickSettingViewController: NSViewController, NSTableViewDataSource, NSTab
sender.stringValue = "1"
}
let value = customSpeedTextField.doubleValue
let sliderValue = log(value / AppData.minSpeed) / log(AppData.maxSpeed / AppData.minSpeed) * sliderSteps
speedSlider.doubleValue = sliderValue
speedSlider.doubleValue = convertSpeedToSliderValue(value)
if player.info.playSpeed != value {
player.setSpeed(value)
}
Expand Down

0 comments on commit c5110ac

Please sign in to comment.