mirror of
https://github.com/mpv-player/mpv
synced 2025-03-23 11:47:45 +00:00
mac/touchbar: rewrite touch bar in swift
This commit is contained in:
parent
c155c18023
commit
c8088d801a
@ -1556,7 +1556,7 @@ macos_touchbar = get_option('macos-touchbar').require(
|
|||||||
)
|
)
|
||||||
features += {'macos-touchbar': macos_touchbar.allowed()}
|
features += {'macos-touchbar': macos_touchbar.allowed()}
|
||||||
if features['macos-touchbar']
|
if features['macos-touchbar']
|
||||||
sources += files('osdep/mac/touchbar.m')
|
swift_sources += files('osdep/mac/touch_bar.swift')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if features['swift'] and swift_sources.length() > 0
|
if features['swift'] and swift_sources.length() > 0
|
||||||
|
@ -30,9 +30,6 @@
|
|||||||
#include "osdep/threads.h"
|
#include "osdep/threads.h"
|
||||||
#include "osdep/main-fn.h"
|
#include "osdep/main-fn.h"
|
||||||
|
|
||||||
#if HAVE_MACOS_TOUCHBAR
|
|
||||||
#import "osdep/mac/touchbar.h"
|
|
||||||
#endif
|
|
||||||
#if HAVE_SWIFT
|
#if HAVE_SWIFT
|
||||||
#include "osdep/mac/swift.h"
|
#include "osdep/mac/swift.h"
|
||||||
#endif
|
#endif
|
||||||
@ -161,14 +158,7 @@ static const char mac_icon[] =
|
|||||||
#if HAVE_MACOS_TOUCHBAR
|
#if HAVE_MACOS_TOUCHBAR
|
||||||
- (NSTouchBar *)makeTouchBar
|
- (NSTouchBar *)makeTouchBar
|
||||||
{
|
{
|
||||||
TouchBar *tBar = [[TouchBar alloc] init];
|
return [[TouchBar alloc] init];
|
||||||
tBar.delegate = tBar;
|
|
||||||
tBar.customizationIdentifier = customID;
|
|
||||||
tBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar];
|
|
||||||
tBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem,
|
|
||||||
nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle,
|
|
||||||
currentPosition, timeLeft];
|
|
||||||
return tBar;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
343
osdep/mac/touch_bar.swift
Normal file
343
osdep/mac/touch_bar.swift
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software) you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation) either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* mpv is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY) without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extension NSTouchBar.CustomizationIdentifier {
|
||||||
|
public static let customId: NSTouchBar.CustomizationIdentifier = "io.mpv.touchbar"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NSTouchBarItem.Identifier {
|
||||||
|
public static let seekBar = NSTouchBarItem.Identifier(custom: ".seekbar")
|
||||||
|
public static let play = NSTouchBarItem.Identifier(custom: ".play")
|
||||||
|
public static let nextItem = NSTouchBarItem.Identifier(custom: ".nextItem")
|
||||||
|
public static let previousItem = NSTouchBarItem.Identifier(custom: ".previousItem")
|
||||||
|
public static let nextChapter = NSTouchBarItem.Identifier(custom: ".nextChapter")
|
||||||
|
public static let previousChapter = NSTouchBarItem.Identifier(custom: ".previousChapter")
|
||||||
|
public static let cycleAudio = NSTouchBarItem.Identifier(custom: ".cycleAudio")
|
||||||
|
public static let cycleSubtitle = NSTouchBarItem.Identifier(custom: ".cycleSubtitle")
|
||||||
|
public static let currentPosition = NSTouchBarItem.Identifier(custom: ".currentPosition")
|
||||||
|
public static let timeLeft = NSTouchBarItem.Identifier(custom: ".timeLeft")
|
||||||
|
|
||||||
|
init(custom: String) {
|
||||||
|
self.init(NSTouchBar.CustomizationIdentifier.customId + custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TouchBar {
|
||||||
|
enum `Type` {
|
||||||
|
case button
|
||||||
|
case text
|
||||||
|
case slider
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
let name: String
|
||||||
|
let type: Type
|
||||||
|
let command: String
|
||||||
|
var view: NSView?
|
||||||
|
var item: NSCustomTouchBarItem?
|
||||||
|
var constraint: NSLayoutConstraint?
|
||||||
|
let image: NSImage
|
||||||
|
let imageAlt: NSImage
|
||||||
|
|
||||||
|
init(
|
||||||
|
name: String = "",
|
||||||
|
type: Type = .button,
|
||||||
|
command: String = "",
|
||||||
|
view: NSView? = nil,
|
||||||
|
item: NSCustomTouchBarItem? = nil,
|
||||||
|
constraint: NSLayoutConstraint? = nil,
|
||||||
|
image: NSImage? = nil,
|
||||||
|
imageAlt: NSImage? = nil
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.command = command
|
||||||
|
self.view = view
|
||||||
|
self.item = item
|
||||||
|
self.constraint = constraint
|
||||||
|
self.image = image ?? NSImage(size: NSSize(width: 1, height: 1))
|
||||||
|
self.imageAlt = imageAlt ?? NSImage(size: NSSize(width: 1, height: 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TouchBar: NSTouchBar, NSTouchBarDelegate {
|
||||||
|
var configs: [NSTouchBarItem.Identifier:Config] = [:]
|
||||||
|
var isPaused: Bool = false
|
||||||
|
var position: Double = 0
|
||||||
|
var duration: Double = 0
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
configs = [
|
||||||
|
.seekBar: Config(name: "Seek Bar", type: .slider, command: "seek %f absolute-percent"),
|
||||||
|
.currentPosition: Config(name: "Current Position", type: .text),
|
||||||
|
.timeLeft: Config(name: "Time Left", type: .text),
|
||||||
|
.play: Config(
|
||||||
|
name: "Play Button",
|
||||||
|
type: .button,
|
||||||
|
command: "cycle pause",
|
||||||
|
image: .init(named: NSImage.touchBarPauseTemplateName),
|
||||||
|
imageAlt: .init(named: NSImage.touchBarPlayTemplateName)
|
||||||
|
),
|
||||||
|
.previousItem: Config(
|
||||||
|
name: "Previous Playlist Item",
|
||||||
|
type: .button,
|
||||||
|
command: "playlist-prev",
|
||||||
|
image: .init(named: NSImage.touchBarGoBackTemplateName)
|
||||||
|
),
|
||||||
|
.nextItem: Config(
|
||||||
|
name: "Next Playlist Item",
|
||||||
|
type: .button,
|
||||||
|
command: "playlist-next",
|
||||||
|
image: .init(named: NSImage.touchBarGoForwardTemplateName)
|
||||||
|
),
|
||||||
|
.previousChapter: Config(
|
||||||
|
name: "Previous Chapter",
|
||||||
|
type: .button,
|
||||||
|
command: "add chapter -1",
|
||||||
|
image: .init(named: NSImage.touchBarSkipBackTemplateName)
|
||||||
|
),
|
||||||
|
.nextChapter: Config(
|
||||||
|
name: "Next Chapter",
|
||||||
|
type: .button,
|
||||||
|
command: "add chapter 1",
|
||||||
|
image: .init(named: NSImage.touchBarSkipAheadTemplateName)
|
||||||
|
),
|
||||||
|
.cycleAudio: Config(
|
||||||
|
name: "Cycle Audio",
|
||||||
|
type: .button,
|
||||||
|
command: "cycle audio",
|
||||||
|
image: .init(named: NSImage.touchBarAudioInputTemplateName)
|
||||||
|
),
|
||||||
|
.cycleSubtitle: Config(
|
||||||
|
name: "Cycle Subtitle",
|
||||||
|
type: .button,
|
||||||
|
command: "cycle sub",
|
||||||
|
image: .init(named: NSImage.touchBarComposeTemplateName)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
delegate = self
|
||||||
|
customizationIdentifier = .customId;
|
||||||
|
defaultItemIdentifiers = [.play, .previousItem, .nextItem, .seekBar]
|
||||||
|
customizationAllowedItemIdentifiers = [.play, .seekBar, .previousItem, .nextItem,
|
||||||
|
.previousChapter, .nextChapter, .cycleAudio, .cycleSubtitle, .currentPosition, .timeLeft]
|
||||||
|
addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||||
|
guard let config = configs[identifier] else { return nil }
|
||||||
|
|
||||||
|
switch config.type {
|
||||||
|
case .button:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
let image = config.image
|
||||||
|
let button = NSButton(image: image, target: self, action: #selector(buttonAction(_:)))
|
||||||
|
item.view = button;
|
||||||
|
item.customizationLabel = config.name
|
||||||
|
configs[identifier]?.view = button
|
||||||
|
configs[identifier]?.item = item
|
||||||
|
item.addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
|
||||||
|
return item
|
||||||
|
case .text:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
let text = NSTextField(labelWithString: "0:00")
|
||||||
|
text.alignment = .center
|
||||||
|
item.view = text;
|
||||||
|
item.customizationLabel = config.name
|
||||||
|
configs[identifier]?.view = text
|
||||||
|
configs[identifier]?.item = item
|
||||||
|
item.addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
|
||||||
|
return item
|
||||||
|
case .slider:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
let slider = NSSlider(target: self, action: #selector(seekbarChanged(_:)))
|
||||||
|
slider.minValue = 0
|
||||||
|
slider.maxValue = 100
|
||||||
|
item.view = slider;
|
||||||
|
item.customizationLabel = config.name
|
||||||
|
configs[identifier]?.view = slider
|
||||||
|
configs[identifier]?.item = item
|
||||||
|
item.addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(
|
||||||
|
forKeyPath keyPath: String?,
|
||||||
|
of object: Any?,
|
||||||
|
change: [NSKeyValueChangeKey:Any]?,
|
||||||
|
context: UnsafeMutableRawPointer?
|
||||||
|
) {
|
||||||
|
guard let visible = change?[.newKey] as? Bool else { return }
|
||||||
|
if keyPath == "isVisible" && visible {
|
||||||
|
updateTouchBarTimeItems()
|
||||||
|
updatePlayButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTouchBarTimeItems() {
|
||||||
|
if !isVisible { return }
|
||||||
|
updateSlider()
|
||||||
|
updateTimeLeft()
|
||||||
|
updateCurrentPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSlider() {
|
||||||
|
guard let config = configs[.seekBar], let slider = config.view as? NSSlider else { return }
|
||||||
|
if !(config.item?.isVisible ?? false) { return }
|
||||||
|
|
||||||
|
if duration <= 0 {
|
||||||
|
slider.isEnabled = false
|
||||||
|
slider.doubleValue = 0
|
||||||
|
} else {
|
||||||
|
slider.isEnabled = true
|
||||||
|
if (!slider.isHighlighted) {
|
||||||
|
slider.doubleValue = (position / duration) * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTimeLeft() {
|
||||||
|
guard let config = configs[.timeLeft], let text = config.view as? NSTextField else { return }
|
||||||
|
if !(config.item?.isVisible ?? false) { return }
|
||||||
|
|
||||||
|
removeConstraintFor(identifier: .timeLeft)
|
||||||
|
if duration <= 0 {
|
||||||
|
text.stringValue = ""
|
||||||
|
} else {
|
||||||
|
let left = Int(floor(duration) - floor(position))
|
||||||
|
let leftFormat = format(time: left)
|
||||||
|
let durationFormat = format(time: Int(duration))
|
||||||
|
text.stringValue = "-\(leftFormat)"
|
||||||
|
applyConstraintFrom(string: "-\(durationFormat)", identifier: .timeLeft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCurrentPosition() {
|
||||||
|
guard let config = configs[.currentPosition], let text = config.view as? NSTextField else { return }
|
||||||
|
if !(config.item?.isVisible ?? false) { return }
|
||||||
|
|
||||||
|
text.stringValue = format(time: Int(floor(position)))
|
||||||
|
removeConstraintFor(identifier: .currentPosition)
|
||||||
|
if duration <= 0 {
|
||||||
|
applyConstraintFrom(string: format(time: Int(position)), identifier: .currentPosition)
|
||||||
|
} else {
|
||||||
|
applyConstraintFrom(string: format(time: Int(duration)), identifier: .currentPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePlayButton() {
|
||||||
|
guard let config = configs[.play], let button = config.view as? NSButton else { return }
|
||||||
|
if !isVisible || !(config.item?.isVisible ?? false) { return }
|
||||||
|
|
||||||
|
if isPaused {
|
||||||
|
button.image = configs[.play]?.imageAlt
|
||||||
|
} else {
|
||||||
|
button.image = configs[.play]?.image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func buttonAction(_ button: NSButton) {
|
||||||
|
guard let identifier = getIdentifierFrom(view: button), let command = configs[identifier]?.command else { return }
|
||||||
|
EventsResponder.sharedInstance().inputHelper.command(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func seekbarChanged(_ slider: NSSlider) {
|
||||||
|
guard let identifier = getIdentifierFrom(view: slider), let command = configs[identifier]?.command else { return }
|
||||||
|
EventsResponder.sharedInstance().inputHelper.command(String(format: command, slider.doubleValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
func format(time: Int) -> String {
|
||||||
|
let seconds = time % 60
|
||||||
|
let minutes = (time / 60) % 60
|
||||||
|
let hours = time / (60 * 60)
|
||||||
|
|
||||||
|
var stime = hours > 0 ? "\(hours):" : ""
|
||||||
|
stime = (stime.count > 0 || minutes > 9) ? String(format: "%@%02d:", stime, minutes) : "\(minutes):"
|
||||||
|
stime = String(format: "%@%02d", stime, seconds)
|
||||||
|
|
||||||
|
return stime
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeConstraintFor(identifier: NSTouchBarItem.Identifier) {
|
||||||
|
guard let text = configs[identifier]?.view as? NSTextField,
|
||||||
|
let constraint = configs[identifier]?.constraint as? NSLayoutConstraint else { return }
|
||||||
|
text.removeConstraint(constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyConstraintFrom(string: String, identifier: NSTouchBarItem.Identifier) {
|
||||||
|
guard let text = configs[identifier]?.view as? NSTextField else { return }
|
||||||
|
let fString = string.components(separatedBy: .decimalDigits).joined(separator: "0")
|
||||||
|
let textField = NSTextField(labelWithString: fString)
|
||||||
|
let size = textField.frame.size
|
||||||
|
|
||||||
|
let con = NSLayoutConstraint(item: text, attribute: .width, relatedBy: .equal, toItem: nil,
|
||||||
|
attribute: .notAnAttribute, multiplier: 1.0, constant: ceil(size.width * 1.1))
|
||||||
|
text.addConstraint(con)
|
||||||
|
configs[identifier]?.constraint = con
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIdentifierFrom(view: NSView) -> NSTouchBarItem.Identifier? {
|
||||||
|
for (identifier, config) in configs {
|
||||||
|
if config.view == view {
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func processEvent(_ event: UnsafeMutablePointer<mpv_event>) {
|
||||||
|
switch event.pointee.event_id {
|
||||||
|
case MPV_EVENT_END_FILE:
|
||||||
|
position = 0
|
||||||
|
duration = 0
|
||||||
|
case MPV_EVENT_PROPERTY_CHANGE:
|
||||||
|
handlePropertyChange(event)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePropertyChange(_ event: UnsafeMutablePointer<mpv_event>) {
|
||||||
|
let pData = OpaquePointer(event.pointee.data)
|
||||||
|
guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else { return }
|
||||||
|
|
||||||
|
switch String(cString: property.name) {
|
||||||
|
case "time-pos" where property.format == MPV_FORMAT_DOUBLE:
|
||||||
|
let newPosition = max(LibmpvHelper.mpvDoubleToDouble(property.data) ?? 0, 0)
|
||||||
|
if Int(floor(newPosition) - floor(position)) != 0 {
|
||||||
|
position = newPosition
|
||||||
|
updateTouchBarTimeItems()
|
||||||
|
}
|
||||||
|
case "duration" where property.format == MPV_FORMAT_DOUBLE:
|
||||||
|
duration = LibmpvHelper.mpvDoubleToDouble(property.data) ?? 0
|
||||||
|
updateTouchBarTimeItems()
|
||||||
|
case "pause" where property.format == MPV_FORMAT_FLAG:
|
||||||
|
isPaused = LibmpvHelper.mpvFlagToBool(property.data) ?? false
|
||||||
|
updatePlayButton()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of mpv.
|
|
||||||
*
|
|
||||||
* mpv is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* mpv is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
#define BASE_ID @"io.mpv.touchbar"
|
|
||||||
static NSTouchBarCustomizationIdentifier customID = BASE_ID;
|
|
||||||
static NSTouchBarItemIdentifier seekBar = BASE_ID ".seekbar";
|
|
||||||
static NSTouchBarItemIdentifier play = BASE_ID ".play";
|
|
||||||
static NSTouchBarItemIdentifier nextItem = BASE_ID ".nextItem";
|
|
||||||
static NSTouchBarItemIdentifier previousItem = BASE_ID ".previousItem";
|
|
||||||
static NSTouchBarItemIdentifier nextChapter = BASE_ID ".nextChapter";
|
|
||||||
static NSTouchBarItemIdentifier previousChapter = BASE_ID ".previousChapter";
|
|
||||||
static NSTouchBarItemIdentifier cycleAudio = BASE_ID ".cycleAudio";
|
|
||||||
static NSTouchBarItemIdentifier cycleSubtitle = BASE_ID ".cycleSubtitle";
|
|
||||||
static NSTouchBarItemIdentifier currentPosition = BASE_ID ".currentPosition";
|
|
||||||
static NSTouchBarItemIdentifier timeLeft = BASE_ID ".timeLeft";
|
|
||||||
|
|
||||||
struct mpv_event;
|
|
||||||
|
|
||||||
@interface TouchBar : NSTouchBar <NSTouchBarDelegate>
|
|
||||||
|
|
||||||
-(void)processEvent:(struct mpv_event *)event;
|
|
||||||
|
|
||||||
@property(nonatomic, retain) NSDictionary *touchbarItems;
|
|
||||||
@property(nonatomic, assign) double duration;
|
|
||||||
@property(nonatomic, assign) double position;
|
|
||||||
@property(nonatomic, assign) int pause;
|
|
||||||
|
|
||||||
@end
|
|
@ -1,342 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of mpv.
|
|
||||||
*
|
|
||||||
* mpv is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* mpv is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "player/client.h"
|
|
||||||
#import "osdep/mac/touchbar.h"
|
|
||||||
#import "osdep/mac/events_objc.h"
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#if HAVE_SWIFT
|
|
||||||
#include "osdep/mac/swift.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@implementation TouchBar
|
|
||||||
|
|
||||||
@synthesize touchbarItems = _touchbar_items;
|
|
||||||
@synthesize duration = _duration;
|
|
||||||
@synthesize position = _position;
|
|
||||||
@synthesize pause = _pause;
|
|
||||||
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
if (self = [super init]) {
|
|
||||||
self.touchbarItems = @{
|
|
||||||
seekBar: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"slider",
|
|
||||||
@"name": @"Seek Bar",
|
|
||||||
@"cmd": @"seek %f absolute-percent"
|
|
||||||
}],
|
|
||||||
play: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Play Button",
|
|
||||||
@"cmd": @"cycle pause",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate],
|
|
||||||
@"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]
|
|
||||||
}],
|
|
||||||
previousItem: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Previous Playlist Item",
|
|
||||||
@"cmd": @"playlist-prev",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate]
|
|
||||||
}],
|
|
||||||
nextItem: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Next Playlist Item",
|
|
||||||
@"cmd": @"playlist-next",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate]
|
|
||||||
}],
|
|
||||||
previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Previous Chapter",
|
|
||||||
@"cmd": @"add chapter -1",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate]
|
|
||||||
}],
|
|
||||||
nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Next Chapter",
|
|
||||||
@"cmd": @"add chapter 1",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate]
|
|
||||||
}],
|
|
||||||
cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Cycle Audio",
|
|
||||||
@"cmd": @"cycle audio",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate]
|
|
||||||
}],
|
|
||||||
cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"button",
|
|
||||||
@"name": @"Cycle Subtitle",
|
|
||||||
@"cmd": @"cycle sub",
|
|
||||||
@"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate]
|
|
||||||
}],
|
|
||||||
currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"text",
|
|
||||||
@"name": @"Current Position"
|
|
||||||
}],
|
|
||||||
timeLeft: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
||||||
@"type": @"text",
|
|
||||||
@"name": @"Time Left"
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
[self addObserver:self forKeyPath:@"visible" options:0 context:nil];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
|
|
||||||
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
|
|
||||||
{
|
|
||||||
if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
|
|
||||||
NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
|
||||||
NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)];
|
|
||||||
slider.minValue = 0.0f;
|
|
||||||
slider.maxValue = 100.0f;
|
|
||||||
tbItem.view = slider;
|
|
||||||
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
|
|
||||||
[self.touchbarItems[identifier] setObject:slider forKey:@"view"];
|
|
||||||
[self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
|
|
||||||
[tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
|
|
||||||
return tbItem;
|
|
||||||
} else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) {
|
|
||||||
NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
|
||||||
NSImage *tbImage = self.touchbarItems[identifier][@"image"];
|
|
||||||
NSButton *tbButton = [NSButton buttonWithImage:tbImage target:self action:@selector(buttonAction:)];
|
|
||||||
tbItem.view = tbButton;
|
|
||||||
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
|
|
||||||
[self.touchbarItems[identifier] setObject:tbButton forKey:@"view"];
|
|
||||||
[self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
|
|
||||||
[tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
|
|
||||||
return tbItem;
|
|
||||||
} else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) {
|
|
||||||
NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
|
||||||
NSTextField *tbText = [NSTextField labelWithString:@"0:00"];
|
|
||||||
tbText.alignment = NSTextAlignmentCenter;
|
|
||||||
tbItem.view = tbText;
|
|
||||||
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
|
|
||||||
[self.touchbarItems[identifier] setObject:tbText forKey:@"view"];
|
|
||||||
[self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
|
|
||||||
[tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
|
|
||||||
return tbItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
||||||
ofObject:(id)object
|
|
||||||
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
|
|
||||||
context:(void *)context {
|
|
||||||
if ([keyPath isEqualToString:@"visible"]) {
|
|
||||||
NSNumber *visible = [object valueForKey:@"visible"];
|
|
||||||
if (visible.boolValue) {
|
|
||||||
[self updateTouchBarTimeItems];
|
|
||||||
[self updatePlayButton];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateTouchBarTimeItems
|
|
||||||
{
|
|
||||||
if (!self.isVisible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
[self updateSlider];
|
|
||||||
[self updateTimeLeft];
|
|
||||||
[self updateCurrentPosition];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateSlider
|
|
||||||
{
|
|
||||||
NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"];
|
|
||||||
if (!tbItem.visible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
|
|
||||||
|
|
||||||
if (self.duration <= 0) {
|
|
||||||
seekSlider.enabled = NO;
|
|
||||||
seekSlider.doubleValue = 0;
|
|
||||||
} else {
|
|
||||||
seekSlider.enabled = YES;
|
|
||||||
if (!seekSlider.highlighted)
|
|
||||||
seekSlider.doubleValue = (self.position/self.duration)*100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateTimeLeft
|
|
||||||
{
|
|
||||||
NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"];
|
|
||||||
if (!tbItem.visible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
|
|
||||||
|
|
||||||
[self removeConstraintForIdentifier:timeLeft];
|
|
||||||
if (self.duration <= 0) {
|
|
||||||
timeLeftItem.stringValue = @"";
|
|
||||||
} else {
|
|
||||||
int left = (int)(floor(self.duration)-floor(self.position));
|
|
||||||
NSString *leftFormat = [self formatTime:left];
|
|
||||||
NSString *durFormat = [self formatTime:self.duration];
|
|
||||||
timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
|
|
||||||
[self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
|
|
||||||
forIdentifier:timeLeft];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateCurrentPosition
|
|
||||||
{
|
|
||||||
NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"];
|
|
||||||
if (!tbItem.visible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
|
|
||||||
NSString *posFormat = [self formatTime:(int)floor(self.position)];
|
|
||||||
curPosItem.stringValue = posFormat;
|
|
||||||
|
|
||||||
[self removeConstraintForIdentifier:currentPosition];
|
|
||||||
if (self.duration <= 0) {
|
|
||||||
[self applyConstraintFromString:[self formatTime:self.position]
|
|
||||||
forIdentifier:currentPosition];
|
|
||||||
} else {
|
|
||||||
NSString *durFormat = [self formatTime:self.duration];
|
|
||||||
[self applyConstraintFromString:durFormat forIdentifier:currentPosition];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updatePlayButton
|
|
||||||
{
|
|
||||||
NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"];
|
|
||||||
if (!self.isVisible || !tbItem.visible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NSButton *playButton = self.touchbarItems[play][@"view"];
|
|
||||||
if (self.pause) {
|
|
||||||
playButton.image = self.touchbarItems[play][@"imageAlt"];
|
|
||||||
} else {
|
|
||||||
playButton.image = self.touchbarItems[play][@"image"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)buttonAction:(NSButton *)sender
|
|
||||||
{
|
|
||||||
NSString *identifier = [self getIdentifierFromView:sender];
|
|
||||||
[[EventsResponder sharedInstance].inputHelper command:self.touchbarItems[identifier][@"cmd"]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)seekbarChanged:(NSSlider *)slider
|
|
||||||
{
|
|
||||||
NSString *identifier = [self getIdentifierFromView:slider];
|
|
||||||
NSString *seek = [NSString stringWithFormat:
|
|
||||||
self.touchbarItems[identifier][@"cmd"], slider.doubleValue];
|
|
||||||
[[EventsResponder sharedInstance].inputHelper command:seek];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)formatTime:(int)time
|
|
||||||
{
|
|
||||||
int seconds = time % 60;
|
|
||||||
int minutes = (time / 60) % 60;
|
|
||||||
int hours = time / (60 * 60);
|
|
||||||
|
|
||||||
NSString *stime = hours > 0 ? [NSString stringWithFormat:@"%d:", hours] : @"";
|
|
||||||
stime = (stime.length > 0 || minutes > 9) ?
|
|
||||||
[NSString stringWithFormat:@"%@%02d:", stime, minutes] :
|
|
||||||
[NSString stringWithFormat:@"%d:", minutes];
|
|
||||||
stime = [NSString stringWithFormat:@"%@%02d", stime, seconds];
|
|
||||||
|
|
||||||
return stime;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeConstraintForIdentifier:(NSTouchBarItemIdentifier)identifier
|
|
||||||
{
|
|
||||||
NSTextField *field = self.touchbarItems[identifier][@"view"];
|
|
||||||
[field removeConstraint:self.touchbarItems[identifier][@"constrain"]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applyConstraintFromString:(NSString *)string
|
|
||||||
forIdentifier:(NSTouchBarItemIdentifier)identifier
|
|
||||||
{
|
|
||||||
NSTextField *field = self.touchbarItems[identifier][@"view"];
|
|
||||||
if (field) {
|
|
||||||
NSString *fString = [[string componentsSeparatedByCharactersInSet:
|
|
||||||
[NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"];
|
|
||||||
NSTextField *textField = [NSTextField labelWithString:fString];
|
|
||||||
NSSize size = [textField frame].size;
|
|
||||||
|
|
||||||
NSLayoutConstraint *con =
|
|
||||||
[NSLayoutConstraint constraintWithItem:field
|
|
||||||
attribute:NSLayoutAttributeWidth
|
|
||||||
relatedBy:NSLayoutRelationEqual
|
|
||||||
toItem:nil
|
|
||||||
attribute:NSLayoutAttributeNotAnAttribute
|
|
||||||
multiplier:1.0
|
|
||||||
constant:(int)ceil(size.width*1.1)];
|
|
||||||
[field addConstraint:con];
|
|
||||||
[self.touchbarItems[identifier] setObject:con forKey:@"constrain"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)getIdentifierFromView:(id)view
|
|
||||||
{
|
|
||||||
NSString *identifier;
|
|
||||||
for (identifier in self.touchbarItems)
|
|
||||||
if([self.touchbarItems[identifier][@"view"] isEqual:view])
|
|
||||||
break;
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)processEvent:(struct mpv_event *)event
|
|
||||||
{
|
|
||||||
switch (event->event_id) {
|
|
||||||
case MPV_EVENT_END_FILE: {
|
|
||||||
self.position = 0;
|
|
||||||
self.duration = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
|
||||||
[self handlePropertyChange:(mpv_event_property *)event->data];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handlePropertyChange:(struct mpv_event_property *)property
|
|
||||||
{
|
|
||||||
NSString *name = [NSString stringWithUTF8String:property->name];
|
|
||||||
mpv_format format = property->format;
|
|
||||||
|
|
||||||
if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
|
|
||||||
double newPosition = *(double *)property->data;
|
|
||||||
newPosition = newPosition < 0 ? 0 : newPosition;
|
|
||||||
if ((int)(floor(newPosition) - floor(self.position)) != 0) {
|
|
||||||
self.position = newPosition;
|
|
||||||
[self updateTouchBarTimeItems];
|
|
||||||
}
|
|
||||||
} else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
|
|
||||||
self.duration = *(double *)property->data;
|
|
||||||
[self updateTouchBarTimeItems];
|
|
||||||
} else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
|
|
||||||
self.pause = *(int *)property->data;
|
|
||||||
[self updatePlayButton];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
Loading…
Reference in New Issue
Block a user