From 3415d04a72b969fa0209812d86d14c58aa042778 Mon Sep 17 00:00:00 2001 From: Jared Wahlstrand Date: Sat, 5 Aug 2023 15:02:54 -0400 Subject: [PATCH] reorganize tests, add some docs on preferences --- Project.toml | 2 +- docs/make.jl | 3 +- docs/src/diff3to4.md | 8 +- docs/src/doc/preferences.md | 21 ++ src/GLib/gio.jl | 1 + src/GLib/loop.jl | 2 + test/gfile.jl | 8 +- test/gui.jl | 692 ------------------------------------ test/gui/canvas.jl | 74 ++++ test/gui/dialogs.jl | 28 ++ test/gui/displays.jl | 84 +++++ test/gui/input.jl | 217 +++++++++++ test/gui/misc.jl | 208 +++++++++++ test/gui/window.jl | 86 +++++ test/mainloop.jl | 4 +- test/runtests.jl | 7 +- 16 files changed, 745 insertions(+), 700 deletions(-) create mode 100644 docs/src/doc/preferences.md create mode 100644 test/gui/canvas.jl create mode 100644 test/gui/displays.jl create mode 100644 test/gui/input.jl create mode 100644 test/gui/misc.jl create mode 100644 test/gui/window.jl diff --git a/Project.toml b/Project.toml index 866db2e9..c9a730ff 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Gtk4" uuid = "9db2cae5-386f-4011-9d63-a5602296539b" -version = "0.4.2" +version = "0.5.0" [deps] BitFlags = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" diff --git a/docs/make.jl b/docs/make.jl index 0cd16eb6..93020847 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -27,7 +27,8 @@ makedocs( ], "Gtk.jl to Gtk4.jl" => "diff3to4.md", "Reference" => ["doc/reference.md", - "doc/GLib_reference.md" + "doc/GLib_reference.md", + "doc/preferences.md" ], #"GI Reference" => "doc/GI_reference.md" ], diff --git a/docs/src/diff3to4.md b/docs/src/diff3to4.md index 90f0f92e..15930718 100644 --- a/docs/src/diff3to4.md +++ b/docs/src/diff3to4.md @@ -38,9 +38,13 @@ Events such as button presses are handled through "event controllers" in GTK 4. Dialogs no longer have a `run` method that takes over the GLib main loop while waiting for the user's response. -## For developers +## GLib event loop -All uses of `mutable` from `GLib.MutableTypes` should be replaced by Julia's `Ref`. +The GLib main loop starts automatically if Julia is in an interactive session. If not, you will have to start it by calling `start_main_loop` or by creating a `GtkApplication` and calling `run` (see the example `application.jl`). + +## MutableTypes and GValue + +All uses of `mutable` from Gtk.jl's `GLib.MutableTypes` should be replaced by Julia's `Ref`. The type of a `GValue` can be set using `settype!` rather than `setindex!`. ## More information diff --git a/docs/src/doc/preferences.md b/docs/src/doc/preferences.md new file mode 100644 index 00000000..7d63ed6e --- /dev/null +++ b/docs/src/doc/preferences.md @@ -0,0 +1,21 @@ +# Preference Settings + +Here is a list of preferences for Gtk4 that can be set using Preferences.jl. + +## EGL directories (Linux & Wayland) + +GTK4 has a few different rendering backends, and by default on Linux it uses one based on OpenGL. Gtk4.jl uses JLL based libraries rather than the ones that come with your Linux distribution, and on Wayland, unfortunately, unless you tell `libglvnd_jll` where to find libEGL, it will be unable to find an OpenGL provider. As a result, on Wayland a Cairo-based fallback backend will be used. This may work fine for you, but it means that `GtkGLArea` will not work. We can tell `libglvnd_jll` where to find libEGL by setting the environment variable `__EGL_VENDOR_LIBRARY_DIRS`. See [here](https://gitlab.freedesktop.org/glvnd/libglvnd/-/blob/master/src/EGL/icd_enumeration.md) for details. + +You can point `libglvnd_jll` to a libEGL location using the preference `"EGL_vendorlib_dirs"`: +```julia +using Gtk4 +Gtk4.set_EGL_vendorlib_dirs("/usr/share/glvnd/egl_vendor.d") +[ Info: Setting will take effect after restarting Julia. +``` +where "/usr/share/glvnd/egl_vendor.d" is a typical location for Mesa's libEGL (this should be modified if it's somewhere else on your distribution). Other vendor-provided libraries may be in other locations, and a colon-separated list of directories can be used for that situation. **Note that this has only been tested for the Mesa-provided libEGL on Fedora and Ubuntu.** + +## UV loop integration + +GTK relies on an event loop (provided by GLib) to process and handle mouse and keyboard events, while Julia relies on its own event loop (provided by libuv) for IO, timers, etc. Interactions between these event loops can cause REPL lag and can interfere with multithreading performance. Explicit integration of the two loops by creating a libuv event source in the GLib main loop is currently [disabled](https://github.com/JuliaGraphics/Gtk.jl/pull/630) because it caused [slowdowns in multithreaded code](https://github.com/JuliaGraphics/Gtk.jl/issues/503). On some Macs, unfortunately, [REPL lag](https://github.com/JuliaGtk/Gtk4.jl/issues/23) occurs without this explicit integration (explicit in the sense that libuv can insert events in the GLib main loop through its own GSource). + +By default, explicit GLib loop integration is only turned on on Macs in an interactive session. You can override this using the preference `"uv_loop_integration"`. If it's set to "enabled", the libuv GSource will be created. If it's set to "disabled", the libuv GSource will not be created, even on Macs in an interactive session. The setting "auto" uses the default behavior. The functions `GLib.set_uv_loop_integration` and `GLib.get_uv_loop_integration` can be used to set and get the preference. diff --git a/src/GLib/gio.jl b/src/GLib/gio.jl index da9949ab..9765cc43 100644 --- a/src/GLib/gio.jl +++ b/src/GLib/gio.jl @@ -8,6 +8,7 @@ Base.islink(f::GFile) = (G_.query_file_type(f,FileQueryInfoFlags_NONE,nothing)== Base.basename(f::GFile) = G_.get_basename(f) path(f::GFile) = G_.get_path(f) +query_info(f::GFile, attributes="*", flags=FileQueryInfoFlags_NONE) = G_.query_info(f,attributes,flags,nothing) Base.keys(fi::GFileInfo) = G_.list_attributes(fi,nothing) function getindex(fi::GFileInfo,att::AbstractString) typ = G_.get_attribute_type(fi,att) diff --git a/src/GLib/loop.jl b/src/GLib/loop.jl index ef7cd03e..3b5a35e6 100644 --- a/src/GLib/loop.jl +++ b/src/GLib/loop.jl @@ -14,6 +14,7 @@ to stop the timeout. Related GTK function: [`g_timeout_add`()]($(gtkdoc_func_url("glib","timeout_add"))) """ function g_timeout_add(cb::Function, interval::Integer, priority=PRIORITY_DEFAULT) + #is_loop_running() || @warn("g_timeout_add needs GLib main loop to be running.") callback = @cfunction(GSourceFunc, Cint, (Ref{Function},)) ref, deref = gc_ref_closure(cb) return ccall((:g_timeout_add_full, libglib),Cint, @@ -35,6 +36,7 @@ See also [`@idle_add`](@ref). Related GTK function: [`g_idle_add_full`()]($(gtkdoc_func_url("glib","idle_add_full"))) """ function g_idle_add(cb::Function, priority=PRIORITY_DEFAULT_IDLE) + #is_loop_running() || @warn("g_idle_add needs GLib main loop to be running.") callback = @cfunction(GSourceFunc, Cint, (Ref{Function},)) ref, deref = gc_ref_closure(cb) return ccall((:g_idle_add_full , libglib),Cint, diff --git a/test/gfile.jl b/test/gfile.jl index b8d7527c..0fb079d1 100644 --- a/test/gfile.jl +++ b/test/gfile.jl @@ -8,6 +8,7 @@ using Test path=pwd() f=GLib.GFile(path) +@test basename(f) == "test" path2=GLib.path(GFile(f)) @test path==path2 @@ -19,8 +20,13 @@ f2=GLib.G_.dup(GFile(f)) @test ispath(GFile(f)) @test isdir(GFile(f)) +@test !isfile(GFile(f)) -fi = GLib.G_.query_info(GFile(f),"*",GLib.FileQueryInfoFlags_NONE,nothing) +fi = GLib.query_info(f) +@test fi["standard::name"] == "test" +for k in keys(fi) + q=fi[k] +end attributes = GLib.G_.list_attributes(fi,nothing) #fileinfo=Ref{GFileInfo}() diff --git a/test/gui.jl b/test/gui.jl index cc1ea865..7efb45c6 100644 --- a/test/gui.jl +++ b/test/gui.jl @@ -1,245 +1,13 @@ using Test, Gtk4, Gtk4.GLib, Gtk4.G_, Gtk4.GdkPixbufLib, Cairo -@testset "Window" begin - -w = GtkWindow("Window", 400, 300) - -# window doesn't acquire a width right away and there doesn't seem to be a way -# of finding this out, so we just wait -while width(w) == 0 - sleep(0.1) -end - -@test width(w) == 400 -@test height(w) == 300 -wdth, hght = screen_size(w) -@test wdth > 0 && hght > 0 - -wdth, hght = screen_size() -@test wdth > 0 && hght > 0 - -visible(w,false) -@test isvisible(w) == false -visible(w,true) -@test isvisible(w) == true - -m = Gtk4.monitor(w) -if m!==nothing - r = Gtk4.G_.get_geometry(m) -end - -#r2 = m.geometry - -hide(w) -show(w) -grab_focus(w) -close(w) -destroy(w) - -end - -@testset "get/set property and binding" begin - w = GtkWindow("Window", 400, 300) - @test w.title == "Window" - @test w.visible - w.visible = false - @test w.visible == false - - # test Enum property set/get - @test w.halign == Gtk4.Align_FILL # default value - w.halign = Gtk4.Align_CENTER - @test w.halign == Gtk4.Align_CENTER - - w2 = GtkWindow("Window 2") - b=bind_property(w,:title,w2,:title,GLib.BindingFlags_SYNC_CREATE) - @test w2.title == w.title - - @test b.source == w - @test b.source_property == "title" - @test b.target == w2 - @test b.target_property == "title" - - # test Flags property get - @test b.flags == GLib.BindingFlags_SYNC_CREATE - - w.title = "Another title" - @test w2.title == w.title - - unbind_property(b) - - w.title = "New title" - - @test w2.title != w.title - - w.title = nothing - @test w.title === nothing - - @test w.cursor === nothing - cursor(w,"crosshair") - @test w.cursor != nothing - cursor(w,nothing) - @test w.cursor === nothing - - c = GdkCursor("crosshair") - w.cursor = c - @test w.cursor == c - w.cursor = nothing - @test w.cursor === nothing - - destroy(w) - destroy(w2) -end - -@testset "change Window size" begin - w = GtkWindow("Window", 400, 300) - fullscreen(w) - sleep(1) - unfullscreen(w) - sleep(1) - maximize(w) - sleep(1) - if !G_.is_maximized(w) - @warn("The Window Manager did not maximize the Gtk Window when requested") - end - unmaximize(w) - sleep(1) - @test !G_.is_maximized(w) - Gtk4.default_size(w, 200, 500) - @test Gtk4.default_size(w) == (200, 500) - destroy(w) -end - -@testset "Window with HeaderBar" begin - w = GtkWindow("Header bar", -1, -1, true, false) # need to add HeaderBar before showing the window - hb = GtkHeaderBar() - Gtk4.titlebar(w,hb) - hb.show_title_buttons=false - push!(hb,GtkLabel("end")) - end2 = GtkLabel("end2") - push!(hb,end2) - delete!(hb,end2) - pushfirst!(hb,GtkLabel("start")) - Gtk4.title_widget(hb,GtkLabel("title widget")) - show(w) - present(w) # no need for this, just covers present() - destroy(w) -end - -@testset "WindowGroup" begin - wg=GtkWindowGroup() - w1 = GtkWindow("window 1") - w2 = GtkWindow("window 2") - push!(wg,w1) - push!(wg,w2) - delete!(wg,w1) - delete!(wg,w2) - destroy(w1) - destroy(w2) -end - -@testset "Initially Hidden Canvas" begin -nb = GtkNotebook() -@test hasparent(nb)==false -vbox = GtkBox(:v) -c = GtkCanvas() -cursor(c,"crosshair") -push!(nb, vbox, "A") -push!(nb, c, "B") -insert!(nb, 2, GtkLabel("Something in the middle"), "A*") -pushfirst!(nb, GtkLabel("Something at the beginning"), "First") -splice!(nb, 3) -w = GtkWindow(nb,"TestDataViewer",600,600) -@test w[] == nb -@test parent(nb) == w -@test pagenumber(nb,c)==3 -destroy(w) -end - @testset "Labelframe" begin f = GtkFrame("Label") w = GtkWindow(f,"Labelframe", 400, 400) destroy(w) end -@testset "Iteration and toplevel" begin -## example of last in first covered -## Create this GUI, then shrink window with the mouse -f = GtkBox(:v) -w = GtkWindow(f,"Last in, first covered", 400, 400) - -g1 = GtkBox(:h) -g2 = GtkBox(:h) -@test_throws ErrorException g3 = GtkBox(:w) -push!(f,g1) -push!(f,g2) - -b11 = GtkButton("first") -push!(g1, b11) -b12 = GtkButton(:mnemonic,"_second") -push!(g1, b12) -b21 = GtkButton(:label,"first") -b22 = GtkButton(:icon_name,"document-open-symbolic") -push!(g2, b22) -pushfirst!(g2, b21) -push!(g2, GtkSeparator(:h)) -@test_throws ErrorException push!(g2, b11) -function n_children(w::GtkWidget) - i=0 - for c in w - i=i+1 - end - i -end - -@test n_children(g2) == 3 -empty!(g2) -@test n_children(g2) == 0 - -strs = ["first", "_second"] -i = 1 -for child in g1 - @test get_gtk_property(child,:label,AbstractString) == strs[i] - @test toplevel(child) == w - i += 1 -end - -destroy(w) -end - ## Widgets -@testset "button, label" begin -w = GtkWindow("Widgets") -f = GtkBox(:v) -Gtk4.child(w,f) -l = GtkLabel("label"); push!(f,l) -b = GtkButton("button"); push!(f,b) - -counter = 0 -id = signal_connect(b, "activate") do widget - counter::Int += 1 -end -function incr(widget, user_data) # incrementing counter in this crashes julia - nothing -end -Gtk4.on_signal_clicked(incr, b) -@test GLib.signal_handler_is_connected(b, id) - -@test counter == 0 -activate(b) -@test counter == 1 -signal_handler_block(b, id) -activate(b) -@test counter == 1 -signal_handler_unblock(b, id) -activate(b) -@test counter == 2 -signal_handler_disconnect(b, id) -activate(b) -@test counter == 2 -destroy(w) -end - @testset "Button with custom icon (& Pixbuf)" begin icon = Matrix{GdkPixbufLib.RGB}(undef, 40, 20) fill!(icon, GdkPixbufLib.RGB(0,0xff,0)) @@ -268,442 +36,10 @@ end destroy(w) end -@testset "Image and Picture" begin -# empty image -img = GtkImage() -p = Gtk4.paintable(img) -@test p === nothing -# named icon -img = Gtk4.G_.Image_new_from_icon_name("document-open") -p = Gtk4.paintable(img) -@test p === nothing -empty!(img) -pic = GtkPicture() -icon = Matrix{GdkPixbufLib.RGB}(undef, 40, 20) -fill!(icon, GdkPixbufLib.RGB(0,0xff,0)) -icon[5:end-5, 3:end-3] .= Ref(GdkPixbufLib.RGB(0,0,0xff)) -pb=GdkPixbuf(100, 100, false) -Gtk4.pixbuf(pic, pb) -pic2 = GtkPicture(pb) - -texture = GdkTexture(pb) -@test width(texture)==100 -@test height(texture)==100 -end - -@testset "checkbox" begin -w = GtkWindow("Checkbutton") -check = GtkCheckButton("check me"); push!(w,check) -destroy(w) -end - -@testset "checkbuttons in group" begin - -w = GtkWindow("Checkbutton group test") -b = GtkBox(:v) -cb1 = GtkCheckButton("option 1") -cb2 = GtkCheckButton() -Gtk4.label(cb2, "option 2") -w[]=b -push!(b,cb1) -push!(b,cb2) - -group(cb1,cb2) - -cb1.active = true -cb2.active = true -@test cb1.active == false - -group(cb1,nothing) - -cb1.active = true -cb2.active = true -@test cb1.active == true - -destroy(w) - -end - -@testset "togglebuttons in group" begin - -w = GtkWindow("Togglebutton group test") -b = GtkBox(:v) -tb1 = GtkToggleButton("option 1") -tb2 = GtkToggleButton() -tb2.label = "option 2" -w[]=b -push!(b,tb1) -push!(b,tb2) - -group(tb1,tb2) - -tb1.active = true -tb2.active = true -@test tb1.active == false - -group(tb1,nothing) - -tb1.active = true -tb2.active = true -@test tb1.active == true - -destroy(w) - -end - -@testset "switch and togglebutton" begin -switch = GtkSwitch(true) -w = GtkWindow(switch,"Switch") - -function do_something(b, user_data) - nothing -end - -toggle = GtkToggleButton("toggle me") -Gtk4.on_signal_toggled(do_something, toggle) -w[] = toggle - -destroy(w) -end - -@testset "FontButton" begin -fb = GtkFontButton() -w = GtkWindow(fb) - -fb2 = GtkFontButton("Sans Italic 12") - -destroy(w) -end - -@testset "LinkButton" begin -b = GtkLinkButton("https://github.com/JuliaGtk/Gtk4.jl", "Gtk4.jl", false) -w = GtkWindow(b, "LinkButton") -b2 = GtkLinkButton("https://github.com/JuliaGraphics/Gtk.jl", true) -w[] = b2 -destroy(w) -end - -@testset "VolumeButton" begin -b = GtkVolumeButton(0.3) -w = GtkWindow(b, "VolumeButton", 50, 50, false) -destroy(w) -end - -@testset "ColorButton" begin -b = GtkColorButton() -b = GtkColorButton(GdkRGBA(0, 0.8, 1.0, 0.3)) -r = GdkRGBA("red") -@test_throws ErrorException q = GdkRGBA("octarine") -w = GtkWindow(b, "ColorButton", 50, 50) -destroy(w) -end - - -@testset "slider/scale" begin -sl = GtkScale(:v, 1:10) -w = GtkWindow(sl, "Scale") -Gtk4.value(sl, 3) -push!(sl,π,:right,"pi") -push!(sl,-3,:left) -@test Gtk4.value(sl) == 3 -adj = GtkAdjustment(sl) -@test get_gtk_property(adj,:value,Float64) == 3 -set_gtk_property!(adj,:upper,11) -empty!(sl) - -adj2 = GtkAdjustment(5.0,0.0,10.0,1.0,5.0,5.0) - -sl2 = GtkScale(:h,adj2) -sl3 = GtkScale(:v) -destroy(w) -end - -@testset "spinbutton" begin -sp = GtkSpinButton(1:10) -w = GtkWindow(sp, "SpinButton") -Gtk4.value(sp, 3) -@test Gtk4.value(sp) == 3 -destroy(w) - -adj = GtkAdjustment(5.0,0.0,10.0,1.0,5.0,5.0) -sp2 = GtkSpinButton(adj, 1.0, 2) -adj2 = GtkAdjustment(sp2) -@test adj == adj2 - -configure!(adj2; value = 2.0, lower = 1.0, upper = 20.0, step_increment = 2.0, page_increment = 10.0, page_size = 10.0) -@test adj2.value == 2.0 -@test adj2.lower == 1.0 -@test adj2.upper == 20.0 -@test adj2.step_increment == 2.0 -@test adj2.page_increment == 10.0 -@test adj2.page_size == 10.0 -configure!(adj2) # should change nothing -@test adj2.value == 2.0 -@test adj2.lower == 1.0 -@test adj2.upper == 20.0 -@test adj2.step_increment == 2.0 -@test adj2.page_increment == 10.0 -@test adj2.page_size == 10.0 - -configure!(sp2; climb_rate = 2.0, digits = 3) -@test sp2.climb_rate == 2.0 -@test sp2.digits == 3 -configure!(sp2) # should change nothing -@test sp2.climb_rate == 2.0 -@test sp2.digits == 3 -end - -@testset "progressbar" begin -pb = GtkProgressBar() -w = GtkWindow(pb, "Progress bar") -set_gtk_property!(pb,:fraction,0.7) -@test get_gtk_property(pb,:fraction,Float64) == 0.7 -@test fraction(pb) == 0.7 -fraction(pb,0.8) -pulse_step(pb,0.1) -@test pulse_step(pb) == 0.1 -pulse(pb) -destroy(w) -end - -@testset "levelbar" begin -lb = GtkLevelBar(0,10) -Gtk4.value(lb,5) -@test Gtk4.value(lb)==5 -end - -@testset "spinner" begin -s = GtkSpinner() -w = GtkWindow(s, "Spinner") -start(s) -@test get_gtk_property(s,:spinning,Bool) == true -stop(s) -@test get_gtk_property(s,:spinning,Bool) == false - -destroy(w) -end - -@testset "Entry" begin -e = GtkEntry() -w = GtkWindow(e, "Entry") -set_gtk_property!(e,:text,"initial") -set_gtk_property!(e,:sensitive,false) - -b = GtkEntryBuffer("different") -buffer(e, b) -@test e.text == "different" - -@test fraction(e) == 0.0 -fraction(e, 1.0) -@test fraction(e) == 1.0 -pulse_step(e, 1.0) -@test pulse_step(e) == 1.0 -pulse(e) - -activated = false -signal_connect(e, :activate) do widget - activated = true -end -signal_emit(e, :activate, Nothing) -@test activated - -destroy(w) -end - -@testset "DropDown" begin - -choices = ["one", "two", "three", "four"] -dd = GtkDropDown(choices) -dd.selected = 1 - -sl = Gtk4.model(dd) -for (i,s) in enumerate(sl) - @test s == choices[i] -end -Gtk4.selected_string!(dd, "two") -@test Gtk4.selected_string(dd) == "two" - -win = GtkWindow("DropDown Example",400,200) -push!(win, dd) -destroy(win) - -dd2 = GtkDropDown() -dd3 = GtkDropDown(1:5) - -end - -@testset "ScaleButton" begin -sb = GtkScaleButton(0.0:1.0:10.0) -adj = GtkAdjustment(sb) - -end - -@testset "Statusbar" begin -vbox = GtkBox(:v) -w = GtkWindow(vbox, "Statusbar") -sb = GtkStatusbar() -push!(vbox, sb) -ctxid = Gtk4.context_id(sb, "Statusbar example") -bpush = GtkButton("push item") -bpop = GtkButton("pop item") -push!(vbox, bpush) -push!(vbox, bpop) -global sb_count = 1 -id = signal_connect(bpush, "activate") do widget - push!(sb, ctxid, string("Item ", sb_count)) - global sb_count += 1 -end -id = signal_connect(bpop, "activate") do widget - pop!(sb, ctxid) -end - -activate(bpush) -activate(bpop) -empty!(sb,ctxid) -destroy(w) -end - -@testset "Infobar" begin -infobar = GtkInfoBar() - -end - -# CSS - -@testset "CssProviderLeaf(filename=\"...\")" begin - style_file = joinpath(dirname(@__FILE__), "style_test.css") - - l = GtkLabel("I am some large blue text!") - - provider = GtkCssProvider(nothing,style_file) - - sc = Gtk4.style_context(l) - push!(sc, provider, 600) - - w = GtkWindow(l) - show(w) - - ### add css tests here - - destroy(w) -end - -@testset "Builder" begin -b=GtkBuilder("test.ui") -widgets = [w for w in b] -@test length(widgets)==length(b) -@test length(b)==6 -button = b["a_button"] -@test isa(button,GtkButton) -@test isa(b[1],GtkWidget) - -@load_builder(b) - -destroy(a_window) - -@test button == b["a_button"] - -s = open("test.ui","r") do f - read(f,String) -end -b3 = GtkBuilder(s,-1) -win = b3["a_window"] -destroy(win) - -end - -@testset "Canvas & AspectFrame" begin -c = GtkCanvas() -f = GtkAspectFrame(0.5, 1, 0.5, false) -f[] = c -@test f[] == c -gm = GtkEventControllerMotion(c) -gs = GtkEventControllerScroll(Gtk4.EventControllerScrollFlags_VERTICAL,c) -gk = GtkEventControllerKey(c) -ggc = GtkGestureClick(c) -ggd = GtkGestureDrag(c) -ggz = GtkGestureZoom(c) -t = Gtk4.find_controller(c,GtkEventControllerMotion) -@test t==gm -@test widget(gm) == c -c.draw = function(_) - if isdefined(c,:back) - ctx = Gtk4.getgc(c) - set_source_rgb(ctx, 1.0, 0.0, 0.0) - paint(ctx) - end -end -gf = GtkEventControllerFocus(c) -w = GtkWindow(f, "Canvas") -draw(c) -sleep(0.5) -reveal(c) -destroyed = Ref(false) -function test_destroy(w) - destroyed[] = true -end -on_signal_destroy(test_destroy, c) -destroy(w) -#sleep(0.5) -#@test destroyed[] == true -end - -@testset "SetCoordinates" begin - cnvs = GtkCanvas(300, 280) - sleep(0.2) - draw(cnvs) do c - set_coordinates(getgc(c), BoundingBox(0, 1, 0, 1)) - end - win = GtkWindow(cnvs) - sleep(1.0) - s = size(cnvs) - @test s[1] == 300 - @test s[2] == 280 - mtrx = Cairo.get_matrix(getgc(cnvs)) - @test mtrx.xx == 300 - @test mtrx.yy == 280 - @test mtrx.xy == mtrx.yx == mtrx.x0 == mtrx.y0 == 0 - surf = Gtk4.cairo_surface(cnvs) - destroy(win) -end - @testset "GLArea" begin glarea = GtkGLArea() end -@testset "Menus" begin -using Gtk4.GLib - -menubar = GMenu() -filemenu = GMenu() -open_ = GMenuItem("Open","open") -push!(filemenu, open_) -new_ = GMenuItem("New") -newsubmenu = GMenu(new_) -blank = GMenuItem("Empty file","empty") -push!(newsubmenu,blank) -template = GMenuItem("From template...","template") -push!(newsubmenu,template) -pushfirst!(filemenu, new_) -quit = GMenuItem("Quit","quit") -push!(filemenu, quit) -GLib.submenu(menubar,"File",filemenu) - -mb = GtkPopoverMenuBar(menubar) -b = GtkBox(:h) -push!(b,mb) -win = GtkWindow(b, "Menus", 200, 40) -@test length(filemenu)==3 -destroy(win) -end - -@testset "File Chooser" begin - dlg = GtkFileChooserDialog("Select file", nothing, Gtk4.FileChooserAction_OPEN, - (("_Cancel", Gtk4.ResponseType_CANCEL), - ("_Open", Gtk4.ResponseType_ACCEPT))) - destroy(dlg) -end - @testset "List view" begin ls=GtkListStore(Int32,Bool) @@ -825,31 +161,3 @@ destroy(win) end -@testset "keyval" begin -@test keyval("H") == Gtk4.KEY_H -end - -@testset "FileFilter" begin -emptyfilter = GtkFileFilter() -@test get_gtk_property(emptyfilter, "name") === nothing -fname = "test.csv" -fdisplay = "test.csv" -fmime = "text/csv" -csvfilter1 = GtkFileFilter("*.csv") -@test csvfilter1.name == "*.csv" - -csvfilter2 = GtkFileFilter("*.csv"; name = "Comma Separated Format") -@test csvfilter2.name == "Comma Separated Format" - -csvfilter3 = GtkFileFilter("", "text/csv") -@test csvfilter3.name == "text/csv" - -csvfilter4 = GtkFileFilter("*.csv", "text/csv") -# Pattern takes precedence over mime-type, causing mime-type to be ignored -@test csvfilter4.name == "*.csv" -end - -@testset "IconTheme" begin - i = GtkIconTheme(GdkDisplay()) - Gtk4.icon_theme_add_search_path(i, ".") -end diff --git a/test/gui/canvas.jl b/test/gui/canvas.jl new file mode 100644 index 00000000..2d63ef9d --- /dev/null +++ b/test/gui/canvas.jl @@ -0,0 +1,74 @@ +using Test, Gtk4, Cairo + +@testset "Canvas & AspectFrame" begin +c = GtkCanvas() +f = GtkAspectFrame(0.5, 1, 0.5, false) +f[] = c +@test f[] == c +gm = GtkEventControllerMotion(c) +gs = GtkEventControllerScroll(Gtk4.EventControllerScrollFlags_VERTICAL,c) +gk = GtkEventControllerKey(c) +ggc = GtkGestureClick(c) +ggd = GtkGestureDrag(c) +ggz = GtkGestureZoom(c) +t = Gtk4.find_controller(c,GtkEventControllerMotion) +@test t==gm +@test widget(gm) == c +c.draw = function(_) + if isdefined(c,:back) + ctx = Gtk4.getgc(c) + set_source_rgb(ctx, 1.0, 0.0, 0.0) + paint(ctx) + end +end +gf = GtkEventControllerFocus(c) +w = GtkWindow(f, "Canvas") +draw(c) +sleep(0.5) +reveal(c) +destroyed = Ref(false) +function test_destroy(w) + destroyed[] = true +end +on_signal_destroy(test_destroy, c) +destroy(w) +#sleep(0.5) +#@test destroyed[] == true +end + +@testset "SetCoordinates" begin + cnvs = GtkCanvas(300, 280) + sleep(0.2) + draw(cnvs) do c + set_coordinates(getgc(c), BoundingBox(0, 1, 0, 1)) + end + win = GtkWindow(cnvs) + sleep(1.0) + s = size(cnvs) + @test s[1] == 300 + @test s[2] == 280 + mtrx = Cairo.get_matrix(getgc(cnvs)) + @test mtrx.xx == 300 + @test mtrx.yy == 280 + @test mtrx.xy == mtrx.yx == mtrx.x0 == mtrx.y0 == 0 + surf = Gtk4.cairo_surface(cnvs) + destroy(win) +end + +@testset "Initially Hidden Canvas" begin +nb = GtkNotebook() +@test hasparent(nb)==false +vbox = GtkBox(:v) +c = GtkCanvas() +cursor(c,"crosshair") +push!(nb, vbox, "A") +push!(nb, c, "B") +insert!(nb, 2, GtkLabel("Something in the middle"), "A*") +pushfirst!(nb, GtkLabel("Something at the beginning"), "First") +splice!(nb, 3) +w = GtkWindow(nb,"TestDataViewer",600,600) +@test w[] == nb +@test parent(nb) == w +@test pagenumber(nb,c)==3 +destroy(w) +end diff --git a/test/gui/dialogs.jl b/test/gui/dialogs.jl index 8fd8dddf..e555cda5 100644 --- a/test/gui/dialogs.jl +++ b/test/gui/dialogs.jl @@ -28,3 +28,31 @@ destroy(dlg) destroy(main_window) end + +@testset "File Chooser" begin + dlg = GtkFileChooserDialog("Select file", nothing, Gtk4.FileChooserAction_OPEN, + (("_Cancel", Gtk4.ResponseType_CANCEL), + ("_Open", Gtk4.ResponseType_ACCEPT))) + destroy(dlg) +end + +@testset "FileFilter" begin +emptyfilter = GtkFileFilter() +@test get_gtk_property(emptyfilter, "name") === nothing +fname = "test.csv" +fdisplay = "test.csv" +fmime = "text/csv" +csvfilter1 = GtkFileFilter("*.csv") +@test csvfilter1.name == "*.csv" + +csvfilter2 = GtkFileFilter("*.csv"; name = "Comma Separated Format") +@test csvfilter2.name == "Comma Separated Format" + +csvfilter3 = GtkFileFilter("", "text/csv") +@test csvfilter3.name == "text/csv" + +csvfilter4 = GtkFileFilter("*.csv", "text/csv") +# Pattern takes precedence over mime-type, causing mime-type to be ignored +@test csvfilter4.name == "*.csv" +end + diff --git a/test/gui/displays.jl b/test/gui/displays.jl new file mode 100644 index 00000000..9b6d8b96 --- /dev/null +++ b/test/gui/displays.jl @@ -0,0 +1,84 @@ +using Test, Gtk4 + +@testset "Image and Picture" begin +# empty image +img = GtkImage() +p = Gtk4.paintable(img) +@test p === nothing +# named icon +img = Gtk4.G_.Image_new_from_icon_name("document-open") +p = Gtk4.paintable(img) +@test p === nothing +empty!(img) +pic = GtkPicture() +icon = Matrix{GdkPixbufLib.RGB}(undef, 40, 20) +fill!(icon, GdkPixbufLib.RGB(0,0xff,0)) +icon[5:end-5, 3:end-3] .= Ref(GdkPixbufLib.RGB(0,0,0xff)) +pb=GdkPixbuf(100, 100, false) +Gtk4.pixbuf(pic, pb) +pic2 = GtkPicture(pb) + +texture = GdkTexture(pb) +@test width(texture)==100 +@test height(texture)==100 +end + +@testset "progressbar" begin +pb = GtkProgressBar() +w = GtkWindow(pb, "Progress bar") +set_gtk_property!(pb,:fraction,0.7) +@test get_gtk_property(pb,:fraction,Float64) == 0.7 +@test fraction(pb) == 0.7 +fraction(pb,0.8) +pulse_step(pb,0.1) +@test pulse_step(pb) == 0.1 +pulse(pb) +destroy(w) +end + +@testset "levelbar" begin +lb = GtkLevelBar(0,10) +Gtk4.value(lb,5) +@test Gtk4.value(lb)==5 +end + +@testset "spinner" begin +s = GtkSpinner() +w = GtkWindow(s, "Spinner") +start(s) +@test get_gtk_property(s,:spinning,Bool) == true +stop(s) +@test get_gtk_property(s,:spinning,Bool) == false + +destroy(w) +end + +@testset "Statusbar" begin +vbox = GtkBox(:v) +w = GtkWindow(vbox, "Statusbar") +sb = GtkStatusbar() +push!(vbox, sb) +ctxid = Gtk4.context_id(sb, "Statusbar example") +bpush = GtkButton("push item") +bpop = GtkButton("pop item") +push!(vbox, bpush) +push!(vbox, bpop) +global sb_count = 1 +id = signal_connect(bpush, "activate") do widget + push!(sb, ctxid, string("Item ", sb_count)) + global sb_count += 1 +end +id = signal_connect(bpop, "activate") do widget + pop!(sb, ctxid) +end + +activate(bpush) +activate(bpop) +empty!(sb,ctxid) +destroy(w) +end + +@testset "Infobar" begin +infobar = GtkInfoBar() + +end diff --git a/test/gui/input.jl b/test/gui/input.jl new file mode 100644 index 00000000..4fc44b89 --- /dev/null +++ b/test/gui/input.jl @@ -0,0 +1,217 @@ +using Test, Gtk4 + +@testset "slider/scale" begin +sl = GtkScale(:v, 1:10) +w = GtkWindow(sl, "Scale") +Gtk4.value(sl, 3) +push!(sl,π,:right,"pi") +push!(sl,-3,:left) +@test Gtk4.value(sl) == 3 +adj = GtkAdjustment(sl) +@test get_gtk_property(adj,:value,Float64) == 3 +set_gtk_property!(adj,:upper,11) +empty!(sl) + +adj2 = GtkAdjustment(5.0,0.0,10.0,1.0,5.0,5.0) + +sl2 = GtkScale(:h,adj2) +sl3 = GtkScale(:v) +destroy(w) +end + +@testset "spinbutton" begin +sp = GtkSpinButton(1:10) +w = GtkWindow(sp, "SpinButton") +Gtk4.value(sp, 3) +@test Gtk4.value(sp) == 3 +destroy(w) + +adj = GtkAdjustment(5.0,0.0,10.0,1.0,5.0,5.0) +sp2 = GtkSpinButton(adj, 1.0, 2) +adj2 = GtkAdjustment(sp2) +@test adj == adj2 + +configure!(adj2; value = 2.0, lower = 1.0, upper = 20.0, step_increment = 2.0, page_increment = 10.0, page_size = 10.0) +@test adj2.value == 2.0 +@test adj2.lower == 1.0 +@test adj2.upper == 20.0 +@test adj2.step_increment == 2.0 +@test adj2.page_increment == 10.0 +@test adj2.page_size == 10.0 +configure!(adj2) # should change nothing +@test adj2.value == 2.0 +@test adj2.lower == 1.0 +@test adj2.upper == 20.0 +@test adj2.step_increment == 2.0 +@test adj2.page_increment == 10.0 +@test adj2.page_size == 10.0 + +configure!(sp2; climb_rate = 2.0, digits = 3) +@test sp2.climb_rate == 2.0 +@test sp2.digits == 3 +configure!(sp2) # should change nothing +@test sp2.climb_rate == 2.0 +@test sp2.digits == 3 +end + +@testset "Entry" begin +e = GtkEntry() +w = GtkWindow(e, "Entry") +set_gtk_property!(e,:text,"initial") +set_gtk_property!(e,:sensitive,false) + +b = GtkEntryBuffer("different") +buffer(e, b) +@test e.text == "different" + +@test fraction(e) == 0.0 +fraction(e, 1.0) +@test fraction(e) == 1.0 +pulse_step(e, 1.0) +@test pulse_step(e) == 1.0 +pulse(e) + +activated = false +signal_connect(e, :activate) do widget + activated = true +end +signal_emit(e, :activate, Nothing) +@test activated + +destroy(w) +end + +@testset "DropDown" begin + +choices = ["one", "two", "three", "four"] +dd = GtkDropDown(choices) +dd.selected = 1 + +sl = Gtk4.model(dd) +for (i,s) in enumerate(sl) + @test s == choices[i] +end +Gtk4.selected_string!(dd, "two") +@test Gtk4.selected_string(dd) == "two" + +win = GtkWindow("DropDown Example",400,200) +push!(win, dd) +destroy(win) + +dd2 = GtkDropDown() +dd3 = GtkDropDown(1:5) + +end + +@testset "ScaleButton" begin +sb = GtkScaleButton(0.0:1.0:10.0) +adj = GtkAdjustment(sb) + +end + +@testset "checkbox" begin +w = GtkWindow("Checkbutton") +check = GtkCheckButton("check me"); push!(w,check) +destroy(w) +end + +@testset "checkbuttons in group" begin + +w = GtkWindow("Checkbutton group test") +b = GtkBox(:v) +cb1 = GtkCheckButton("option 1") +cb2 = GtkCheckButton() +Gtk4.label(cb2, "option 2") +w[]=b +push!(b,cb1) +push!(b,cb2) + +group(cb1,cb2) + +cb1.active = true +cb2.active = true +@test cb1.active == false + +group(cb1,nothing) + +cb1.active = true +cb2.active = true +@test cb1.active == true + +destroy(w) + +end + +@testset "togglebuttons in group" begin + +w = GtkWindow("Togglebutton group test") +b = GtkBox(:v) +tb1 = GtkToggleButton("option 1") +tb2 = GtkToggleButton() +tb2.label = "option 2" +w[]=b +push!(b,tb1) +push!(b,tb2) + +group(tb1,tb2) + +tb1.active = true +tb2.active = true +@test tb1.active == false + +group(tb1,nothing) + +tb1.active = true +tb2.active = true +@test tb1.active == true + +destroy(w) + +end + +@testset "switch and togglebutton" begin +switch = GtkSwitch(true) +w = GtkWindow(switch,"Switch") + +function do_something(b, user_data) + nothing +end + +toggle = GtkToggleButton("toggle me") +Gtk4.on_signal_toggled(do_something, toggle) +w[] = toggle + +destroy(w) +end + +@testset "FontButton" begin +fb = GtkFontButton() +w = GtkWindow(fb) + +fb2 = GtkFontButton("Sans Italic 12") + +destroy(w) +end + +@testset "LinkButton" begin +b = GtkLinkButton("https://github.com/JuliaGtk/Gtk4.jl", "Gtk4.jl", false) +w = GtkWindow(b, "LinkButton") +b2 = GtkLinkButton("https://github.com/JuliaGraphics/Gtk.jl", true) +w[] = b2 +destroy(w) +end + +@testset "VolumeButton" begin +b = GtkVolumeButton(0.3) +w = GtkWindow(b, "VolumeButton", 50, 50, false) +destroy(w) +end + +@testset "ColorButton" begin +b = GtkColorButton() +b = GtkColorButton(GdkRGBA(0, 0.8, 1.0, 0.3)) +r = GdkRGBA("red") +@test_throws ErrorException q = GdkRGBA("octarine") +w = GtkWindow(b, "ColorButton", 50, 50) +destroy(w) +end diff --git a/test/gui/misc.jl b/test/gui/misc.jl new file mode 100644 index 00000000..f1cb0588 --- /dev/null +++ b/test/gui/misc.jl @@ -0,0 +1,208 @@ +using Test, Gtk4, Gtk4.G_ + +@testset "get/set property and binding" begin + w = GtkWindow("Window", 400, 300) + @test w.title == "Window" + @test w.visible + w.visible = false + @test w.visible == false + + # test Enum property set/get + @test w.halign == Gtk4.Align_FILL # default value + w.halign = Gtk4.Align_CENTER + @test w.halign == Gtk4.Align_CENTER + + w2 = GtkWindow("Window 2") + b=bind_property(w,:title,w2,:title,GLib.BindingFlags_SYNC_CREATE) + @test w2.title == w.title + + @test b.source == w + @test b.source_property == "title" + @test b.target == w2 + @test b.target_property == "title" + + # test Flags property get + @test b.flags == GLib.BindingFlags_SYNC_CREATE + + w.title = "Another title" + @test w2.title == w.title + + unbind_property(b) + + w.title = "New title" + + @test w2.title != w.title + + w.title = nothing + @test w.title === nothing + + @test w.cursor === nothing + cursor(w,"crosshair") + @test w.cursor != nothing + cursor(w,nothing) + @test w.cursor === nothing + + c = GdkCursor("crosshair") + w.cursor = c + @test w.cursor == c + w.cursor = nothing + @test w.cursor === nothing + + destroy(w) + destroy(w2) +end + +@testset "Iteration and toplevel" begin +## example of last in first covered +## Create this GUI, then shrink window with the mouse +f = GtkBox(:v) +w = GtkWindow(f,"Last in, first covered", 400, 400) + +g1 = GtkBox(:h) +g2 = GtkBox(:h) +@test_throws ErrorException g3 = GtkBox(:w) +push!(f,g1) +push!(f,g2) + +b11 = GtkButton("first") +push!(g1, b11) +b12 = GtkButton(:mnemonic,"_second") +push!(g1, b12) +b21 = GtkButton(:label,"first") +b22 = GtkButton(:icon_name,"document-open-symbolic") +push!(g2, b22) +pushfirst!(g2, b21) +push!(g2, GtkSeparator(:h)) +@test_throws ErrorException push!(g2, b11) +function n_children(w::GtkWidget) + i=0 + for c in w + i=i+1 + end + i +end + +@test n_children(g2) == 3 +empty!(g2) +@test n_children(g2) == 0 + +strs = ["first", "_second"] +i = 1 +for child in g1 + @test get_gtk_property(child,:label,AbstractString) == strs[i] + @test toplevel(child) == w + i += 1 +end + +destroy(w) +end + +@testset "button, label" begin +w = GtkWindow("Widgets") +f = GtkBox(:v) +Gtk4.child(w,f) +l = GtkLabel("label"); push!(f,l) +b = GtkButton("button"); push!(f,b) + +counter = 0 +id = signal_connect(b, "activate") do widget + counter::Int += 1 +end +function incr(widget, user_data) # incrementing counter in this crashes julia + nothing +end +Gtk4.on_signal_clicked(incr, b) +@test GLib.signal_handler_is_connected(b, id) + +@test counter == 0 +activate(b) +@test counter == 1 +signal_handler_block(b, id) +activate(b) +@test counter == 1 +signal_handler_unblock(b, id) +activate(b) +@test counter == 2 +signal_handler_disconnect(b, id) +activate(b) +@test counter == 2 +destroy(w) +end + +@testset "Builder" begin +b=GtkBuilder("test.ui") +widgets = [w for w in b] +@test length(widgets)==length(b) +@test length(b)==6 +button = b["a_button"] +@test isa(button,GtkButton) +@test isa(b[1],GtkWidget) + +@load_builder(b) + +destroy(a_window) + +@test button == b["a_button"] + +s = open("test.ui","r") do f + read(f,String) +end +b3 = GtkBuilder(s,-1) +win = b3["a_window"] +destroy(win) + +end + +@testset "CssProviderLeaf(filename=\"...\")" begin + style_file = joinpath(dirname(@__FILE__), "style_test.css") + + l = GtkLabel("I am some large blue text!") + + provider = GtkCssProvider(nothing,style_file) + + sc = Gtk4.style_context(l) + push!(sc, provider, 600) + + w = GtkWindow(l) + show(w) + + ### add css tests here + + destroy(w) +end + +@testset "keyval" begin +@test keyval("H") == Gtk4.KEY_H +end + +@testset "IconTheme" begin + i = GtkIconTheme(GdkDisplay()) + Gtk4.icon_theme_add_search_path(i, ".") +end + +@testset "Menus" begin +using Gtk4.GLib + +menubar = GMenu() +filemenu = GMenu() +open_ = GMenuItem("Open","open") +push!(filemenu, open_) +new_ = GMenuItem("New") +newsubmenu = GMenu(new_) +blank = GMenuItem("Empty file","empty") +push!(newsubmenu,blank) +template = GMenuItem("From template...","template") +push!(newsubmenu,template) +pushfirst!(filemenu, new_) +quit = GMenuItem("Quit","quit") +push!(filemenu, quit) +GLib.submenu(menubar,"File",filemenu) + +mb = GtkPopoverMenuBar(menubar) +b = GtkBox(:h) +push!(b,mb) +win = GtkWindow(b, "Menus", 200, 40) +@test length(filemenu)==3 +destroy(win) +end + diff --git a/test/gui/window.jl b/test/gui/window.jl new file mode 100644 index 00000000..f81a8772 --- /dev/null +++ b/test/gui/window.jl @@ -0,0 +1,86 @@ +using Test, Gtk4, Gtk4.G_ + +@testset "Window" begin + +w = GtkWindow("Window", 400, 300) + +# window doesn't acquire a width right away and there doesn't seem to be a way +# of finding this out, so we just wait +while width(w) == 0 + sleep(0.1) +end + +@test width(w) == 400 +@test height(w) == 300 +wdth, hght = screen_size(w) +@test wdth > 0 && hght > 0 + +wdth, hght = screen_size() +@test wdth > 0 && hght > 0 + +visible(w,false) +@test isvisible(w) == false +visible(w,true) +@test isvisible(w) == true + +m = Gtk4.monitor(w) +if m!==nothing + r = Gtk4.G_.get_geometry(m) +end + +#r2 = m.geometry + +hide(w) +show(w) +grab_focus(w) +close(w) +destroy(w) + +end + +@testset "change Window size" begin + w = GtkWindow("Window", 400, 300) + fullscreen(w) + sleep(1) + unfullscreen(w) + sleep(1) + maximize(w) + sleep(1) + if !G_.is_maximized(w) + @warn("The Window Manager did not maximize the Gtk Window when requested") + end + unmaximize(w) + sleep(1) + @test !G_.is_maximized(w) + Gtk4.default_size(w, 200, 500) + @test Gtk4.default_size(w) == (200, 500) + destroy(w) +end + +@testset "Window with HeaderBar" begin + w = GtkWindow("Header bar", -1, -1, true, false) # need to add HeaderBar before showing the window + hb = GtkHeaderBar() + Gtk4.titlebar(w,hb) + hb.show_title_buttons=false + push!(hb,GtkLabel("end")) + end2 = GtkLabel("end2") + push!(hb,end2) + delete!(hb,end2) + pushfirst!(hb,GtkLabel("start")) + Gtk4.title_widget(hb,GtkLabel("title widget")) + show(w) + present(w) # no need for this, just covers present() + destroy(w) +end + +@testset "WindowGroup" begin + wg=GtkWindowGroup() + w1 = GtkWindow("window 1") + w2 = GtkWindow("window 2") + push!(wg,w1) + push!(wg,w2) + delete!(wg,w1) + delete!(wg,w2) + destroy(w1) + destroy(w2) +end diff --git a/test/mainloop.jl b/test/mainloop.jl index e5e0ab06..09985d50 100644 --- a/test/mainloop.jl +++ b/test/mainloop.jl @@ -2,7 +2,7 @@ using Gtk4.GLib, Gtk4, Test @testset "mainloop" begin -GLib.start_main_loop() +GLib.start_main_loop(true) x = Ref{Int}(1) @@ -58,7 +58,7 @@ g_source_remove(id) sleep(0.5) @test x[] == 1 -@test GLib.get_uv_loop_integration() == "auto" +@test GLib.get_uv_loop_integration() in ["auto","enabled","disabled"] # pausing the loop diff --git a/test/runtests.jl b/test/runtests.jl index 289efdb9..17e39930 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -32,10 +32,15 @@ include("gui.jl") include("comboboxtext.jl") include("tree.jl") include("text.jl") +include("gui/misc.jl") +include("gui/canvas.jl") include("gui/dialogs.jl") +include("gui/displays.jl") +include("gui/input.jl") include("gui/layout.jl") +include("gui/window.jl") include("gui/examples.jl") -include("gui/application.jl") +include("gui/application.jl") # needs to be last because it messes with the main loop end GC.gc()