flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
164.79k stars 27.16k forks source link

[macos] Transparent FlutterViewController #59969

Open jamesblasco opened 4 years ago

jamesblasco commented 4 years ago

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

class MainFlutterWindow: NSWindow {

  override func awakeFromNib() {
    let flutterViewController = FlutterViewController.init()

    flutterViewController.view.layer?.backgroundColor =  CGColor(red: 1, green: 0, blue: 0, alpha: 1.0);
    self.backgroundColor = .red;
    let windowFrame = self.frame
    self.contentViewController = flutterViewController
    self.setFrame(windowFrame, display: true)

    super.awakeFromNib()
  }
}

Probably because FlutterView is set to be Opaque here FlutterView.mm#L47

iapicca commented 4 years ago

Hi @jamesblasco should I interpret this issue as a feature request like expose 'opaque' for macos desktop apps? thank you

jamesblasco commented 4 years ago

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)
);
jamesblasco commented 4 years ago

With the upcoming macOS 11, this feature becomes key to support translucent backgrounds like the new redesign

image

rydmike commented 4 years ago

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.

image

stuartmorgan commented 4 years ago

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.

stuartmorgan commented 4 years ago

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.

jamesblasco commented 4 years ago

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

yuzuquats commented 4 years ago

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)
    }
  }
}
stuartmorgan commented 4 years ago

Or someone could submit a PR to add the functionality to flutter/engine, which would be substantially simpler than the swizzling.

yuzuquats commented 4 years ago

indeed, hoping to get everything completely and working for myself first locally (including blurring) before trying to clone the separate engine repo

aloenobilis commented 4 years ago

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

mewforest commented 3 years ago

It's not clear to me, can I enable transparency like mentioned before on Windows in Flutter win32?

image

jamesblasco commented 3 years ago

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.

fabiancrx commented 3 years ago

So @jamesblasco the support for this feature is on the uwp API's then ?

stuartmorgan commented 3 years ago

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.

xster commented 3 years ago

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

Screen Shot 2020-12-03 at 12 10 51 AM

jamesblasco commented 3 years ago

You are right @xster. Thanks for sharing it πŸ‘

orestesgaolin commented 3 years ago

Would it allow to have a "frosted" translucent background like in case of Apple's apps? image

jamesblasco commented 3 years ago

😏😏😏

image

madhavarshney commented 3 years ago

@jamesblasco Mind sharing the code for the blur? πŸ˜„

jamesblasco commented 3 years ago

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.

xster commented 3 years ago

oh nice work! Good job getting the title bar transparent look too to match.

stuartmorgan commented 3 years ago

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.

EriKWDev commented 3 years ago

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.

flutter doctor -v output ```yml [βœ“] Flutter (Channel dev, 1.26.0-1.0.pre, on macOS 11.1 20C69 darwin-x64, locale en-SE) β€’ Flutter version 1.26.0-1.0.pre at /Users/erik/development/flutter β€’ Framework revision 63062a6443 (4 weeks ago), 2020-12-13 23:19:13 +0800 β€’ Engine revision 4797b06652 β€’ Dart version 2.12.0 (build 2.12.0-141.0.dev) [βœ“] Android toolchain - develop for Android devices (Android SDK version 29.0.0) β€’ Android SDK at /Users/erik/Library/Android/sdk β€’ Platform android-29, build-tools 29.0.0 β€’ Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java β€’ Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) β€’ All Android licenses accepted. [βœ“] Xcode - develop for iOS and macOS (Xcode 12.3) β€’ Xcode at /Applications/Xcode.app/Contents/Developer β€’ Xcode 12.3, Build version 12C33 β€’ CocoaPods version 1.10.0 [βœ“] Android Studio (version 3.4) β€’ Android Studio at /Applications/Android Studio.app/Contents β€’ Flutter plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter β€’ Dart plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart β€’ Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) [βœ“] VS Code (version 1.52.1) β€’ VS Code at /Applications/Visual Studio Code.app/Contents β€’ Flutter extension version 3.18.1 [βœ“] Connected device (1 available) β€’ macOS (desktop) β€’ macos β€’ darwin-x64 β€’ macOS 11.1 20C69 darwin-x64 β€’ No issues found! ```
oryanmoshe commented 3 years ago

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

vibrantFlutter

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()
}
silverhairs commented 3 years ago

Any news about the implementation of this on Linux or should I just buy a mac too?

RossComputerGuy commented 3 years ago

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.

alexmercerind commented 3 years ago

So, I've worked it out for Windows. Use flutter_acrylic in your app.

acrylic

alexmercerind commented 3 years ago

Well I don't know how blur can be done on GTK3.0, but here is what I've got for Linux.

Screenshot from 2021-06-25 16-31-09

GroovinChip commented 3 years ago

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

vibrantFlutter

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()
}

Worth noting that in order to do this you need to target macOS 10.14 or newer

silverhairs commented 3 years ago

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.

edwardaux commented 3 years ago

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:

Anyway, I've been using this for a few weeks now and it seems pretty robust.

MrbeanN513 commented 2 years ago

Screenshot_20210922_001239

linux #flutter

whiskeyPeak commented 2 years ago

Well I don't know how blur can be done on GTK3.0, but here is what I've got for Linux.

Screenshot from 2021-06-25 16-31-09

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

tofutim commented 2 years ago

You guys are so cool.

sebastianhaberey commented 2 years ago

@edwardaux I like your approach of simply inserting an extra view controller & view into the hierarchy, it seems clean and straightforward. Thanks!

stuartmorgan commented 2 years ago

Another reminder about scope since there's been a lot of tangential discussion:

som3on3 commented 1 year ago

No longer works in flutter 3.7+ to fix, just add: flutterViewController.backgroundColor = .clear

PierreBresson commented 1 year ago

@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**
som3on3 commented 1 year ago

@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

som3on3 commented 1 year ago

@PierreBresson also remove the "+" in front of the line

PierreBresson commented 1 year ago

@som3on3 thanks! πŸ€¦β€β™‚οΈπŸ˜…

flutter-triage-bot[bot] commented 3 months ago

The triaged-desktop label is irrelevant if there is no team-desktop label or fyi-desktop label.