Mousetrap.jl icon indicating copy to clipboard operation
Mousetrap.jl copied to clipboard

Is there a way to influence where new windows are created?

Open MichaelSchloesser opened this issue 1 year ago • 5 comments

When opening a new window, say via an action set to a button the new window opens up at a rather random place. Closing the window and opening it again, it appears in a entirely different place every time. Is there some way to influence this?

A small example:

main() do app::Application
    main_window = Window(app)
    other_window = Window(app)
    set_hide_on_close!(other_window, true)

    button = Button()

    set_child!(main_window, button)

    open_other_window = Action("open_other_window.action", app) do x    
        set_is_modal!(other_window, true)
        present!(other_window)

        return nothing
    end
    set_action!(button, open_other_window)

    connect_signal_close_request!(main_window) do self::Window
        destroy!(other_window)
        return WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE
    end

    present!(main_window)
end

Device specifivations: Device name: DESKTOP-Q9AJ3HU Processor: Intel(R) Core(TM) i5-10600K CPU @ 4.10GHz 4.10 GHz Installed RAM: 16,0 GB System type: 64-bit operating system, x64-based processor Pen and touch: No pen or touch input is available for this display

Windows specifications: Edition: Windows 10 Pro Version: 22H2 Installed on: ‎ 16/‎12/‎2020 OS build: 19045.3930 Experience: Windows Feature Experience Pack 1000.19053.1000.0

MichaelSchloesser avatar Jan 12 '24 12:01 MichaelSchloesser

There's definitely no way to do this cross-platform, as the window manager has complete control over where windows appear. On Linux Wayland or x11 you can get the native pointer to the window and call a Wayland or x11 function on it, but on Windows I'm not aware of a mechanism to set the exact positions.

Mousetrap lacks this feature because of GNOMEs (the developers of GTK4, which Mousetrap is just a wrapper of) design choices, see here for one of the main developers' justification for removing this feature. I personally agree, as being able to set the exact position would violate modern operating systems window layouts, like pinning the window to one side of the screen or next to another window. The window manager should have full control, not the app.

On my Linux fedora and Windows 10 machine, your example spawns the window at the same position every time, so I can't reproduce the random position behavior.

Either way, the correct way to do this is to use the transience property of a window to make sure a window is always spawned on top of another. You have to remember that most GUI apps will have a mostly full-screen window, not a tiny window with just one button, so when spawning a window on the screen, as long as that window isn't tiny and is spawned on top of all other windows, the users will be able to adapt and not get disoriented.

Clemapfel avatar Jan 12 '24 19:01 Clemapfel

Interesting. I agree that the window manager should control the exact window positions. I just hoped to get the smaller window spawn to spawn "inside" the main one or to at least get rid of the randomness (whereever that comes from).

As for the the transience property, running

main() do app::Application
    main_window = Window(app)
    other_window = Window(app)

    set_transient_for!(other_window, main_window)  
end

gives me the error

[ERROR] In Mousetrap.main: type Window has no field _itnernal
Stacktrace:
  [1] getproperty
    @ ./Base.jl:37 [inlined]
  [2] set_transient_for!(self::Window, other::Window)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:1605
  [3] (::var"#11#12")(app::Application)
    @ Main ./REPL[6]:5
  [4] (::TypedFunction)(args::Application)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:91
  [5] (::Mousetrap.var"#14#15"{TypedFunction})(app::Application)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:1542
  [6] (::TypedFunction)(args::Application)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:91
  [7] (::Mousetrap.var"#6#8"{TypedFunction})(x::Tuple{CxxWrap.CxxWrapCore.CxxRef{Mousetrap.detail._Application}})
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:657
  [8] safe_call(scope::String, f::Function, args::Tuple{CxxWrap.CxxWrapCore.CxxRef{Mousetrap.detail._Application}})
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:190
  [9] run!(arg1::Mousetrap.detail._ApplicationAllocated)
    @ Mousetrap.detail ~/.julia/packages/CxxWrap/5IZvn/src/CxxWrap.jl:624
 [10] run!(app::Application)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:1511
 [11] main
    @ ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:1552 [inlined]
 [12] main(f::Function)
    @ Mousetrap ~/.julia/packages/Mousetrap/BoOKO/src/Mousetrap.jl:1538
 [13] top-level scope
    @ REPL[6]:1
 [14] eval
    @ ./boot.jl:370 [inlined]
 [15] eval_user_input(ast::Any, backend::REPL.REPLBackend, mod::Module)
    @ REPL ~/.julia/juliaup/julia-1.9.4+0.x64.linux.gnu/share/julia/stdlib/v1.9/REPL/src/REPL.jl:153
 [16] repl_backend_loop(backend::REPL.REPLBackend, get_module::Function)
    @ REPL ~/.julia/juliaup/julia-1.9.4+0.x64.linux.gnu/share/julia/stdlib/v1.9/REPL/src/REPL.jl:249
 [17] start_repl_backend(backend::REPL.REPLBackend, consumer::Any; get_module::Function)
    @ REPL ~/.julia/juliaup/julia-1.9.4+0.x64.linux.gnu/share/julia/stdlib/v1.9/REPL/src/REPL.jl:234
 [18] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool, backend::Any)
    @ REPL ~/.julia/juliaup/julia-1.9.4+0.x64.linux.gnu/share/julia/stdlib/v1.9/REPL/src/REPL.jl:379
 [19] run_repl(repl::REPL.AbstractREPL, consumer::Any)
    @ REPL ~/.julia/juliaup/julia-1.9.4+0.x64.linux.gnu/share/julia/stdlib/v1.9/REPL/src/REPL.jl:365
 [20] (::Base.var"#1018#1020"{Bool, Bool, Bool})(REPL::Module)
    @ Base ./client.jl:421
 [21] #invokelatest#2
    @ ./essentials.jl:819 [inlined]
 [22] invokelatest
    @ ./essentials.jl:816 [inlined]
 [23] run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
    @ Base ./client.jl:405
 [24] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:322
 [25] _start()
    @ Base ./client.jl:522

MichaelSchloesser avatar Jan 12 '24 20:01 MichaelSchloesser

Wait what, there's a typo, how have I or someone else not found that yet, let me fix that real quick

Clemapfel avatar Jan 12 '24 20:01 Clemapfel

Until #59 is merged you can use this function instead:

function set_transient_for!(self::Window, other::Window)
    detail.set_transient_for!(self._internal, other._internal)
end

It just makes it so self is always on top of other.

Thank you for finding the typo

Clemapfel avatar Jan 12 '24 20:01 Clemapfel

I just gave you a stacktrace, you found the typo ;)

I can confirm that using this function not only works but also got rid of any randomness for me.

MichaelSchloesser avatar Jan 12 '24 21:01 MichaelSchloesser