fyne-io / fyne

Cross platform GUI toolkit in Go inspired by Material Design
https://fyne.io/
Other
24.99k stars 1.39k forks source link

Add option to make window floating / always on top #3429

Closed linkangren closed 1 year ago

linkangren commented 1 year ago

Checklist

Is your feature request related to a problem?

I want to keep the fyne window on top all the time

Is it possible to construct a solution with the existing API?

No response

Describe the solution you'd like to see.

similar:self.setWindowFlags(Qt.WindowStaysOnTopHint)

Jacalz commented 1 year ago

I think this is very hard to do well cross-platform because all window managers do not support this. I personally also think this should be up to the window manager to control (like it usually is on Linux). Whenever apps decide for themselves that their windows always should be on top, I usually find that it just gets annoying because it is not what I, as in the user, want. If Windows would handle it like on Gnome, or KDE Plasma, that would be best: image

phillvancejr commented 1 year ago

Is this a feature the developers don't want to add because they don't like it or is it about the difficulty of implementation? If the former this issue should probably be closed with that answer, but if it is the latter, isn't this just an issue of passing the options to GLFW? GLFW has a hint for this: glfw.WindowHint(glfw.Floating,1) this sets the window to float above all others. I make internal tools for my fellow designers/coworkers and its useful to have tools always on top so I can continue working in the design software while the app is active. I've personally cloned a copy of fyne and added this in just because I find it useful.

I'm not sure of all the places Window needs to be implemented but in my own clone I added this code to fyne/internal/driver/glfw/window.go:

func (w *window) Topmost(makeTopmost bool) {
    w.runOnMainWhenCreated(func (){
        top := 0
        if makeTopmost {
            top = 1
        }
        w.viewport.SetAttrib(glfw.Floating,top)
    })
}

In the mobile driver its an empty method and does nothing. This change allows this:

a := app.New()
w := a.NewWindow("Always on Top")
w.Topmost(true) // set the topmost attribute
w.ShowAndRun()
Jacalz commented 1 year ago

Nothing has been decided but it also probably isn't that hard to implement for desktop with the GLFW support there but we have been prioritizing more important work. However, I am a bit torn about this. I feel like it is something that the user should decide and not the developer. Most desktop environments on Linux support this but I am aware that Windows does not.

I would be very annoyed if a window decides to be top-most. If a window floats above all others should be controllable by me, the user. At the same time, I can kind of see it being useful for things like pop-out video players (i.e. the one in Firefox) but that is also only real valid use case that I can think of (no offense, just having trouble picturing applications in my workflow that behave like this). What is your use case?

Another downside is, that it doesn't work on mobile so I would be a bit hesitant to adding it to the fyne.Window interface if it was implemented. We are a cross platform gui framework after all and making an overly desktop centered API does not seem great.

phillvancejr commented 1 year ago

My co workers and I work in Adobe Illustrator and part of our process is to select a bunch of shapes and calculate their total square footage. I've created a tool, currently in Pyqt that allows this. The window floats always on top of the Adobe Illustrator window so that the user can select any shapes they need and also keep the window above Illustrator, that is to say the tool is never dismissed, which is useful because multiple selections sometimes need to be made and clicking back and forth between inactive and active windows would be annoying in this instance. It is a very good setting for tools.

Another application is for media player apps. I used to have a youtube like app that had allowed you to toggle always on top functionality, which was nice because I could always have the window minimized in the corner without the many sub palettes and windows of my design software covering it. The feature was later removed from the app to my disappointment.

Topmost windows are something that for example C# Avalonia also supports. Avalonia is a cross platform Gui framework targeting desktop, mobile, embedded systems & wasm. I presume on the non desktop platforms the code just does nothing, which is exactly the expected result. Java has supported this as long as I've been aware of in both Swing and JavaFX. JavaFX works on desktop and also on mobile, though I'm not sure what the functionality does on mobile, but I assume it just does nothing, which is expected. Qt also supports topmost and is cross platform across desktop, mobile, browser via wasm & smart tvs. Again on the platforms where it doesn't make sense I assume Qt just does nothing when topmost is requested.

I think this is a case where adding in Desktop specific functionality does not take away from the other platforms as again I wouldn't expect a topmost function to do anything anywhere other than desktop. In my opinion not working on mobile is not a downside because the idea of a "Window" doesn't even exist on mobile in the same sense it does on desktop. I haven't used Fyne on mobile so I'm speaking out of ignorance here on mobile what do Resize, SetFixedSize, SetTitle and CenterOnScreen do? I wouldn't expect these functions to have any meaning in a mobile app personally.

