refactor: replace macos_window_utils
This commit is contained in:
@@ -1,64 +1,17 @@
|
|||||||
import 'package:macos_window_utils/macos_window_utils.dart';
|
|
||||||
import 'package:macos_window_utils/macos/ns_window_delegate.dart';
|
|
||||||
import 'package:macos_window_utils/macos/ns_window_button_type.dart';
|
|
||||||
import 'package:flutter/material.dart' show Offset;
|
|
||||||
import 'fullscreen_state_manager.dart';
|
import 'fullscreen_state_manager.dart';
|
||||||
|
import 'macos_window_delegate.dart';
|
||||||
|
|
||||||
/// Custom window delegate that manages titlebar configuration during fullscreen transitions
|
/// Custom window delegate that manages fullscreen state
|
||||||
class FullscreenWindowDelegate extends NSWindowDelegate {
|
/// Note: Window manipulation (toolbar, titlebar, traffic lights) is now handled
|
||||||
static const double _customButtonY = 21.0;
|
/// directly in Swift's WindowDelegate. This class only updates Dart-side state.
|
||||||
|
class FullscreenWindowDelegate extends MacOSWindowDelegate {
|
||||||
@override
|
@override
|
||||||
void windowWillEnterFullScreen() {
|
void windowWillEnterFullScreen() {
|
||||||
// Notify global state manager
|
|
||||||
FullscreenStateManager().setFullscreen(true);
|
FullscreenStateManager().setFullscreen(true);
|
||||||
|
|
||||||
// Remove toolbar and restore default titlebar before entering fullscreen
|
|
||||||
_prepareForFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void windowWillExitFullScreen() {
|
|
||||||
// Hide title and make transparent immediately (safe to do before transition)
|
|
||||||
WindowManipulator.hideTitle();
|
|
||||||
WindowManipulator.makeTitlebarTransparent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void windowDidExitFullScreen() {
|
void windowDidExitFullScreen() {
|
||||||
// Notify global state manager
|
|
||||||
FullscreenStateManager().setFullscreen(false);
|
FullscreenStateManager().setFullscreen(false);
|
||||||
|
|
||||||
// Add toolbar and reposition traffic lights after transition completes
|
|
||||||
WindowManipulator.addToolbar();
|
|
||||||
|
|
||||||
// Restore custom traffic light positions
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.closeButton,
|
|
||||||
offset: const Offset(20, _customButtonY),
|
|
||||||
);
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.miniaturizeButton,
|
|
||||||
offset: const Offset(40, _customButtonY),
|
|
||||||
);
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.zoomButton,
|
|
||||||
offset: const Offset(60, _customButtonY),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare titlebar for fullscreen mode
|
|
||||||
void _prepareForFullscreen() {
|
|
||||||
WindowManipulator.removeToolbar();
|
|
||||||
WindowManipulator.showTitle();
|
|
||||||
WindowManipulator.makeTitlebarOpaque();
|
|
||||||
|
|
||||||
// Set traffic lights to standard fullscreen positions (null = default)
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(buttonType: NSWindowButtonType.closeButton, offset: null);
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.miniaturizeButton,
|
|
||||||
offset: null,
|
|
||||||
);
|
|
||||||
WindowManipulator.overrideStandardWindowButtonPosition(buttonType: NSWindowButtonType.zoomButton, offset: null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,25 @@
|
|||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'package:flutter/material.dart' show Offset;
|
|
||||||
import 'package:macos_window_utils/macos_window_utils.dart';
|
|
||||||
import 'package:macos_window_utils/macos/ns_window_button_type.dart';
|
|
||||||
import 'fullscreen_window_delegate.dart';
|
import 'fullscreen_window_delegate.dart';
|
||||||
|
import 'macos_window_service.dart';
|
||||||
|
|
||||||
/// Service to manage macOS titlebar configuration
|
/// Service to manage macOS titlebar configuration
|
||||||
class MacOSTitlebarService {
|
class MacOSTitlebarService {
|
||||||
// Standard button Y position when using custom toolbar
|
static bool _initialized = false;
|
||||||
static const double _customButtonY = 21.0;
|
|
||||||
|
|
||||||
/// Initialize the custom titlebar setup (transparent with toolbar)
|
/// Initialize the custom titlebar setup.
|
||||||
/// This configuration automatically handles fullscreen mode natively
|
///
|
||||||
|
/// Note: The initial window configuration (transparent titlebar, toolbar,
|
||||||
|
/// button positions, fullscreen presentation options) is now applied in
|
||||||
|
/// MainFlutterWindow.swift / WindowDelegate.swift BEFORE frame restoration
|
||||||
|
/// to prevent the window from shrinking on launch.
|
||||||
|
///
|
||||||
|
/// This method only sets up the Dart-side callbacks.
|
||||||
static Future<void> setupCustomTitlebar() async {
|
static Future<void> setupCustomTitlebar() async {
|
||||||
if (!Platform.isMacOS) return;
|
if (!Platform.isMacOS || _initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
// Enable window delegate to use presentation options and fullscreen callbacks
|
await MacOSWindowService.initialize(enableWindowDelegate: true);
|
||||||
await WindowManipulator.initialize(enableWindowDelegate: true);
|
|
||||||
|
|
||||||
// Register custom delegate to handle fullscreen transitions
|
|
||||||
final delegate = FullscreenWindowDelegate();
|
final delegate = FullscreenWindowDelegate();
|
||||||
WindowManipulator.addNSWindowDelegate(delegate);
|
MacOSWindowService.addWindowDelegate(delegate);
|
||||||
|
|
||||||
// Make titlebar transparent but keep it functional
|
|
||||||
await WindowManipulator.makeTitlebarTransparent();
|
|
||||||
await WindowManipulator.hideTitle();
|
|
||||||
await WindowManipulator.enableFullSizeContentView();
|
|
||||||
|
|
||||||
// Add toolbar to create space for traffic lights in normal mode
|
|
||||||
await WindowManipulator.addToolbar();
|
|
||||||
|
|
||||||
// Set custom traffic light positions for normal mode
|
|
||||||
await _setCustomButtonPositions();
|
|
||||||
|
|
||||||
// Configure fullscreen presentation to auto-hide toolbar and menubar
|
|
||||||
// This tells macOS to automatically hide the toolbar when entering fullscreen
|
|
||||||
final presentationOptions = NSAppPresentationOptions.from({
|
|
||||||
NSAppPresentationOption.fullScreen,
|
|
||||||
NSAppPresentationOption.autoHideToolbar,
|
|
||||||
NSAppPresentationOption.autoHideMenuBar,
|
|
||||||
NSAppPresentationOption.autoHideDock,
|
|
||||||
});
|
|
||||||
presentationOptions.applyAsFullScreenPresentationOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set traffic light buttons to custom positions (with toolbar offset)
|
|
||||||
static Future<void> _setCustomButtonPositions() async {
|
|
||||||
await WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.closeButton,
|
|
||||||
offset: const Offset(20, _customButtonY),
|
|
||||||
);
|
|
||||||
await WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.miniaturizeButton,
|
|
||||||
offset: const Offset(40, _customButtonY),
|
|
||||||
);
|
|
||||||
await WindowManipulator.overrideStandardWindowButtonPosition(
|
|
||||||
buttonType: NSWindowButtonType.zoomButton,
|
|
||||||
offset: const Offset(60, _customButtonY),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/// Abstract class for receiving macOS window delegate callbacks.
|
||||||
|
/// Extend this class and register with MacOSWindowService to receive
|
||||||
|
/// fullscreen transition events.
|
||||||
|
abstract class MacOSWindowDelegate {
|
||||||
|
/// Called when the window is about to enter fullscreen mode.
|
||||||
|
void windowWillEnterFullScreen() {}
|
||||||
|
|
||||||
|
/// Called when the window has entered fullscreen mode.
|
||||||
|
void windowDidEnterFullScreen() {}
|
||||||
|
|
||||||
|
/// Called when the window is about to exit fullscreen mode.
|
||||||
|
void windowWillExitFullScreen() {}
|
||||||
|
|
||||||
|
/// Called when the window has exited fullscreen mode.
|
||||||
|
void windowDidExitFullScreen() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'macos_window_delegate.dart';
|
||||||
|
|
||||||
|
/// Service for manipulating macOS window properties.
|
||||||
|
/// This is a native implementation replacing the macos_window_utils package.
|
||||||
|
///
|
||||||
|
/// Note: Titlebar, toolbar, and traffic light position management is now handled
|
||||||
|
/// directly in Swift (WindowDelegate.swift) during fullscreen transitions.
|
||||||
|
/// This service only exposes what's needed externally:
|
||||||
|
/// - Traffic light visibility (for video controls)
|
||||||
|
/// - Fullscreen enter/exit (for video controls)
|
||||||
|
/// - Delegate registration (for FullscreenStateManager updates)
|
||||||
|
class MacOSWindowService {
|
||||||
|
static const _channel = MethodChannel('com.plezy/window_utils');
|
||||||
|
static bool _initialized = false;
|
||||||
|
static bool _delegateEnabled = false;
|
||||||
|
static final List<MacOSWindowDelegate> _delegates = [];
|
||||||
|
|
||||||
|
// MARK: - Private Helpers
|
||||||
|
|
||||||
|
static Future<void> _invoke(String method, [Map<String, dynamic>? args]) async {
|
||||||
|
if (!Platform.isMacOS) return;
|
||||||
|
await _channel.invokeMethod(method, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _notifyDelegates(void Function(MacOSWindowDelegate) callback) {
|
||||||
|
for (final delegate in _delegates) {
|
||||||
|
callback(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<dynamic> _handleMethodCall(MethodCall call) async {
|
||||||
|
switch (call.method) {
|
||||||
|
case 'windowWillEnterFullScreen':
|
||||||
|
_notifyDelegates((d) => d.windowWillEnterFullScreen());
|
||||||
|
case 'windowDidEnterFullScreen':
|
||||||
|
_notifyDelegates((d) => d.windowDidEnterFullScreen());
|
||||||
|
case 'windowWillExitFullScreen':
|
||||||
|
_notifyDelegates((d) => d.windowWillExitFullScreen());
|
||||||
|
case 'windowDidExitFullScreen':
|
||||||
|
_notifyDelegates((d) => d.windowDidExitFullScreen());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
/// Initialize the window service.
|
||||||
|
/// Must be called before using other methods.
|
||||||
|
/// Set [enableWindowDelegate] to true to receive fullscreen callbacks.
|
||||||
|
static Future<void> initialize({bool enableWindowDelegate = false}) async {
|
||||||
|
if (!Platform.isMacOS) return;
|
||||||
|
|
||||||
|
if (!_initialized) {
|
||||||
|
await _channel.invokeMethod('initialize', {
|
||||||
|
'enableWindowDelegate': enableWindowDelegate,
|
||||||
|
});
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up handler if not already done and delegate is requested
|
||||||
|
if (enableWindowDelegate && !_delegateEnabled) {
|
||||||
|
_channel.setMethodCallHandler(_handleMethodCall);
|
||||||
|
_delegateEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a delegate to receive window events.
|
||||||
|
static void addWindowDelegate(MacOSWindowDelegate delegate) {
|
||||||
|
if (!_delegates.contains(delegate)) {
|
||||||
|
_delegates.add(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a previously added delegate.
|
||||||
|
static void removeWindowDelegate(MacOSWindowDelegate delegate) {
|
||||||
|
_delegates.remove(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Traffic Light Buttons
|
||||||
|
|
||||||
|
/// Show or hide all traffic light buttons (close, miniaturize, zoom).
|
||||||
|
static Future<void> setTrafficLightsVisible(bool visible) =>
|
||||||
|
_invoke('setTrafficLightsVisible', {'visible': visible});
|
||||||
|
|
||||||
|
// MARK: - Fullscreen
|
||||||
|
|
||||||
|
/// Enter fullscreen mode.
|
||||||
|
static Future<void> enterFullscreen() => _invoke('enterFullscreen');
|
||||||
|
|
||||||
|
/// Exit fullscreen mode.
|
||||||
|
static Future<void> exitFullscreen() => _invoke('exitFullscreen');
|
||||||
|
|
||||||
|
/// Check if the window is in fullscreen mode.
|
||||||
|
static Future<bool> isFullscreen() async {
|
||||||
|
if (!Platform.isMacOS) return false;
|
||||||
|
return await _channel.invokeMethod<bool>('isFullscreen') ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import 'package:flutter/services.dart'
|
|||||||
KeyEvent,
|
KeyEvent,
|
||||||
KeyDownEvent,
|
KeyDownEvent,
|
||||||
HardwareKeyboard;
|
HardwareKeyboard;
|
||||||
import 'package:macos_window_utils/macos_window_utils.dart';
|
import '../../services/macos_window_service.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../mpv/mpv.dart';
|
import '../../mpv/mpv.dart';
|
||||||
@@ -633,17 +633,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateTrafficLightVisibility() async {
|
void _updateTrafficLightVisibility() async {
|
||||||
if (Platform.isMacOS) {
|
await MacOSWindowService.setTrafficLightsVisible(_showControls);
|
||||||
if (_showControls) {
|
|
||||||
await WindowManipulator.showCloseButton();
|
|
||||||
await WindowManipulator.showMiniaturizeButton();
|
|
||||||
await WindowManipulator.showZoomButton();
|
|
||||||
} else {
|
|
||||||
await WindowManipulator.hideCloseButton();
|
|
||||||
await WindowManipulator.hideMiniaturizeButton();
|
|
||||||
await WindowManipulator.hideZoomButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadPlaybackExtras() async {
|
Future<void> _loadPlaybackExtras() async {
|
||||||
@@ -1081,9 +1071,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
|||||||
// Use native macOS fullscreen - titlebar is handled automatically
|
// Use native macOS fullscreen - titlebar is handled automatically
|
||||||
// Window listener will update _isFullscreen for UI
|
// Window listener will update _isFullscreen for UI
|
||||||
if (isCurrentlyFullscreen) {
|
if (isCurrentlyFullscreen) {
|
||||||
await WindowManipulator.exitFullscreen();
|
await MacOSWindowService.exitFullscreen();
|
||||||
} else {
|
} else {
|
||||||
await WindowManipulator.enterFullscreen();
|
await MacOSWindowService.enterFullscreen();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For Windows/Linux, use window_manager
|
// For Windows/Linux, use window_manager
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import flutter_webrtc
|
|||||||
import gamepads_darwin
|
import gamepads_darwin
|
||||||
import hotkey_manager_macos
|
import hotkey_manager_macos
|
||||||
import in_app_review
|
import in_app_review
|
||||||
import macos_window_utils
|
|
||||||
import os_media_controls
|
import os_media_controls
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
@@ -32,7 +31,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
GamepadsDarwinPlugin.register(with: registry.registrar(forPlugin: "GamepadsDarwinPlugin"))
|
GamepadsDarwinPlugin.register(with: registry.registrar(forPlugin: "GamepadsDarwinPlugin"))
|
||||||
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
||||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
|
||||||
OsMediaControlsPlugin.register(with: registry.registrar(forPlugin: "OsMediaControlsPlugin"))
|
OsMediaControlsPlugin.register(with: registry.registrar(forPlugin: "OsMediaControlsPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ PODS:
|
|||||||
- HotKey
|
- HotKey
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- macos_window_utils (1.0.0):
|
|
||||||
- FlutterMacOS
|
|
||||||
- os_media_controls (0.0.1):
|
- os_media_controls (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- package_info_plus (0.0.1):
|
- package_info_plus (0.0.1):
|
||||||
@@ -76,7 +74,6 @@ DEPENDENCIES:
|
|||||||
- gamepads_darwin (from `Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos`)
|
- gamepads_darwin (from `Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos`)
|
||||||
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
|
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
|
||||||
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
||||||
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
|
||||||
- os_media_controls (from `Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos`)
|
- os_media_controls (from `Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos`)
|
||||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
@@ -111,8 +108,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos
|
||||||
in_app_review:
|
in_app_review:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
||||||
macos_window_utils:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
|
|
||||||
os_media_controls:
|
os_media_controls:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
@@ -144,7 +139,6 @@ SPEC CHECKSUMS:
|
|||||||
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||||
hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe
|
hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe
|
||||||
in_app_review: 66e7680752b632d83f4f0e88b34d52ed303fbff4
|
in_app_review: 66e7680752b632d83f4f0e88b34d52ed303fbff4
|
||||||
macos_window_utils: 23f54331a0fd51eea9e0ed347253bf48fd379d1d
|
|
||||||
os_media_controls: c07c04c4afdf59dda0a3f398457a46823c4ce0ed
|
os_media_controls: c07c04c4afdf59dda0a3f398457a46823c4ce0ed
|
||||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
6AC86ED72EA70B4C0067BC66 /* plezy.icon in Resources */ = {isa = PBXBuildFile; fileRef = 6AC86ED62EA70B4C0067BC66 /* plezy.icon */; };
|
6AC86ED72EA70B4C0067BC66 /* plezy.icon in Resources */ = {isa = PBXBuildFile; fileRef = 6AC86ED62EA70B4C0067BC66 /* plezy.icon */; };
|
||||||
6AD8B1612ED7B50000E9E1B4 /* MpvPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1632ED7B50000E9E1B4 /* MpvPlayerCore.swift */; };
|
6AD8B1612ED7B50000E9E1B4 /* MpvPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1632ED7B50000E9E1B4 /* MpvPlayerCore.swift */; };
|
||||||
6AD8B1622ED7B50000E9E1B4 /* MpvPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1642ED7B50000E9E1B4 /* MpvPlayerPlugin.swift */; };
|
6AD8B1622ED7B50000E9E1B4 /* MpvPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1642ED7B50000E9E1B4 /* MpvPlayerPlugin.swift */; };
|
||||||
|
6AD8B1662ED7B50000E9E1B5 /* WindowUtilsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1682ED7B50000E9E1B5 /* WindowUtilsPlugin.swift */; };
|
||||||
|
6AD8B1672ED7B50000E9E1B5 /* WindowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B1692ED7B50000E9E1B5 /* WindowDelegate.swift */; };
|
||||||
9AECA605E2E1BECE3CA5BD01 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78F9F23331B540A25C4A70C7 /* Pods_Runner.framework */; };
|
9AECA605E2E1BECE3CA5BD01 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78F9F23331B540A25C4A70C7 /* Pods_Runner.framework */; };
|
||||||
FE52EE1D2D489FA88507D14E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84260356652A3270E664FED4 /* Pods_RunnerTests.framework */; };
|
FE52EE1D2D489FA88507D14E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84260356652A3270E664FED4 /* Pods_RunnerTests.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@@ -86,6 +88,8 @@
|
|||||||
6AC86ED62EA70B4C0067BC66 /* plezy.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = plezy.icon; sourceTree = "<group>"; };
|
6AC86ED62EA70B4C0067BC66 /* plezy.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = plezy.icon; sourceTree = "<group>"; };
|
||||||
6AD8B1632ED7B50000E9E1B4 /* MpvPlayerCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MpvPlayerCore.swift; sourceTree = "<group>"; };
|
6AD8B1632ED7B50000E9E1B4 /* MpvPlayerCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MpvPlayerCore.swift; sourceTree = "<group>"; };
|
||||||
6AD8B1642ED7B50000E9E1B4 /* MpvPlayerPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MpvPlayerPlugin.swift; sourceTree = "<group>"; };
|
6AD8B1642ED7B50000E9E1B4 /* MpvPlayerPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MpvPlayerPlugin.swift; sourceTree = "<group>"; };
|
||||||
|
6AD8B1682ED7B50000E9E1B5 /* WindowUtilsPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowUtilsPlugin.swift; sourceTree = "<group>"; };
|
||||||
|
6AD8B1692ED7B50000E9E1B5 /* WindowDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDelegate.swift; sourceTree = "<group>"; };
|
||||||
78F9F23331B540A25C4A70C7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
78F9F23331B540A25C4A70C7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
84260356652A3270E664FED4 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
84260356652A3270E664FED4 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -196,6 +200,8 @@
|
|||||||
children = (
|
children = (
|
||||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||||
|
6AD8B1682ED7B50000E9E1B5 /* WindowUtilsPlugin.swift */,
|
||||||
|
6AD8B1692ED7B50000E9E1B5 /* WindowDelegate.swift */,
|
||||||
6AD8B1652ED7B50000E9E1B4 /* MpvPlayer */,
|
6AD8B1652ED7B50000E9E1B4 /* MpvPlayer */,
|
||||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||||
33E51914231749380026EE4D /* Release.entitlements */,
|
33E51914231749380026EE4D /* Release.entitlements */,
|
||||||
@@ -460,6 +466,8 @@
|
|||||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||||
6AD8B1612ED7B50000E9E1B4 /* MpvPlayerCore.swift in Sources */,
|
6AD8B1612ED7B50000E9E1B4 /* MpvPlayerCore.swift in Sources */,
|
||||||
6AD8B1622ED7B50000E9E1B4 /* MpvPlayerPlugin.swift in Sources */,
|
6AD8B1622ED7B50000E9E1B4 /* MpvPlayerPlugin.swift in Sources */,
|
||||||
|
6AD8B1662ED7B50000E9E1B5 /* WindowUtilsPlugin.swift in Sources */,
|
||||||
|
6AD8B1672ED7B50000E9E1B5 /* WindowDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,9 +13,26 @@ class MainFlutterWindow: NSWindow {
|
|||||||
self.contentViewController = flutterViewController
|
self.contentViewController = flutterViewController
|
||||||
self.setFrame(windowFrame, display: true)
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
|
// Apply initial window configuration BEFORE frame restoration
|
||||||
|
// This prevents the window from shrinking on launch
|
||||||
|
self.titlebarAppearsTransparent = true
|
||||||
|
self.titleVisibility = .hidden
|
||||||
|
self.styleMask.insert(.fullSizeContentView)
|
||||||
|
|
||||||
|
// Add forwarding toolbar for click-through in titlebar area
|
||||||
|
let toolbar = ForwardingToolbar(flutterViewController: flutterViewController)
|
||||||
|
self.toolbar = toolbar
|
||||||
|
|
||||||
// Register MPV player plugin for video playback
|
// Register MPV player plugin for video playback
|
||||||
MpvPlayerPlugin.register(with: flutterViewController.registrar(forPlugin: "MpvPlayerPlugin"))
|
MpvPlayerPlugin.register(with: flutterViewController.registrar(forPlugin: "MpvPlayerPlugin"))
|
||||||
|
|
||||||
|
// Register window utils plugin for dynamic titlebar/fullscreen control from Dart
|
||||||
|
WindowUtilsPlugin.register(with: flutterViewController.registrar(forPlugin: "WindowUtilsPlugin"))
|
||||||
|
WindowUtilsPlugin.setWindow(self)
|
||||||
|
|
||||||
|
// Set custom traffic light positions using centralized values from plugin
|
||||||
|
WindowUtilsPlugin.setInitialTrafficLightPositions()
|
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
// Re-insert after background layer but before Flutter control views
|
// Re-insert after background layer but before Flutter control views
|
||||||
layer.removeFromSuperlayer()
|
layer.removeFromSuperlayer()
|
||||||
if let superlayer = window?.contentView?.layer {
|
if let superlayer = window?.contentView?.layer {
|
||||||
superlayer.insertSublayer(layer, at: 1)
|
superlayer.insertSublayer(layer, at: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
class WindowDelegate: NSObject, NSWindowDelegate {
|
||||||
|
weak var channel: FlutterMethodChannel?
|
||||||
|
weak var window: NSWindow?
|
||||||
|
|
||||||
|
// Hardcoded presentation options for fullscreen mode
|
||||||
|
// Auto-hide toolbar, menu bar, and dock when in fullscreen
|
||||||
|
private let fullScreenPresentationOptions: NSApplication.PresentationOptions = [
|
||||||
|
.fullScreen,
|
||||||
|
.autoHideToolbar,
|
||||||
|
.autoHideMenuBar,
|
||||||
|
.autoHideDock
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - Private Helpers
|
||||||
|
|
||||||
|
private func emit(_ method: String) {
|
||||||
|
channel?.invokeMethod(method, arguments: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - NSWindowDelegate
|
||||||
|
|
||||||
|
func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions) -> NSApplication.PresentationOptions {
|
||||||
|
return fullScreenPresentationOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowWillEnterFullScreen(_ notification: Notification) {
|
||||||
|
guard let window = window else { return }
|
||||||
|
// Remove toolbar before entering fullscreen
|
||||||
|
window.toolbar = nil
|
||||||
|
// Show title and make titlebar opaque for native fullscreen look
|
||||||
|
window.titleVisibility = .visible
|
||||||
|
window.titlebarAppearsTransparent = false
|
||||||
|
// Reset traffic light positions to default
|
||||||
|
WindowUtilsPlugin.setTrafficLightPositions(custom: false, window: window)
|
||||||
|
// Notify Dart for state management only
|
||||||
|
emit("windowWillEnterFullScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowDidEnterFullScreen(_ notification: Notification) {
|
||||||
|
emit("windowDidEnterFullScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowWillExitFullScreen(_ notification: Notification) {
|
||||||
|
guard let window = window else { return }
|
||||||
|
// Hide title and make titlebar transparent BEFORE exiting
|
||||||
|
window.titleVisibility = .hidden
|
||||||
|
window.titlebarAppearsTransparent = true
|
||||||
|
emit("windowWillExitFullScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowDidExitFullScreen(_ notification: Notification) {
|
||||||
|
guard let window = window else { return }
|
||||||
|
// Restore toolbar
|
||||||
|
if let flutterVC = window.contentViewController {
|
||||||
|
let toolbar = ForwardingToolbar(flutterViewController: flutterVC)
|
||||||
|
window.toolbar = toolbar
|
||||||
|
}
|
||||||
|
// Restore custom traffic light positions
|
||||||
|
WindowUtilsPlugin.setTrafficLightPositions(custom: true, window: window)
|
||||||
|
emit("windowDidExitFullScreen")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
// MARK: - ForwardingView
|
||||||
|
// A view that forwards mouse events to the Flutter view controller
|
||||||
|
class ForwardingView: NSView {
|
||||||
|
weak var flutterViewController: NSViewController?
|
||||||
|
|
||||||
|
override func mouseDown(with event: NSEvent) {
|
||||||
|
flutterViewController?.mouseDown(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseUp(with event: NSEvent) {
|
||||||
|
flutterViewController?.mouseUp(with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ForwardingToolbar
|
||||||
|
// A custom toolbar that forwards mouse events from the toolbar area to Flutter
|
||||||
|
class ForwardingToolbar: NSToolbar, NSToolbarDelegate {
|
||||||
|
let flutterViewController: NSViewController
|
||||||
|
|
||||||
|
init(flutterViewController: NSViewController) {
|
||||||
|
self.flutterViewController = flutterViewController
|
||||||
|
super.init(identifier: "ForwardingToolbar")
|
||||||
|
self.delegate = self
|
||||||
|
self.showsBaselineSeparator = false
|
||||||
|
|
||||||
|
// Prevent toolbar customization UI (the "rounded box")
|
||||||
|
self.allowsUserCustomization = false
|
||||||
|
self.allowsExtensionItems = false
|
||||||
|
if #available(macOS 15.0, *) {
|
||||||
|
self.allowsDisplayModeCustomization = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
[.flexibleSpace, NSToolbarItem.Identifier("ForwardingItem")]
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
toolbarDefaultItemIdentifiers(toolbar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||||
|
if itemIdentifier == NSToolbarItem.Identifier("ForwardingItem") {
|
||||||
|
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||||
|
item.isBordered = false // Remove the rounded box appearance
|
||||||
|
let view = ForwardingView()
|
||||||
|
view.flutterViewController = flutterViewController
|
||||||
|
view.widthAnchor.constraint(lessThanOrEqualToConstant: 100000).isActive = true
|
||||||
|
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 1).isActive = true
|
||||||
|
item.view = view
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - WindowUtilsPlugin
|
||||||
|
class WindowUtilsPlugin: NSObject, FlutterPlugin {
|
||||||
|
private static var instance: WindowUtilsPlugin?
|
||||||
|
private var channel: FlutterMethodChannel?
|
||||||
|
private weak var window: NSWindow?
|
||||||
|
private var windowDelegate: WindowDelegate?
|
||||||
|
private var originalButtonConstraints: [NSWindow.ButtonType: [NSLayoutConstraint]] = [:]
|
||||||
|
|
||||||
|
// Centralized traffic light positions - the single source of truth
|
||||||
|
private static let customButtonPositions: [(NSWindow.ButtonType, CGPoint)] = [
|
||||||
|
(.closeButton, CGPoint(x: 20, y: 21)),
|
||||||
|
(.miniaturizeButton, CGPoint(x: 40, y: 21)),
|
||||||
|
(.zoomButton, CGPoint(x: 60, y: 21))
|
||||||
|
]
|
||||||
|
|
||||||
|
static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "com.plezy/window_utils",
|
||||||
|
binaryMessenger: registrar.messenger
|
||||||
|
)
|
||||||
|
let instance = WindowUtilsPlugin()
|
||||||
|
instance.channel = channel
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
|
self.instance = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setWindow(_ window: NSWindow) {
|
||||||
|
instance?.window = window
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply custom traffic light positions. Called by MainFlutterWindow on startup.
|
||||||
|
static func setInitialTrafficLightPositions() {
|
||||||
|
guard let instance = instance, let window = instance.window else { return }
|
||||||
|
instance.applyTrafficLightPositions(custom: true, window: window)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply traffic light positions. Called by WindowDelegate during fullscreen transitions.
|
||||||
|
static func setTrafficLightPositions(custom: Bool, window: NSWindow) {
|
||||||
|
guard let instance = instance else { return }
|
||||||
|
instance.applyTrafficLightPositions(custom: custom, window: window)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyTrafficLightPositions(custom: Bool, window: NSWindow) {
|
||||||
|
if custom {
|
||||||
|
for (buttonType, offset) in WindowUtilsPlugin.customButtonPositions {
|
||||||
|
overrideButtonPosition(window: window, buttonType: buttonType, offset: offset)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (buttonType, _) in WindowUtilsPlugin.customButtonPositions {
|
||||||
|
resetButtonPosition(window: window, buttonType: buttonType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
guard let window = window else {
|
||||||
|
result(FlutterError(code: "NO_WINDOW", message: "Window not available", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "initialize":
|
||||||
|
let args = call.arguments as? [String: Any]
|
||||||
|
let enableWindowDelegate = args?["enableWindowDelegate"] as? Bool ?? false
|
||||||
|
initialize(window: window, enableWindowDelegate: enableWindowDelegate)
|
||||||
|
result(nil)
|
||||||
|
|
||||||
|
case "setTrafficLightsVisible":
|
||||||
|
let args = call.arguments as? [String: Any]
|
||||||
|
let visible = args?["visible"] as? Bool ?? true
|
||||||
|
for buttonType in [NSWindow.ButtonType.closeButton, .miniaturizeButton, .zoomButton] {
|
||||||
|
window.standardWindowButton(buttonType)?.isHidden = !visible
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
|
||||||
|
case "enterFullscreen":
|
||||||
|
if !window.styleMask.contains(.fullScreen) {
|
||||||
|
window.toggleFullScreen(nil)
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
|
||||||
|
case "exitFullscreen":
|
||||||
|
if window.styleMask.contains(.fullScreen) {
|
||||||
|
window.toggleFullScreen(nil)
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
|
||||||
|
case "isFullscreen":
|
||||||
|
result(window.styleMask.contains(.fullScreen))
|
||||||
|
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize(window: NSWindow, enableWindowDelegate: Bool) {
|
||||||
|
self.window = window
|
||||||
|
|
||||||
|
if enableWindowDelegate {
|
||||||
|
let delegate = WindowDelegate()
|
||||||
|
delegate.channel = channel
|
||||||
|
delegate.window = window
|
||||||
|
windowDelegate = delegate
|
||||||
|
window.delegate = delegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func withButton(
|
||||||
|
_ buttonType: NSWindow.ButtonType,
|
||||||
|
in window: NSWindow,
|
||||||
|
action: (NSButton, NSView) -> Void
|
||||||
|
) {
|
||||||
|
guard let button = window.standardWindowButton(buttonType),
|
||||||
|
let superview = button.superview else { return }
|
||||||
|
action(button, superview)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func positionConstraints(for button: NSButton, in superview: NSView) -> [NSLayoutConstraint] {
|
||||||
|
superview.constraints.filter { constraint in
|
||||||
|
((constraint.firstItem as? NSButton) == button || (constraint.secondItem as? NSButton) == button) &&
|
||||||
|
(constraint.firstAttribute == .left || constraint.firstAttribute == .leading ||
|
||||||
|
constraint.firstAttribute == .top || constraint.firstAttribute == .centerY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func overrideButtonPosition(window: NSWindow, buttonType: NSWindow.ButtonType, offset: CGPoint) {
|
||||||
|
withButton(buttonType, in: window) { button, superview in
|
||||||
|
// Store original constraints if not already stored
|
||||||
|
if originalButtonConstraints[buttonType] == nil {
|
||||||
|
let constraints = superview.constraints.filter { constraint in
|
||||||
|
(constraint.firstItem as? NSButton) == button || (constraint.secondItem as? NSButton) == button
|
||||||
|
}
|
||||||
|
originalButtonConstraints[buttonType] = constraints
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing position constraints
|
||||||
|
superview.removeConstraints(positionConstraints(for: button, in: superview))
|
||||||
|
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
// Add new positioning constraints
|
||||||
|
superview.addConstraints([
|
||||||
|
button.leftAnchor.constraint(equalTo: superview.leftAnchor, constant: offset.x),
|
||||||
|
button.topAnchor.constraint(equalTo: superview.topAnchor, constant: offset.y)
|
||||||
|
])
|
||||||
|
superview.layoutSubtreeIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetButtonPosition(window: NSWindow, buttonType: NSWindow.ButtonType) {
|
||||||
|
withButton(buttonType, in: window) { button, superview in
|
||||||
|
// Remove custom constraints
|
||||||
|
superview.removeConstraints(positionConstraints(for: button, in: superview))
|
||||||
|
|
||||||
|
// Restore original constraints if we have them
|
||||||
|
if let originalConstraints = originalButtonConstraints[buttonType] {
|
||||||
|
superview.addConstraints(originalConstraints)
|
||||||
|
originalButtonConstraints.removeValue(forKey: buttonType)
|
||||||
|
}
|
||||||
|
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = true
|
||||||
|
superview.layoutSubtreeIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -768,14 +768,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
macos_window_utils:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: macos_window_utils
|
|
||||||
sha256: d4df3501fd32ac0d2d7590cb6a8e4758337d061c8fa0db816fdd636be63a8438
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.0"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ dependencies:
|
|||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.2
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
macos_window_utils: ^1.9.0
|
|
||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.0
|
||||||
uuid: ^4.4.0
|
uuid: ^4.4.0
|
||||||
window_manager: ^0.4.3
|
window_manager: ^0.4.3
|
||||||
|
|||||||
Reference in New Issue
Block a user