Open jamesblasco opened 4 years ago
Hi @jamesblasco
should I interpret this issue as a feature request like expose 'opaque' for macos desktop apps
?
thank you
I don't know if that is the only thing that would need to be done to implement a transparent background
.
I would do something similar to what is already implemented on Android, and add a BackgroundMode param to the FlutterViewController
Android example code:
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.backgroundMode(FlutterActivity.BackgroundMode.transparent)
.build(context)
);
With the upcoming macOS 11, this feature becomes key to support translucent backgrounds like the new redesign
This type of design is used extensively in Windows too (and some Linux GUIs), so it would be very useful for building experience like it with Flutter, if it was supported out of the box for all desktop platforms at least. Not sure if it would potentially be useful on large tablets too, but I imagine that on Chrome OS and Fucshia it could be as well.
This type of design is used extensively in Windows too (and some Linux GUIs), so it would be very useful for building experience like it with Flutter, if it was supported out of the box for all desktop platforms at least.
Please file separate bugs for each platform; it'll be much easier to track which are done and which aren't that way. All of the work here will be completely different on each platform.
Note that transparency alone won't let you achieve the affect in the screenshots above. You'll need to make changes to native code on each platform's runner to have the OS blur what's underneath the window, using the relevant OS APIs.
Yes, we can try to implement the native OS blur ourselves but we are blocked because the FlutterViewController is opaque. That's why I think it should be the priority, then we can try to make a plugin that takes care of the blur or even add it to flutter engine in the future
In case anyone is interested in a hack to get this to work. I haven't gotten window blurring to work (nsvisualeffectview is have some issues for me) but I suspect some private CGSSetWindowBackgroundBlurRadiusFunction APIs might be needed
public extension NSView {
@objc func originalIsOpaque() -> Bool {
return true;
}
@objc func isOpaqueMethodToCall() -> Bool {
if self.className != "FlutterView" {
return originalIsOpaque();
}
let openglView: NSOpenGLView = self as! NSOpenGLView
var opacity: GLint = 0
let opacityPointer: UnsafePointer<GLint> = UnsafePointer(&opacity)
openglView.openGLContext?.setValues(opacityPointer, for: .surfaceOpacity)
return false;
}
}
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override init() {
super.init()
swizzleNSViewOpaqueImplementation();
}
@objc func swizzleNSViewOpaqueImplementation() {
let aClass: AnyClass? = NSClassFromString("FlutterView")
let originalMethod = class_getInstanceMethod(aClass, Selector("isOpaque"))
let placeholderMethod = class_getInstanceMethod(aClass, #selector(NSView.originalIsOpaque))
if let originalMethod = originalMethod, let placeholderMethod = placeholderMethod {
// save the original implementation
method_exchangeImplementations(originalMethod, placeholderMethod)
}
let isOpaqueMethod = class_getInstanceMethod(aClass, Selector("isOpaque"))
let isOpaqueMethodToCall = class_getInstanceMethod(aClass, #selector(NSView.isOpaqueMethodToCall))
if let isOpaqueMethod = isOpaqueMethod, let isOpaqueMethodToCall = isOpaqueMethodToCall {
// switch to the implementation we actually want to call
method_exchangeImplementations(isOpaqueMethod, isOpaqueMethodToCall)
}
}
}
Or someone could submit a PR to add the functionality to flutter/engine, which would be substantially simpler than the swizzling.
indeed, hoping to get everything completely and working for myself first locally (including blurring) before trying to clone the separate engine repo
From stackoverflow : As of swift 3.0 changing the transparency is possible with
yourView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
which works in Xcode 8.2
It's not clear to me, can I enable transparency like mentioned before on Windows in Flutter win32?
No, it is not posible for now. I tried also implementing it natively with c++ but the win32 apis donβt support it.
Only managed to make transparent one full color.
So @jamesblasco the support for this feature is on the uwp API's then ?
This issue is specifically about macOS; please file a new issue to request support for and/or discuss implementation of a the equivalent functionality on Windows.
I might be misunderstanding the issue but I can get transparency through the Flutter macOS app by just setting
window.isOpaque = false
window.backgroundColor = .clear
// aesthetically, probably
window.hasShadow = false
You are right @xster. Thanks for sharing it π
Would it allow to have a "frosted" translucent background like in case of Apple's apps?
πππ
@jamesblasco Mind sharing the code for the blur? π
Sure! Once Flutter macOS supports platforms views (#41722) I will try to implement it as a plugin.
// Transparent view
self.isOpaque = false
self.backgroundColor = .clear
// Add the blur layer
let blurView = NSVisualEffectView()
let view = contentViewController?.view.superview;
blurView.frame = CGRect(x: 0, y: 0, width: 2000, height: 2000)
view?.addSubview(blurView, positioned: NSWindow.OrderingMode.below, relativeTo: nil)
You can add this code inside MainFlutterWindow
. Also, you can play with NSVisualEffectView.materials
to see different effects.
I tried to use layout constraints for the view but the app becomes unresponsive when resizing it.
oh nice work! Good job getting the title bar transparent look too to match.
I might be misunderstanding the issue but I can get transparency through the Flutter macOS app by just setting
window.isOpaque = false window.backgroundColor = .clear // aesthetically, probably window.hasShadow = false
That probably has undefined rendering behavior, because we're currently returning YES
for the view's isOpaque
property.
When I added the code from @jamesblasco I can get a wonderful looking app when I make background colors have opacity, but It seems that the app doesn't work anymore? When I click on the plus button from the sample app it doesn't seem to register. If I remove the code from MainFlutterWindow again, clicking resolves and works again.
Any workaround? ...or am I the only one experiencing this behaviour?
I'm running MacOS Big Sur on an Intel Mac with flutter dev channel.
I faced the same issue as @EriKWDev, when adding the code @jamesblasco posted the window looked great, but the app became unresponsive (for some reason the cursor still changed on button hovers, but onPressed
wasn't executed when clicking)
To solve this I replaced the content view with the new background view, by changing the hierarchy I was able to use the app again.
PS: It also supports window resizing and full screen
Here's the code:
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
/* Hiding the window titlebar */
self.titleVisibility = NSWindow.TitleVisibility.hidden;
self.titlebarAppearsTransparent = true;
self.isMovableByWindowBackground = true;
self.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isEnabled = false;
/* Making the window transparent */
self.isOpaque = false
self.backgroundColor = .clear
/* Adding a NSVisualEffectView to act as a translucent background */
let contentView = contentViewController!.view;
let superView = contentView.superview!;
let blurView = NSVisualEffectView()
blurView.frame = superView.bounds
blurView.autoresizingMask = [.width, .height]
blurView.blendingMode = NSVisualEffectView.BlendingMode.behindWindow
/* Pick the correct material for the task */
blurView.material = NSVisualEffectView.Material.underWindowBackground
/* Replace the contentView and the background view */
superView.replaceSubview(contentView, with: blurView)
blurView.addSubview(contentView)
super.awakeFromNib()
}
Any news about the implementation of this on Linux or should I just buy a mac too?
Any news about the implementation of this on Linux or should I just buy a mac too?
I'd like to see this on Linux as well.
So, I've worked it out for Windows. Use flutter_acrylic in your app.
Well I don't know how blur can be done on GTK3.0, but here is what I've got for Linux.
I faced the same issue as @EriKWDev, when adding the code @jamesblasco posted the window looked great, but the app became unresponsive (for some reason the cursor still changed on button hovers, but
onPressed
wasn't executed when clicking) To solve this I replaced the content view with the new background view, by changing the hierarchy I was able to use the app again. PS: It also supports window resizing and full screenHere's the code:
override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) /* Hiding the window titlebar */ self.titleVisibility = NSWindow.TitleVisibility.hidden; self.titlebarAppearsTransparent = true; self.isMovableByWindowBackground = true; self.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isEnabled = false; /* Making the window transparent */ self.isOpaque = false self.backgroundColor = .clear /* Adding a NSVisualEffectView to act as a translucent background */ let contentView = contentViewController!.view; let superView = contentView.superview!; let blurView = NSVisualEffectView() blurView.frame = superView.bounds blurView.autoresizingMask = [.width, .height] blurView.blendingMode = NSVisualEffectView.BlendingMode.behindWindow /* Pick the correct material for the task */ blurView.material = NSVisualEffectView.Material.underWindowBackground /* Replace the contentView and the background view */ superView.replaceSubview(contentView, with: blurView) blurView.addSubview(contentView) super.awakeFromNib() }
Worth noting that in order to do this you need to target macOS 10.14 or newer
I tried this package called bitsdojo_window; it has a bunch of widgets to customize your window and you can easily accomplish this on any Desktop platform.
The posts above were super helpful in getting me headed in the right direction. However, we really shouldn't be mucking around with NSWindow.contentView
... AppKit doesn't like when you do that. Running the code above will yield errors that look like:
2021-09-10 16:48:24.206458+1000 xxxx[70071:1327206] NSWindow warning: adding an unknown subview: <NSVisualEffectView: 0x7facee4501c0>. Break on NSLog to debug.
2021-09-10 16:48:24.207487+1000 xxxx[70071:1327206] Call stack:
0 AppKit 0x00007fff22d397b5 -[NSThemeFrame addSubview:] + 112
1 AppKit 0x00007fff22d7dc4c -[NSView replaceSubview:with:] + 154
2 xxxx 0x000000010ca48aac $s14xxxx17MainFlutterWindowC12awakeFromNibyyF + 2988
3 xxxx 0x000000010ca48ceb $s14xxxx17MainFlutterWindowC12awakeFromNibyyFTo + 43
4 CoreFoundation 0x00007fff204ff5c6 -[NSSet makeObjectsPerformSelector:] + 231
In addition to this warning, the approach above does something weird to the resize handle cursors and it means it is really painful to grab the corners to resize.
We really should be using the standard AppKit behaviour of adding a child view controller (which does the blurring) and then add the FlutterViewController
to that VC. For example, here's what I'm currently using:
class BlurryContainerViewController: NSViewController {
let flutterViewController = FlutterViewController()
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError()
}
override func loadView() {
let blurView = NSVisualEffectView()
blurView.autoresizingMask = [.width, .height]
blurView.blendingMode = .behindWindow
blurView.state = .active
if #available(macOS 10.14, *) {
blurView.material = .sidebar
}
self.view = blurView
}
override func viewDidLoad() {
super.viewDidLoad()
self.addChild(flutterViewController)
flutterViewController.view.frame = self.view.bounds
flutterViewController.view.autoresizingMask = [.width, .height]
self.view.addSubview(flutterViewController.view)
}
}
class MainFlutterWindow: NSWindow, NSWindowDelegate {
override func awakeFromNib() {
delegate = self
let blurryContainerViewController = BlurryContainerViewController()
let windowFrame = self.frame
self.contentViewController = blurryContainerViewController
self.setFrame(windowFrame, display: true)
self.minSize = NSSize(width: 600, height: 400)
// Doing this gives us a toolbar (that we're going to make transparent) but it gives
// a bit more real estate to grab and drag with.
if #available(macOS 10.13, *) {
let customToolbar = NSToolbar()
customToolbar.showsBaselineSeparator = false
self.toolbar = customToolbar
}
// Don't show the title bar
self.titleVisibility = .hidden
// Weirdly, you'd think we wouldn't need this because we're hiding the titlebar
// on the previous line, but if we don't, then the toolbar is shown opaquely.
self.titlebarAppearsTransparent = true
// Allows the user to grab by the title/tool bar and move around
self.isMovableByWindowBackground = true
// Combines both titlebar and toolbar
if #available(macOS 11.0, *) {
self.toolbarStyle = .unified
}
// Without this, the content view on the right sits underneath the toolbar
self.styleMask.insert(.fullSizeContentView)
// Making the window itself transparent
self.isOpaque = false
self.backgroundColor = .clear
RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController)
super.awakeFromNib()
}
func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions {
// Hides the toolbar when in fullscreen mode
return [.autoHideToolbar, .autoHideMenuBar, .fullScreen]
}
}
A couple of notes:
BlurryContainerViewController
that contains the FlutterViewController
and then we set that blurry container as the contentViewController
for the window.contentView
BlurryContainerViewController
, you'll notice that I'm doing quite a bit of customisation of the toolbar and titlebar. All of this is to achieve my desired look and feel... it is unlikely that you will want the same configuration so you'll probably need to tweak a bunch of those settings.Anyway, I've been using this for a few weeks now and it seems pretty robust.
linux #flutter
Well I don't know how blur can be done on GTK3.0, but here is what I've got for Linux.
Hey!
Would you mind explaining how you achieved that result? Everything i tried with GTK3 and Cairo in the my_application.cc file hasn't worked yet
You guys are so cool.
@edwardaux I like your approach of simply inserting an extra view controller & view into the hierarchy, it seems clean and straightforward. Thanks!
Another reminder about scope since there's been a lot of tangential discussion:
No longer works in flutter 3.7+ to fix, just add: flutterViewController.backgroundColor = .clear
@som3on3 Not working with flutter 3.7.3, I get the following error :
FlutterWindow.swift:32:1: error: binary operator '+' cannot be applied to operands of type 'NSRect' (aka 'CGRect') and 'NSColor?'
+ flutterViewController.backgroundColor = .clear // **Required post-Flutter 3.7.0**
FlutterWindow.swift:32:43: error: cannot assign value of type '()' to type 'NSRect' (aka 'CGRect')
+ flutterViewController.backgroundColor = .clear // **Required post-Flutter 3.7.0**
@PierreBresson edit MainFlutterWindow.swift
let flutterViewController = FlutterViewController.init()
flutterViewController.backgroundColor = .clear
let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController)
works just fine in 3.7.3
@PierreBresson also remove the "+" in front of the line
@som3on3 thanks! π€¦ββοΈπ
The triaged-desktop
label is irrelevant if there is no team-desktop
label or fyi-desktop
label.
I am trying to make a transparent app in macOS but the Flutter view has a black background I can't remove.
I set the window and the view within the view controller to have a red background but it still shows black
Probably because FlutterView is set to be Opaque here FlutterView.mm#L47