I'd be happy to do this myself in any given program if I was given access to the underlying glfw window, but Fyne does not expose the glfw window handle. This could be a valid alternative, just put some function in which exposes the native window handle (*glfw.Window on desktop) and then the developer can modify it as they see fit without having to add many new apis to Fyne. This is actually something that GLFW supports at least in C, in C you can get the native window handle back for example an NSWindow* on mac and then the developer can do any special handling on that. Flutter alllows similar functionality by allowing you to write native code for each platform

I can appreciate your point about the user deciding whether or not something is topmost, but that choice, as far as I can tell, is OS specific. You seem to be on Gnome where you can chose but for example I work on Mac and I cannot make arbitrary windows topmost, I can only make a window topmost if the developer programmed the application with that ability. I'm not 100% certain but I'm pretty sure that the same is true for Windows, I don't think (again could be wrong) that you can just make any random application topmost unless the developer built the software to do that. At the end of the day if you build an application that is always on top and your users don't like that, they can just not use your app.

For what its worth this is the 4th issue opened requesting or inquiring about this feature. I'm not the original author of this issue or any of the others so it does seem like multiple developers are interested in Fyne supporting this functionality.

Jacalz commented 1 year ago

Thanks for the input. Your ideas are very useful.

For what its worth this is the 4th issue opened requesting or inquiring about this feature.

Oh, right. Yeah this should be closed as a duplicate of https://github.com/fyne-io/fyne/issues/1129.

I haven't used Fyne on mobile so I'm speaking out of ignorance here on mobile what do Resize, SetFixedSize, SetTitle and CenterOnScreen do?

They are kind of there from a legacy perspective. They would probably have been placed in the driver/desktop package if they were introduced today but I’m not sure. I believe there is an option open for deprecating Resize and moving it into the driver/desktop package. Something similar could probably be done with this if we agree on that being the way forward.

Jacalz commented 1 year ago

Closing as duplicate of https://github.com/fyne-io/fyne/issues/1129.

Jacalz commented 1 year ago

You mention for issues but I can find only two. Are there more open duplicates that should be closed?

phillvancejr commented 1 year ago

Sorry, I should have been more clear about that. There were only two open but I came across two other closed issues when I was searching to see if Fyne supported it. Ah, I figured that those functions weren't useful on mobile, but that does mean that Fyne already has a precedent of supporting desktop specific functionality that has no equivalent on mobile, which is ok, that is what I personally would expect and desire. I wouldn't for example expect a desktop api to support Geolocation (though I suppose it could) and Flutter supports this on mobile but not on desktop (at least I don't think it does). Thanks for your time! Don't know if you'll end up supporting it but in any case Fyne is still great, thanks!

claytron5000 commented 4 months ago

@phillvancejr I've been trying to get similar code to yours to work in the user space by importing glfw directly and then typing the method func Topmost(w *glfw.Window) {. The compiler is yelling at me, I'm assuming the window type from fyne is different than glfw's window. Any suggestions on implementing this functionality?

Jacalz commented 4 months ago

The GLFW window type is indeed different from our Fyne window; it is embedded as a field somewhere inside the struct. Unless you manage to do some field value lookup using reflect, I think it will be quite hard to force the window to be topmost.

andydotxyz commented 4 months ago

Until the PR exposing native window handles lands :)

phillvancejr commented 4 months ago

@claytron5000, my memory on this is a bit fuzzy as I ended up using Wails for my UI stuff since it provided this out of the box, but it was not difficult to get topmost working on Windows & Mac, I had it working last year in august for mac when I was using this. You can see the little demo I did here toward the bottom of the comment: https://github.com/fyne-io/fyne/issues/1129#issuecomment-1699961010

andydotxyz's native window handle pr will probably make it more straightforward but if you want to try out my solution yourself it wasn't difficult but just slightly involved due to the structure of the project. I don't know where the code is I used to demo this but I think the simplest way I did it was something like:

That is I think how I set it up last time, but its been quite a while since I used this so its possible the code base as changed sufficiently that this no longer works. In any case once the native handle pr comes through it should be more straight forward to set this up, a few years ago in the minifb window library's rust port I added the same functionality using the native window handles exposed via that api, so once that is in it will be easier.