From e59ee8d1369cb5ba8de267479474b1f958c2e376 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 31 Dec 2020 12:50:11 -0600 Subject: [PATCH] Require Observables 0.4 & improve inference/specialization --- Project.toml | 2 +- examples/widgets.jl | 4 +- src/GtkObservables.jl | 20 ++- src/extrawidgets.jl | 94 ++++++------ src/graphics_interaction.jl | 37 +++-- src/precompile.jl | 279 ++++++++++++++++++++++-------------- src/rubberband.jl | 10 +- src/widgets.jl | 121 ++++++++-------- test/runtests.jl | 38 +++-- 9 files changed, 352 insertions(+), 253 deletions(-) diff --git a/Project.toml b/Project.toml index eb8f349..b5fc057 100644 --- a/Project.toml +++ b/Project.toml @@ -22,7 +22,7 @@ FixedPointNumbers = "0.8" Graphics = "1" Gtk = "1" IntervalSets = "0.5" -Observables = "0.3" +Observables = "0.4" Reexport = "0.2, 1" RoundingIntegers = "0.2, 1" julia = "1.3" diff --git a/examples/widgets.jl b/examples/widgets.jl index 46e7e59..a1693c2 100644 --- a/examples/widgets.jl +++ b/examples/widgets.jl @@ -34,10 +34,10 @@ push!(mainwin, vbox) cnvs = canvas() auxwin = Window(cnvs) showwin = map(cb) do val - set_gtk_property!(auxwin, :visible, val) + set_gtk_property!(auxwin, "visible", val) end # Also make sure it gets destroyed when we destroy the main window -signal_connect(mainwin, :destroy) do w +signal_connect(mainwin, "destroy") do w destroy(auxwin) end # Draw something in the auxillary window diff --git a/src/GtkObservables.jl b/src/GtkObservables.jl index 26755fe..e372574 100644 --- a/src/GtkObservables.jl +++ b/src/GtkObservables.jl @@ -1,7 +1,7 @@ module GtkObservables -if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel")) - @eval Base.Experimental.@optlevel 1 +if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@compiler_options")) + @eval Base.Experimental.@compiler_options optimize=1 end using LinearAlgebra # for `inv` @@ -65,7 +65,19 @@ Base.getindex(w::Widget) = getindex(observable(w)) Base.setindex!(w::Widget, val) = setindex!(observable(w), val) Observables.on(f, w::Widget; kwargs...) = on(f, observable(w); kwargs...) Observables.onany(f, w::Widget, ws::Union{Widget,Observable}...; kwargs...) = onany(f, observable(w)::Observable, map(observable, ws)...; kwargs...) -Base.map(f, w::Widget, ws::Union{Widget,Observable}...; kwargs...) = map(f, observable(w)::Observable, map(observable, ws)...; kwargs...) +Base.map(f::F, w::Widget, ws::Union{Widget,Observable}...; kwargs...) where F = map(f, observable(w)::Observable, map(observable, ws)...; kwargs...) + +function Base.precompile(w::Widget) + tf = true + if hasfield(typeof(w), :preserved) + for f in w.preserved + if isa(f, Observables.ObserverFunction) + tf &= precompile(f) + end + end + end + return tf +end # Define specific widgets include("widgets.jl") @@ -142,7 +154,7 @@ Preserve `obj` until `widget` has been [`destroy`](@ref)ed. """ function gc_preserve(widget::Union{GtkWidget,GtkCanvas}, obj) _ref_dict[obj] = obj - signal_connect(widget, :destroy) do w + signal_connect(widget, "destroy") do w delete!(_ref_dict, obj) end end diff --git a/src/extrawidgets.jl b/src/extrawidgets.jl index 5e7ebc9..1d38cba 100644 --- a/src/extrawidgets.jl +++ b/src/extrawidgets.jl @@ -14,7 +14,7 @@ frame(f::GtkFrame) = f struct Player{P} <: Widget observable::Observable{Int} widget::P - preserved::Vector + preserved::Vector{Any} function Player{P}(observable::Observable{Int}, widget, preserved) where P obj = new{P}(observable, widget, preserved) @@ -43,34 +43,34 @@ end frame(p::PlayerWithTextbox) = p.frame -function PlayerWithTextbox(builder, index::Observable, range::AbstractUnitRange, id::Integer=1) +function PlayerWithTextbox(builder, index::Observable{Int}, range::UnitRange{Int}, id::Int=1) 1 <= id <= 2 || error("only 2 player widgets are defined in player.glade") direction = Observable(Int8(0)) - frame = Gtk.G_.object(builder,"player_frame$id") - scale = slider(range; widget=Gtk.G_.object(builder,"index_scale$id"), observable=index) - entry = textbox(first(range); widget=Gtk.G_.object(builder,"index_entry$id"), observable=index, range=range) - play_back = button(; widget=Gtk.G_.object(builder,"play_back$id")) - step_back = button(; widget=Gtk.G_.object(builder,"step_back$id")) - stop = button(; widget=Gtk.G_.object(builder,"stop$id")) - step_forward = button(; widget=Gtk.G_.object(builder,"step_forward$id")) - play_forward = button(; widget=Gtk.G_.object(builder,"play_forward$id")) + frame = Gtk.G_.object(builder,"player_frame$id")::Gtk.GtkFrameLeaf + scale = slider(range; widget=Gtk.G_.object(builder,"index_scale$id")::Gtk.GtkScaleLeaf, observable=index) + entry = textbox(first(range); widget=Gtk.G_.object(builder,"index_entry$id")::Gtk.GtkEntryLeaf, observable=index, range=range) + play_back = button(; widget=Gtk.G_.object(builder,"play_back$id")::Gtk.GtkButtonLeaf) + step_back = button(; widget=Gtk.G_.object(builder,"step_back$id")::Gtk.GtkButtonLeaf) + stop = button(; widget=Gtk.G_.object(builder,"stop$id")::Gtk.GtkButtonLeaf) + step_forward = button(; widget=Gtk.G_.object(builder,"step_forward$id")::Gtk.GtkButtonLeaf) + play_forward = button(; widget=Gtk.G_.object(builder,"play_forward$id")::Gtk.GtkButtonLeaf) # Fix up widget properties set_gtk_property!(scale.widget, "round-digits", 0) # glade/gtkbuilder bug that I have to set this here? # Link the buttons clampindex(i) = clamp(i, minimum(range), maximum(range)) - preserved = Any[on(play_back) do _ direction[] = -1 end, - on(step_back) do _ + preserved = Any[on(play_back; weak=true) do _ direction[] = -1 end, + on(step_back; weak=true) do _ direction[] = 0 index[] = clampindex(index[] - 1) end, - on(stop) do _ direction[] = 0 end, - on(step_forward) do _ + on(stop; weak=true) do _ direction[] = 0 end, + on(step_forward; weak=true) do _ direction[] = 0 index[] = clampindex(index[] + 1) end, - on(play_forward) do _ direction[] = +1 end] + on(play_forward; weak=true) do _ direction[] = +1 end] function advance() i = index[] + direction[] if !(i ∈ range) @@ -81,7 +81,7 @@ function PlayerWithTextbox(builder, index::Observable, range::AbstractUnitRange, nothing end # Stop playing if the widget is destroyed - signal_connect(frame, :destroy) do widget + signal_connect(frame, "destroy") do widget setindex!(direction, 0) end # Start the timer @@ -90,16 +90,18 @@ function PlayerWithTextbox(builder, index::Observable, range::AbstractUnitRange, advance() end end) + # Configure the cleanup + ondestroy(frame, preserved) # Create the player object PlayerWithTextbox(range, direction, frame, scale, entry, play_back, step_back, stop, step_forward, play_forward), preserved end -function PlayerWithTextbox(index::Observable, range::AbstractUnitRange, id::Integer=1) +function PlayerWithTextbox(index::Observable{Int}, range::AbstractUnitRange{<:Integer}, id::Integer=1) builder = GtkBuilder(filename=joinpath(splitdir(@__FILE__)[1], "player.glade")) - PlayerWithTextbox(builder, index, range, id) + PlayerWithTextbox(builder, index, convert(UnitRange{Int}, range), convert(Int, id)) end -player(range::AbstractRange{Int}; style="with-textbox", id::Int=1) = - player(Observable(first(range)), range; style=style, id=id) +player(range::AbstractRange{Int}; style="with-textbox", id::Integer=1) = + player(Observable(first(range)), convert(UnitRange{Int}, range)::UnitRange{Int}; style=style, id=id) """ player(range; style="with-textbox", id=1) @@ -113,7 +115,7 @@ slice by keyboard. You can create up to two player widgets for the same GUI, as long as you pass `id=1` and `id=2`, respectively. """ -function player(cs::Observable, range::AbstractUnitRange; style="with-textbox", id::Int=1) +function player(cs::Observable, range::AbstractUnitRange{<:Integer}; style="with-textbox", id::Integer=1) if style == "with-textbox" widget, preserved = PlayerWithTextbox(cs, range, id) return Player(cs, widget, preserved) @@ -124,6 +126,7 @@ end Base.unsafe_convert(::Type{Ptr{Gtk.GLib.GObject}}, p::PlayerWithTextbox) = Base.unsafe_convert(Ptr{Gtk.GLib.GObject}, frame(p)) +Gtk.destroy(p::PlayerWithTextbox) = destroy(frame(p)) ################# A time widget ########################## @@ -157,10 +160,10 @@ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) (Dates.Hour(x), x) # last crop, we have the hours now, and the time is kept as well end t2 = map(last, H) # here is the final time - connect!(observable, t2) # we connect the input and output times so that any update to the resulting time will go into the input observable and actually show on the widgets + connect_nofire!(observable, t2) # we connect the input and output times so that any update to the resulting time will go into the input observable and actually show on the widgets Sint = Observable(Dates.value(first(S[]))) # necessary for now, until range-like GtkObservables.widgets can accept other ranges. Ssb = spinbutton(-1:60, widget=b["second"], observable=Sint) # allow for values outside the actual range of seconds so that we'll be able to increase and decrease minutes. - on(Sint) do x + on(Sint; weak=true) do x Δ = Dates.Second(x) - first(S[]) # how much did we change by, this should always be ±1 new_t = observable[] + Δ # new time new_t = new_t < zerotime ? zerotime : new_t # julia Time is allowed negative values, here we correct for that @@ -169,11 +172,11 @@ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) end Sint2 = map(src -> Dates.value(Dates.Second(src)), t2) # Any change in the value of the seconds, namely 60 -> 0, needs to loop back into the beginning of this last chain of events. Sint3 = async_latest(Sint2) # important, otherwise we get an endless update loop - connect!(Sint, Sint3) # final step of connecting the two + connect_nofire!(Sint, Sint3) # final step of connecting the two # everything is the same for minutes: Mint = Observable(Dates.value(first(M[]))) Msb = spinbutton(-1:60, widget=b["minute"], observable=Mint) - on(Mint) do x + on(Mint; weak=true) do x Δ = Dates.Minute(x) - first(M[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -182,11 +185,11 @@ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) end Mint2 = map(src -> Dates.value(Dates.Minute(src)), t2) Mint3 = async_latest(Mint2) - connect!(Mint, Mint3) + connect_nofire!(Mint, Mint3) # while I think this next part is not entirely necessary for Hours, my brain hurts and I want this to be over. It works. Hint = Observable(Dates.value(first(H[]))) Hsb = spinbutton(0:23, widget=b["hour"], observable=Hint) - on(Hint) do x + on(Hint; weak=true) do x Δ = Dates.Hour(x) - first(H[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -195,7 +198,7 @@ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) end Hint2 = map(src -> Dates.value(Dates.Hour(src)), t2) Hint3 = async_latest(Hint2) - connect!(Hint, Hint3) + connect_nofire!(Hint, Hint3) if widget === nothing return TimeWidget(observable, b["frame"]) @@ -244,10 +247,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) (Dates.Year(x), x) end t2 = map(last, y) - connect!(observable, t2) + connect_nofire!(observable, t2) Sint = Observable(Dates.value(first(S[]))) Ssb = spinbutton(-1:60, widget=b["second"], observable=Sint) - on(Sint) do x + on(Sint; weak=true) do x Δ = Dates.Second(x) - first(S[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -256,10 +259,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end Sint2 = map(src -> Dates.value(Dates.Second(src)), t2) Sint3 = async_latest(Sint2) - connect!(Sint, Sint3) + connect_nofire!(Sint, Sint3) Mint = Observable(Dates.value(first(M[]))) Msb = spinbutton(-1:60, widget=b["minute"], observable=Mint) - on(Mint) do x + on(Mint; weak=true) do x Δ = Dates.Minute(x) - first(M[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -268,10 +271,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end Mint2 = map(src -> Dates.value(Dates.Minute(src)), t2) Mint3 = async_latest(Mint2) - connect!(Mint, Mint3) + connect_nofire!(Mint, Mint3) Hint = Observable(Dates.value(first(H[]))) Hsb = spinbutton(-1:24, widget=b["hour"], observable=Hint) - on(Hint) do x + on(Hint; weak=true) do x Δ = Dates.Hour(x) - first(H[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -280,10 +283,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end Hint2 = map(src -> Dates.value(Dates.Hour(src)), t2) Hint3 = async_latest(Hint2) - connect!(Hint, Hint3) + connect_nofire!(Hint, Hint3) dint = Observable(Dates.value(first(d[]))) dsb = spinbutton(-1:32, widget=b["day"], observable=dint) - on(dint) do x + on(dint; weak=true) do x Δ = Dates.Day(x) - first(d[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -292,10 +295,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end dint2 = map(src -> Dates.value(Dates.Day(src)), t2) dint3 = async_latest(dint2) - connect!(dint, dint3) + connect_nofire!(dint, dint3) mint = Observable(Dates.value(first(m[]))) msb = spinbutton(-1:13, widget=b["month"], observable=mint) - on(mint) do x + on(mint; weak=true) do x Δ = Dates.Month(x) - first(m[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -304,10 +307,10 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end mint2 = map(src -> Dates.value(Dates.Month(src)), t2) mint3 = async_latest(mint2) - connect!(mint, mint3) + connect_nofire!(mint, mint3) yint = Observable(Dates.value(first(y[]))) ysb = spinbutton(-1:10000, widget=b["year"], observable=yint) - on(yint) do x + on(yint; weak=true) do x Δ = Dates.Year(x) - first(y[]) new_t = observable[] + Δ new_t = new_t < zerotime ? zerotime : new_t @@ -316,12 +319,19 @@ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) end yint2 = map(src -> Dates.value(Dates.Year(src)), t2) yint3 = async_latest(yint2) - connect!(yint, yint3) + connect_nofire!(yint, yint3) - if widget == nothing + if widget === nothing return TimeWidget(observable, b["frame"]) else push!(widget, b["frame"]) return TimeWidget(observable, widget) end end + +function connect_nofire!(dest::Observable, src::Observables.AbstractObservable) + on(src; weak=true) do val + dest.val = val + end + return nothing +end diff --git a/src/graphics_interaction.jl b/src/graphics_interaction.jl index 2544ee0..c8b92b7 100644 --- a/src/graphics_interaction.jl +++ b/src/graphics_interaction.jl @@ -231,7 +231,7 @@ struct Canvas{U} mouse::MouseHandler{U} preserved::Vector{Any} - function Canvas{U}(w::Integer=-1, h::Integer=-1; own::Bool=true) where U + function Canvas{U}(w::Int=-1, h::Int=-1; own::Bool=true) where U gtkcanvas = GtkCanvas(w, h) # Delete the Gtk handlers for id in gtkcanvas.mouse.ids @@ -240,13 +240,14 @@ struct Canvas{U} empty!(gtkcanvas.mouse.ids) # Initialize our own handlers mouse = MouseHandler{U}(gtkcanvas) - set_gtk_property!(gtkcanvas, :is_focus, true) + set_gtk_property!(gtkcanvas, "is_focus", true) preserved = [] canvas = new{U}(gtkcanvas, mouse, preserved) gc_preserve(gtkcanvas, canvas) canvas end end +Canvas{U}(w::Integer, h::Integer=-1; own::Bool=true) where U = Canvas{U}(Int(w)::Int, Int(h)::Int; own=own) """ canvas(U=DeviceUnit, w=-1, h=-1) - c::GtkObservables.Canvas @@ -284,7 +285,7 @@ using `do`-block notation: This would paint an image-Observable `imgobs` onto the canvas and then draw a red circle centered on `xsig`, `ysig`. """ -function Gtk.draw(drawfun::Function, c::Canvas, signals::Observable...) +function Gtk.draw(drawfun::F, c::Canvas, signals::Observable...) where F @guarded draw(c.widget) do widget # This used to have a `yield` in it to allow the Gtk event queue to run, # but that caused @@ -299,6 +300,16 @@ function Gtk.draw(drawfun::Function, c::Canvas, signals::Observable...) push!(c.preserved, drawfunc) drawfunc end +function Gtk.draw(drawfun::F, c::Canvas, signal::Observable) where F + @guarded draw(c.widget) do widget + drawfun(widget, signal[]) + end + drawfunc = on(signal) do _ + draw(c.widget) + end + push!(c.preserved, drawfunc) + drawfunc +end # Painting an image to a canvas function Base.copy!(ctx::GraphicsContext, img::AbstractArray{C}) where C<:Union{Colorant,Number} @@ -484,12 +495,12 @@ You can flip the direction of either pan operation with `xpanflip` and """ function init_pan_scroll(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - filter_x::Function = evt->(evt.modifiers & 0x0f) == SHIFT || evt.direction == LEFT || evt.direction == RIGHT, - filter_y::Function = evt->(evt.modifiers & 0x0f) == 0 && (evt.direction == UP || evt.direction == DOWN), + @nospecialize(filter_x::Function) = evt->(evt.modifiers & 0x0f) == SHIFT || evt.direction == LEFT || evt.direction == RIGHT, + @nospecialize(filter_y::Function) = evt->(evt.modifiers & 0x0f) == 0 && (evt.direction == UP || evt.direction == DOWN), xpanflip = false, ypanflip = false) where {U,T} enabled = Observable(true) - pan = on(canvas.mouse.scroll) do event::MouseScroll{U} + pan = on(canvas.mouse.scroll; weak=true) do event::MouseScroll{U} if enabled[] s = 0.1*scrollpm(event.direction) if filter_x(event) @@ -523,11 +534,11 @@ click-drag panning has been met (by default, clicking mouse button """ function init_pan_drag(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - initiate::Function = pandrag_init_default) where {U,T} + @nospecialize(initiate::Function) = pandrag_init_default) where {U,T} enabled = Observable(true) active = Observable(false) pos1ref, zr1ref, mtrxref = Ref{XY{DeviceUnit}}(), Ref{XY{ClosedInterval{T}}}(), Ref{Matrix{Float64}}() # julia#15276 - init = on(canvas.mouse.buttonpress) do btn::MouseButton{U} + init = on(canvas.mouse.buttonpress; weak=true) do btn::MouseButton{U} if initiate(btn) active[] = true # Because the user coordinates will change during panning, @@ -539,12 +550,12 @@ function init_pan_drag(canvas::Canvas{U}, end return nothing end - drag = on(canvas.mouse.motion) do btn::MouseButton{U} + drag = on(canvas.mouse.motion; weak=true) do btn::MouseButton{U} if active[] btn.button == 0 && return nothing pos1, zr1, mtrx = pos1ref[], zr1ref[], mtrxref[] xd, yd = convertunits(DeviceUnit, canvas, btn.position.x, btn.position.y) - dx, dy, _ = mtrx*[xd-pos1.x, yd-pos1.y, 1] + dx, dy, _ = mtrx*[xd-pos1.x, yd-pos1.y, 1.0] fv = zr[].fullview cv = XY(interior(minimum(zr1.x)-dx..maximum(zr1.x)-dx, fv.x), interior(minimum(zr1.y)-dy..maximum(zr1.y)-dy, fv.y)) @@ -553,7 +564,7 @@ function init_pan_drag(canvas::Canvas{U}, end end end - finish = on(canvas.mouse.buttonrelease) do btn::MouseButton{U} + finish = on(canvas.mouse.buttonrelease; weak=true) do btn::MouseButton{U} btn.button == 0 && return nothing active[] = false return nothing @@ -593,13 +604,13 @@ zooming with `flip`. """ function init_zoom_scroll(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - filter::Function = evt->(evt.modifiers & 0x0f) == CONTROL, + @nospecialize(filter::Function) = evt->(evt.modifiers & 0x0f) == CONTROL, focus::Symbol = :pointer, factor = 2.0, flip = false) where {U,T} focus == :pointer || focus == :center || error("focus must be :pointer or :center") enabled = Observable(true) - zm = on(canvas.mouse.scroll) do event::MouseScroll{U} + zm = on(canvas.mouse.scroll; weak=true) do event::MouseScroll{U} if enabled[] && filter(event) # println("zoom scroll: ", event) s = factor diff --git a/src/precompile.jl b/src/precompile.jl index 7c124fc..ac09b31 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -1,120 +1,183 @@ function _precompile_() ccall(:jl_generating_output, Cint, ()) == 1 || return nothing - @assert precompile(Tuple{Core.kwftype(typeof(timewidget)),NamedTuple{(:observable,), Tuple{Observable{Time}}},typeof(timewidget),Time}) # time: 0.115743145 - @assert precompile(Tuple{typeof(init_zoom_rubberband),Canvas{UserUnit},Observable{ZoomRegion{RInt64}}}) # time: 0.110777095 - @assert precompile(Tuple{typeof(init_zoom_rubberband),Canvas{UserUnit},Observable{ZoomRegion{RInt64}},typeof(GtkObservables.zrb_init_default), typeof(GtkObservables.zrb_reset_default), Int}) # time: 0.110777095 - @assert precompile(Tuple{Core.kwftype(typeof(datetimewidget)),NamedTuple{(:observable,), Tuple{Observable{DateTime}}},typeof(datetimewidget),DateTime}) # time: 0.110061295 - @assert precompile(Tuple{typeof(label),String}) # time: 0.101116486 - @assert precompile(Tuple{typeof(copy!),Canvas{UserUnit},Matrix{RGB{N0f8}}}) # time: 0.09909209 - @assert precompile(Tuple{typeof(player),Observable{Int64},UnitRange{Int64}}) # time: 0.082910724 - @assert precompile(Tuple{typeof(canvas),Int64,Int64}) # time: 0.08071636 - @assert precompile(Tuple{typeof(show),IOBuffer,Label}) # time: 0.07879969 - @assert precompile(Tuple{typeof(textarea),String}) # time: 0.054318994 - @assert precompile(Tuple{typeof(image_surface),Matrix{RGBA{Float64}}}) # time: 0.046262104 - @assert precompile(Tuple{typeof(textbox),String}) # time: 0.043787602 + + # slider + sl = slider(1:3) + sl[] = (1:5, 3) + precompile(sl) + destroy(sl) + + # checkbox + cb = checkbox(true) + cb[] = false + precompile(cb) + destroy(cb) + + # togglebutton + tb = togglebutton(true) + tb[] = false + precompile(tb) + destroy(tb) + + # button + btn = button("Push me") + btn[] = nothing + precompile(btn) + destroy(btn) + + # textbox + tb1, tb2 = textbox("Edit me"), textbox(3; range=1:5) + tb1[] = "done" + tb2[] = 4 + precompile(tb1) + precompile(tb2) + destroy(tb1); destroy(tb2) + + # textarea + ta = textarea("Lorem ipsum...") + ta[] = "...muspi meroL" + precompile(ta) + destroy(ta) + + # dropdown + dd = dropdown(["one", "two", "three"]) + precompile(dd) + destroy(dd) @assert precompile(Tuple{typeof(dropdown),Tuple{Vararg{String, 100}}}) # time: 0.042523906 - @assert precompile(Tuple{Core.kwftype(typeof(togglebutton)),NamedTuple{(:label,), Tuple{String}},typeof(togglebutton)}) # time: 0.040888157 - @assert precompile(Tuple{Core.kwftype(typeof(checkbox)),NamedTuple{(:label,), Tuple{String}},typeof(checkbox)}) # time: 0.034800116 - @assert precompile(Tuple{typeof(progressbar),ClosedInterval{Int64}}) # time: 0.033394996 - @assert precompile(Tuple{typeof(slider),UnitRange{Int64}}) # time: 0.03210807 - @assert precompile(Tuple{typeof(spinbutton),UnitRange{Int64}}) # time: 0.0317442 - @assert precompile(Tuple{typeof(image_surface),Matrix{Float64}}) # time: 0.025033748 - @assert precompile(Tuple{Core.kwftype(typeof(dropdown)),NamedTuple{(:label,), Tuple{String}},typeof(dropdown),Vector{Pair{String, Function}}}) # time: 0.02340839 - @assert precompile(Tuple{Core.kwftype(typeof(textbox)),NamedTuple{(:range,), Tuple{UnitRange{Int64}}},typeof(textbox),Int64}) # time: 0.022958083 - @assert precompile(Tuple{Type{GtkAspectFrame},Canvas{DeviceUnit},String,Float64,Float64,Float64}) # time: 0.021899309 - @assert precompile(Tuple{typeof(canvas),Type{UserUnit}}) # time: 0.020233927 - @assert precompile(Tuple{typeof(canvas),Type{UserUnit},Int64,Int64}) # time: 0.020233927 - @assert precompile(Tuple{typeof(mousescroll_cb),Ptr{GObject},Ptr{Gtk.GdkEventScroll},MouseHandler{UserUnit}}) # time: 0.018706173 - @assert precompile(Tuple{typeof(setindex!),Slider{Int64},Tuple{UnitRange{Int64}, Int64}}) # time: 0.018451277 - @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt64},Float64}) # time: 0.018138554 - @assert precompile(Tuple{typeof(map),Function,Textbox{String},Textbox{Int64}}) # time: 0.016691625 - @assert precompile(Tuple{typeof(mousemove_cb),Ptr{GObject},Ptr{Gtk.GdkEventMotion},MouseHandler{UserUnit}}) # time: 0.016197048 - @assert precompile(Tuple{typeof(cyclicspinbutton),UnitRange{Int64},Observable{Bool}}) # time: 0.015953336 - @assert precompile(Tuple{typeof(button),String}) # time: 0.015578984 - @assert precompile(Tuple{typeof(axes),ZoomRegion{RInt64}}) # time: 0.012924676 - @assert precompile(Tuple{typeof(image_surface),Matrix{Gray{N0f8}}}) # time: 0.011829626 @assert precompile(Tuple{typeof(dropdown),Vector{Pair{String, Function}}}) # time: 0.01169991 - @assert precompile(Tuple{typeof(+),XY{Float64},XY{Float64}}) # time: 0.011136752 - @assert precompile(Tuple{Core.kwftype(typeof(textbox)),NamedTuple{(:observable,), Tuple{Observable{Int64}}},typeof(textbox),Type{Int64}}) # time: 0.010784853 - @assert precompile(Tuple{typeof(init_zoom_scroll),Canvas{UserUnit},Observable{ZoomRegion{RInt64}}}) # time: 0.01061211 - @assert precompile(Tuple{typeof(map),Function,Label}) # time: 0.009765576 - @assert precompile(Tuple{Type{ZoomRegion},Tuple{UnitRange{Int64}, UnitRange{Int64}}}) # time: 0.009671802 - @assert precompile(Tuple{Core.kwftype(typeof(textbox)),NamedTuple{(:widget, :observable, :range), Tuple{GtkEntryLeaf, Observable{Int64}, UnitRange{Int64}}},typeof(textbox),Int64}) # time: 0.008382424 - @assert precompile(Tuple{typeof(draw),Function,Canvas{UserUnit},Observable{Int64},Vararg{Observable{Int64}, 100}}) # time: 0.008291145 - @assert precompile(Tuple{typeof(draw),Function,Canvas{UserUnit},Observable{Vector{Any}},Vararg{Observable{Vector{Any}}, 100}}) # time: 0.007357308 - @assert precompile(Tuple{Core.kwftype(typeof(button)),NamedTuple{(:widget,), Tuple{GtkToolButtonLeaf}},typeof(button)}) # time: 0.006894188 - @assert precompile(Tuple{Type{ZoomRegion},Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}) # time: 0.006405025 - @assert precompile(Tuple{typeof(map),Function,Button}) # time: 0.006336905 - @assert precompile(Tuple{typeof(draw),Function,Canvas{UserUnit},Observable{Any}}) # time: 0.006168176 - @assert precompile(Tuple{typeof(canvas),Type{UserUnit}}) # time: 0.005956597 - @assert precompile(Tuple{typeof(map),Function,Checkbox}) # time: 0.005810558 - @assert precompile(Tuple{Core.kwftype(typeof(slider)),NamedTuple{(:observable, :orientation), Tuple{Observable{Int64}, Char}},typeof(slider),UnitRange{Int64}}) # time: 0.005702973 - @assert precompile(Tuple{Core.kwftype(typeof(spinbutton)),NamedTuple{(:value,), Tuple{Int64}},typeof(spinbutton),UnitRange{Int64}}) # time: 0.005454661 - @assert precompile(Tuple{Core.kwftype(typeof(slider)),NamedTuple{(:widget, :observable), Tuple{GtkScaleLeaf, Observable{Int64}}},typeof(slider),UnitRange{Int64}}) # time: 0.004878002 - @assert precompile(Tuple{typeof(init_pan_drag),Canvas{UserUnit},Observable{ZoomRegion{RInt64}}}) # time: 0.004753301 - @assert precompile(Tuple{typeof(mousedown_cb),Ptr{GObject},Ptr{Gtk.GdkEventButton},MouseHandler{UserUnit}}) # time: 0.00433181 - @assert precompile(Tuple{Core.kwftype(typeof(spinbutton)),NamedTuple{(:widget, :observable), Tuple{GtkSpinButtonLeaf, Observable{Int64}}},typeof(spinbutton),UnitRange{Int64}}) # time: 0.004098546 - @assert precompile(Tuple{Type{GtkWindow},Textarea}) # time: 0.00398958 - @assert precompile(Tuple{typeof(set_coordinates),Canvas{UserUnit},BoundingBox}) # time: 0.003914427 - @assert precompile(Tuple{typeof(mousedown_cb),Ptr{GObject},Ptr{Gtk.GdkEventButton},MouseHandler{DeviceUnit}}) # time: 0.003356859 - @assert precompile(Tuple{typeof(mousescroll_cb),Ptr{GObject},Ptr{Gtk.GdkEventScroll},MouseHandler{DeviceUnit}}) # time: 0.00330406 - @assert precompile(Tuple{typeof(setindex!),Observable{ZoomRegion{RInt64}},Tuple{UnitRange{Int64}, UnitRange{Int64}}}) # time: 0.003122286 - @assert precompile(Tuple{typeof(draw),Function,Canvas{UserUnit}}) # time: 0.00281273 - @assert precompile(Tuple{typeof(fill!),Canvas{UserUnit},RGB{N0f8}}) # time: 0.002759036 - @assert precompile(Tuple{typeof(init_pan_scroll),Canvas{UserUnit},Observable{ZoomRegion{RInt64}}}) # time: 0.00264137 - @assert precompile(Tuple{typeof(convertunits),Type{DeviceUnit},Canvas{UserUnit},UserUnit,UserUnit}) # time: 0.002624784 + + # label + lbl = label("Info") + lbl[] = "other info" + precompile(lbl) + destroy(lbl) + @assert precompile(Tuple{typeof(show),IOBuffer,Label}) # time: 0.07879969 + + # spinbutton + sb = spinbutton(1:3) + sb[] = (1:4, 4) + precompile(sb) + destroy(sb) + + # cyclicspinbutton + csb = cyclicspinbutton(1:3, Observable(true)) + csb[] = 4 + precompile(csb) + destroy(csb) + + # progressbar + pb = progressbar(1:3) + pb[] = 2 + precompile(pb) + destroy(pb) + pb = progressbar(1.0 .. 3.0) + pb[] = 2.2 + precompile(pb) + destroy(pb) + + # player + p = player(1:3) + precompile(p) + p[] = 2 + destroy(p) + + # # timewidget + # tw = timewidget(Dates.Time(1,1,1)) + # tw[] = Dates.Time(2,2,2) + # precompile(tw) + # destroy(tw) + + # # datetimewidget + # dtw = datetimewidget(DateTime(1,1,1,1,1,1)) + # dtw[] = DateTime(2,2,2,2,2,2) + # destroy(dtw) + + # canvas + @assert precompile(Tuple{typeof(canvas),Int,Int}) # time: 0.08071636 @assert precompile(Tuple{typeof(canvas)}) # time: 0.00260139 - @assert precompile(Tuple{Type{Label},Observable{String},GtkLabelLeaf,Vector{Any}}) # time: 0.002581081 - @assert precompile(Tuple{typeof(set_coordinates),Canvas{UserUnit},ZoomRegion{RInt64}}) # time: 0.002466032 - @assert precompile(Tuple{Type{Dropdown},Observable{String},Observable{Any},GtkComboBoxTextLeaf,UInt64,Vector{Any}}) # time: 0.002410124 - @assert precompile(Tuple{typeof(gc_preserve),GtkFrameLeaf,Player{PlayerWithTextbox}}) # time: 0.002317133 - @assert precompile(Tuple{typeof(ondestroy),GtkProgressBarLeaf,Vector{Any}}) # time: 0.002292251 - @assert precompile(Tuple{typeof(player),UnitRange{Int64}}) # time: 0.002277744 - @assert precompile(Tuple{typeof(ondestroy),GtkLabelLeaf,Vector{Any}}) # time: 0.002236544 - @assert precompile(Tuple{typeof(ondestroy),GtkComboBoxTextLeaf,Vector{Any}}) # time: 0.002222859 - @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt64},Float64,XY{DeviceUnit}}) # time: 0.002209134 - @assert precompile(Tuple{Core.kwftype(typeof(button)),NamedTuple{(:widget,), Tuple{GtkButtonLeaf}},typeof(button)}) # time: 0.002120643 - @assert precompile(Tuple{typeof(setindex!),Textbox{Int64},Int64}) # time: 0.001964045 - @assert precompile(Tuple{Type{MouseButton{UserUnit}}}) # time: 0.001963396 - @assert precompile(Tuple{typeof(destroy),SpinButton{Int64}}) # time: 0.001867615 - @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt64},Int64}) # time: 0.001822723 - @assert precompile(Tuple{typeof(setindex!),SpinButton{Int64},Tuple{UnitRange{Int64}, Int64}}) # time: 0.001812057 - @assert precompile(Tuple{typeof(destroy),Slider{Int64}}) # time: 0.001750338 - @assert precompile(Tuple{typeof(draw),Function,Canvas{DeviceUnit}}) # time: 0.001727843 - @assert precompile(Tuple{typeof(setindex!),Checkbox,Bool}) # time: 0.001716981 - @assert precompile(Tuple{typeof(setindex!),TimeWidget{DateTime},DateTime}) # time: 0.001705442 - @assert precompile(Tuple{typeof(setindex!),Label,String}) # time: 0.001647036 - @assert precompile(Tuple{typeof(setindex!),TimeWidget{Time},Time}) # time: 0.001623848 - @assert precompile(Tuple{typeof(show),IOBuffer,XY{Float64}}) # time: 0.001530783 - @assert precompile(Tuple{typeof(show),IOBuffer,XY{Int64}}) # time: 0.001503061 - @assert precompile(Tuple{Type{BoundingBox},XY{ClosedInterval{Int64}}}) # time: 0.001497522 - @assert precompile(Tuple{typeof(get_gtk_property),Label,Symbol,Type{String}}) # time: 0.001479955 - @assert precompile(Tuple{typeof(show),IOBuffer,UserUnit}) # time: 0.001462472 - @assert precompile(Tuple{Core.kwftype(typeof(spinbutton)),NamedTuple{(:orientation,), Tuple{String}},typeof(spinbutton),UnitRange{Int64}}) # time: 0.001407626 - @assert precompile(Tuple{Type{MouseButton{DeviceUnit}}}) # time: 0.001365265 - @assert precompile(Tuple{typeof(pan_x),ZoomRegion{RInt64},Float64}) # time: 0.001364175 - @assert precompile(Tuple{typeof(rubberband_move),Canvas{UserUnit},RubberBand{UserUnit},MouseButton{UserUnit},Cairo.CairoContext}) # time: 0.001334047 - @assert precompile(Tuple{Type{ZoomRegion},Matrix{RGB{N0f8}}}) # time: 0.001308999 - @assert precompile(Tuple{typeof(setindex!),Observable{ZoomRegion{RInt64}},XY{ClosedInterval{Int64}}}) # time: 0.001308547 - @assert precompile(Tuple{Core.kwftype(typeof(checkbox)),NamedTuple{(:label,), Tuple{String}},typeof(checkbox),Bool}) # time: 0.001228299 - @assert precompile(Tuple{Type{GtkFrame},Canvas{DeviceUnit}}) # time: 0.001184776 - @assert precompile(Tuple{Type{ZoomRegion},Tuple{UnitRange{Int64}, UnitRange{Int64}},Tuple{UnitRange{Int64}, UnitRange{Int64}}}) # time: 0.001152972 + for U in (UserUnit, DeviceUnit) + @assert precompile(Tuple{typeof(canvas),Type{U}}) # time: 0.020233927 + @assert precompile(Tuple{typeof(canvas),Type{U},Int,Int}) # time: 0.020233927 + @assert precompile(Tuple{Type{GtkAspectFrame},Canvas{U},String,Float64,Float64,Float64}) # time: 0.021899309 + @assert precompile(Tuple{typeof(gc_preserve),GtkWindowLeaf,Canvas{U}}) + + # @assert precompile(Tuple{typeof(draw),Function,Canvas{U}}) # time: 0.00281273 + # @assert precompile(Tuple{typeof(draw),Function,Canvas{U},Observable{Any}}) # time: 0.006168176 + # @assert precompile(Tuple{typeof(draw),Function,Canvas{U},Observable{Any},Vararg{Observable{Any}, 100}}) # time: 0.007357308 + + @assert precompile(Tuple{typeof(fill!),Canvas{U},RGB{N0f8}}) # time: 0.002759036 + @assert precompile(Tuple{typeof(fill!),Canvas{U},RGBA{N0f8}}) + + @assert precompile(Tuple{Type{MouseButton{U}}}) # time: 0.001963396 + @assert precompile(Tuple{Type{MouseHandler{U}},GtkCanvas}) # time: 0.017885247 + @assert precompile(Tuple{typeof(mousedown_cb),Ptr{GObject},Ptr{Gtk.GdkEventButton},MouseHandler{U}}) + @assert precompile(Tuple{typeof(mouseup_cb),Ptr{GObject},Ptr{Gtk.GdkEventButton},MouseHandler{U}}) + @assert precompile(Tuple{typeof(mousescroll_cb),Ptr{GObject},Ptr{Gtk.GdkEventScroll},MouseHandler{U}}) # time: 0.018706173 + @assert precompile(Tuple{typeof(mousemove_cb),Ptr{GObject},Ptr{Gtk.GdkEventMotion},MouseHandler{U}}) # time: 0.016197048 + @assert precompile(Tuple{typeof(rubberband_move),Canvas{U},RubberBand{U},MouseButton{U},Cairo.CairoContext}) # time: 0.001334047 + + @assert precompile(Tuple{typeof(set_coordinates),Canvas{U},BoundingBox}) # time: 0.003914427 + @assert precompile(Tuple{typeof(set_coordinates),Canvas{U},ZoomRegion{RInt}}) # time: 0.002466032 + end + @assert precompile(Tuple{typeof(convertunits),Type{DeviceUnit},Canvas{UserUnit},UserUnit,UserUnit}) # time: 0.002624784 @assert precompile(Tuple{typeof(convertunits),Type{UserUnit},Canvas{UserUnit},DeviceUnit,DeviceUnit}) # time: 0.001084311 - @assert precompile(Tuple{Type{ZoomRegion},XY{ClosedInterval{RInt64}},BoundingBox}) # time: 0.001061675 - @assert precompile(Tuple{typeof(convert),Type{XY{RInt64}},XY{Float64}}) # time: 0.001046295 - @assert precompile(Tuple{Type{DeviceUnit},Int64}) # time: 0.001015558 - # Special handling of the interactives to precompile the callback functions + # image_surface & copy! + for T in (Gray{N0f8}, GrayA{N0f8}, Gray{Float32}, Gray{Float64}, + RGB{N0f8}, RGBA{N0f8}, RGB{Float32}, RGBA{Float32}, RGB{Float64}, RGBA{Float64}) + @assert precompile(Tuple{typeof(image_surface),Matrix{T}}) # time: 0.046262104 etc. + for U in (UserUnit, DeviceUnit) + @assert precompile(Tuple{typeof(copy!),Canvas{U},Matrix{T}}) # time: 0.09909209 + end + end + + # zoom & friends + @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt},Float64}) # time: 0.018138554 for U in (UserUnit, DeviceUnit) - guidict = init_pan_scroll(canvas(U), Observable(ZoomRegion(rand(3,3)))) - @assert precompile(guidict["pan"].f, (MouseScroll{U},)) + @assert precompile(Tuple{Type{XY{U}},Float64,Float64}) # time: 0.010058553 + @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt},Float64,XY{U}}) # time: 0.002209134 + end + @assert precompile(Tuple{typeof(zoom),ZoomRegion{RInt},Int}) # time: 0.001822723 + @assert precompile(Tuple{Type{ZoomRegion},Tuple{UnitRange{Int}, UnitRange{Int}}}) # time: 0.009671802 + @assert precompile(Tuple{Type{ZoomRegion},Tuple{Base.OneTo{Int}, Base.OneTo{Int}}}) # time: 0.006405025 + @assert precompile(Tuple{Type{ZoomRegion},Matrix{RGB{N0f8}}}) # time: 0.001308999 + @assert precompile(Tuple{Type{ZoomRegion},Tuple{UnitRange{Int}, UnitRange{Int}},Tuple{UnitRange{Int}, UnitRange{Int}}}) # time: 0.001152972 + @assert precompile(Tuple{Type{ZoomRegion},XY{ClosedInterval{RInt}},BoundingBox}) # time: 0.001061675 + + @assert precompile(Tuple{typeof(setindex!),Observable{ZoomRegion{RInt}},Tuple{UnitRange{Int}, UnitRange{Int}}}) # time: 0.003122286 + @assert precompile(Tuple{typeof(setindex!),Observable{ZoomRegion{RInt}},XY{ClosedInterval{Int}}}) # time: 0.001308547 + @assert precompile(Tuple{Type{BoundingBox},XY{ClosedInterval{Int}}}) # time: 0.001497522 + + # pan & friends + @assert precompile(Tuple{typeof(pan),ClosedInterval{Int},Float64,ClosedInterval{Int}}) + @assert precompile(Tuple{typeof(pan_x),ZoomRegion{RInt},Float64}) # time: 0.001364175 + @assert precompile(Tuple{typeof(pan_y),ZoomRegion{RInt},Float64}) # time: 0.001364175 + + # advanced interaction + for U in (UserUnit, DeviceUnit), T in (RInt, Float64) + @assert precompile(Tuple{typeof(init_zoom_rubberband),Canvas{UserUnit},Observable{ZoomRegion{RInt}}}) # time: 0.110777095 + @assert precompile(Tuple{typeof(init_zoom_rubberband),Canvas{UserUnit},Observable{ZoomRegion{RInt}},typeof(GtkObservables.zrb_init_default), typeof(GtkObservables.zrb_reset_default), Int}) # time: 0.110777095 + @assert precompile(Tuple{typeof(init_zoom_scroll),Canvas{UserUnit},Observable{ZoomRegion{RInt}}}) # time: 0.01061211 + @assert precompile(Tuple{typeof(init_pan_drag),Canvas{UserUnit},Observable{ZoomRegion{RInt}}}) # time: 0.004753301 + @assert precompile(Tuple{typeof(init_pan_scroll),Canvas{UserUnit},Observable{ZoomRegion{RInt}}}) # time: 0.00264137 + # Special handling of the interactives to precompile the callback functions + zr = T == RInt ? ZoomRegion(rand(3,3)) : (xy = XY(T(1) .. T(3), T(1) .. T(3)); ZoomRegion(xy, xy)) + guidict = init_pan_scroll(canvas(U), Observable(zr)) + @assert precompile(guidict["pan"]) for f in (init_pan_drag, init_zoom_rubberband) - guidict = f(canvas(U), Observable(ZoomRegion(rand(3,3)))) - @assert precompile(guidict["init"].f, (MouseButton{U},)) - @assert precompile(guidict["drag"].f, (MouseButton{U},)) - @assert precompile(guidict["finish"].f, (MouseButton{U},)) + guidict = f(canvas(U), Observable(zr)) + @assert precompile(guidict["init"]) + @assert precompile(guidict["drag"]) + @assert precompile(guidict["finish"]) end - guidict = init_zoom_scroll(canvas(U), Observable(ZoomRegion(rand(3,3)))) - @assert precompile(guidict["zoom"].f, (MouseScroll{U},)) + guidict = init_zoom_scroll(canvas(U), Observable(zr)) + @assert precompile(guidict["zoom"]) end + + # misc + @assert precompile(Tuple{typeof(axes),ZoomRegion{RInt}}) # time: 0.012924676 + @assert precompile(Tuple{typeof(+),XY{Float64},XY{Float64}}) # time: 0.011136752 + @assert precompile(Tuple{typeof(show),IOBuffer,XY{Float64}}) # time: 0.001530783 + @assert precompile(Tuple{typeof(show),IOBuffer,XY{Int}}) # time: 0.001503061 + @assert precompile(Tuple{typeof(convert),Type{XY{RInt}},XY{Float64}}) # time: 0.001046295 end diff --git a/src/rubberband.jl b/src/rubberband.jl index 289f534..64e28a7 100644 --- a/src/rubberband.jl +++ b/src/rubberband.jl @@ -21,8 +21,8 @@ selections smaller than some threshold. """ function init_zoom_rubberband(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - initiate::Function = zrb_init_default, - reset::Function = zrb_reset_default, + @nospecialize(initiate::Function) = zrb_init_default, + @nospecialize(reset::Function) = zrb_reset_default, minpixels::Integer = 2) where {U,T} enabled = Observable(true) active = Observable(false) @@ -36,7 +36,7 @@ function init_zoom_rubberband(canvas::Canvas{U}, end rb = RubberBand(XY{U}(-1,-1), XY{U}(-1,-1), false, minpixels) ctxcopy = Ref{Cairo.CairoContext}() - init = on(canvas.mouse.buttonpress) do btn::MouseButton{U} + init = on(canvas.mouse.buttonpress; weak=true) do btn::MouseButton{U} if enabled[] if initiate(btn) active[] = true @@ -49,13 +49,13 @@ function init_zoom_rubberband(canvas::Canvas{U}, end nothing end - drag = on(canvas.mouse.motion) do btn::MouseButton{U} + drag = on(canvas.mouse.motion; weak=true) do btn::MouseButton{U} if active[] btn.button == 0 && return nothing rubberband_move(canvas, rb, btn, ctxcopy[]) end end - finish = on(canvas.mouse.buttonrelease) do btn::MouseButton{U} + finish = on(canvas.mouse.buttonrelease; weak=true) do btn::MouseButton{U} if active[] btn.button == 0 && return nothing active[] = false diff --git a/src/widgets.jl b/src/widgets.jl index ca05ba7..1469169 100644 --- a/src/widgets.jl +++ b/src/widgets.jl @@ -48,7 +48,7 @@ function init_observable2widget(getter::Function, setter!::Function, widget::GtkWidget, id, observable) - on(observable) do val + on(observable; weak=true) do val if signal_handler_is_connected(widget, id) signal_handler_block(widget, id) # prevent "recursive firing" of the handler curval = getter(widget) @@ -70,10 +70,8 @@ defaultsetter!(widget,val) = Gtk.G_.value(widget, val) Create a `destroy` callback for `widget` that terminates updating dependent signals. """ function ondestroy(widget::GtkWidget, preserved::AbstractVector) - signal_connect(widget, :destroy) do widget - map(close, preserved) + signal_connect(widget, "destroy") do widget empty!(preserved) - # TODO? close observable end nothing end @@ -84,7 +82,7 @@ struct Slider{T<:Number} <: InputWidget{T} observable::Observable{T} widget::GtkScaleLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function Slider{T}(observable::Observable{T}, widget, id, preserved) where T obj = new{T}(observable, widget, id, preserved) @@ -136,7 +134,7 @@ function slider(range::AbstractRange{T}; Gtk.G_.value(widget, value) ## widget -> observable - id = signal_connect(widget, :value_changed) do w + id = signal_connect(widget, "value_changed") do w observable[] = defaultgetter(w) end @@ -170,7 +168,7 @@ struct Checkbox <: InputWidget{Bool} observable::Observable{Bool} widget::GtkCheckButtonLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function Checkbox(observable::Observable{Bool}, widget, id, preserved) obj = new(observable, widget, id, preserved) @@ -202,13 +200,13 @@ function checkbox(value::Bool; widget=nothing, observable=nothing, label="", own end Gtk.G_.active(widget, value) - id = signal_connect(widget, :clicked) do w + id = signal_connect(widget, "clicked") do w observable[] = Gtk.G_.active(w) end preserved = [] push!(preserved, init_observable2widget(w->Gtk.G_.active(w), - (w,val)->Gtk.G_.active(w, val), - widget, id, observable)) + (w,val)->Gtk.G_.active(w, val), + widget, id, observable)) if own ondestroy(widget, preserved) end @@ -224,7 +222,7 @@ struct ToggleButton <: InputWidget{Bool} observable::Observable{Bool} widget::GtkToggleButtonLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function ToggleButton(observable::Observable{Bool}, widget, id, preserved) obj = new(observable, widget, id, preserved) @@ -256,7 +254,7 @@ function togglebutton(value::Bool; widget=nothing, observable=nothing, label="", end Gtk.G_.active(widget, value) - id = signal_connect(widget, :clicked) do w + id = signal_connect(widget, "clicked") do w setindex!(observable, Gtk.G_.active(w)) end preserved = [] @@ -309,11 +307,11 @@ function button(; if own === nothing own = observable != obsin end - if widget == nothing + if widget === nothing widget = GtkButton(label) end - id = signal_connect(widget, :clicked) do w + id = signal_connect(widget, "clicked") do w setindex!(observable, nothing) end @@ -361,7 +359,7 @@ function textbox(::Type{T}; observable=nothing, syncsig=true, own=nothing, - gtksignal=:activate) where T + gtksignal::String="activate") where T if T <: AbstractString && range !== nothing throw(ArgumentError("You cannot set a range on a string textbox")) end @@ -373,7 +371,7 @@ function textbox(::Type{T}; if widget === nothing widget = GtkEntry() end - set_gtk_property!(widget, :text, value) + set_gtk_property!(widget, "text", value) id = signal_connect(widget, gtksignal) do w setindex!(observable, entrygetter(w, observable, range)) @@ -399,14 +397,14 @@ function textbox(value::T; observable=nothing, syncsig=true, own=nothing, - gtksignal=:activate) where T + gtksignal="activate") where T textbox(T; widget=widget, value=value, range=range, observable=observable, syncsig=syncsig, own=own, gtksignal=gtksignal) end -entrygetter(w, observable::Observable{T}, ::Nothing) where {T<:AbstractString} = - get_gtk_property(w, :text, String) +entrygetter(w, ::Observable{<:AbstractString}, ::Nothing) = + get_gtk_property(w, "text", String) function entrygetter(w, observable::Observable{T}, range) where T - val = tryparse(T, get_gtk_property(w, :text, String)) + val = tryparse(T, get_gtk_property(w, "text", String)) if val === nothing nval = observable[] # Invalid entry, restore the old value @@ -426,16 +424,16 @@ function nearest(val, r::AbstractRange) r[clamp(i, first(ax), last(ax))] end -entrysetter!(w, val) = set_gtk_property!(w, :text, string(val)) +entrysetter!(w, val) = set_gtk_property!(w, "text", string(val)) ######################### Textarea ########################### struct Textarea <: InputWidget{String} observable::Observable{String} - widget::GtkTextView + widget::GtkTextViewLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function Textarea(observable::Observable{String}, widget, id, preserved) obj = new(observable, widget, id, preserved) @@ -464,20 +462,21 @@ function textarea(value::String=""; if widget === nothing widget = GtkTextView() end - buf = Gtk.G_.buffer(widget) - set_gtk_property!(buf, :text, value) + buf = widget[:buffer, GtkTextBuffer] + buf[String] = value + sleep(0.01) # without this, we more frequently get segfaults...not sure why - id = signal_connect(buf, :changed) do w - setindex!(observable, get_gtk_property(w, :text, String)) + id = signal_connect(buf, "changed") do w + setindex!(observable, w[String]) end preserved = [] if syncsig - # GtkTextBuffer is not a GtkWdiget, so we have to do this manually - push!(preserved, on(observable) do val + # GtkTextBuffer is not a GtkWidget, so we have to do this manually + push!(preserved, on(observable; weak=true) do val signal_handler_block(buf, id) - curval = get_gtk_property(buf, :text, String) - curval != val && set_gtk_property!(buf, :text, val) + curval = get_gtk_property(buf, "text", String) + curval != val && set_gtk_property!(buf, "text", val) signal_handler_unblock(buf, id) nothing end) @@ -494,7 +493,7 @@ struct Dropdown <: InputWidget{String} mappedsignal::Observable{Any} widget::GtkComboBoxTextLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function Dropdown(observable::Observable{String}, mappedsignal::Observable, widget, id, preserved) obj = new(observable, mappedsignal, widget, id, preserved) @@ -559,7 +558,7 @@ function dropdown(; choices=nothing, str2int = Dict{String,Int}() int2str = Dict{Int,String}() getactive(w) = int2str[get_gtk_property(w, :active, Int)] - setactive!(w, val) = set_gtk_property!(widget, :active, str2int[val]) + setactive!(w, val) = set_gtk_property!(w, "active", str2int[val]) k = -1 for c in choices str = juststring(c) @@ -572,7 +571,7 @@ function dropdown(; choices=nothing, end setactive!(widget, value) - id = signal_connect(widget, :changed) do w + id = signal_connect(widget, "changed") do w setindex!(observable, getactive(w)) end @@ -592,6 +591,10 @@ function dropdown(; choices=nothing, Dropdown(observable, mappedsignal, widget, id, preserved) end +function Base.precompile(w::Dropdown) + return invoke(precompile, Tuple{Widget}, w) & precompile(w.mappedsignal) +end + function dropdown(choices; kwargs...) dropdown(; choices=choices, kwargs...) end @@ -673,7 +676,7 @@ pairaction(p::Pair{String,F}) where {F<:Function} = p.second struct Label <: Widget observable::Observable{String} - widget::GtkLabel + widget::GtkLabelLeaf preserved::Vector{Any} function Label(observable::Observable{String}, widget, preserved) @@ -704,13 +707,15 @@ function label(value; if widget === nothing widget = GtkLabel(value) else - set_gtk_property!(widget, :label, value) + set_gtk_property!(widget, "label", value) end preserved = [] if syncsig - push!(preserved, on(observable) do val - set_gtk_property!(widget, :label, val) - end) + let widget=widget + push!(preserved, on(observable; weak=true) do val + set_gtk_property!(widget, "label", val) + end) + end end if own ondestroy(widget, preserved) @@ -792,7 +797,7 @@ struct SpinButton{T<:Number} <: InputWidget{T} observable::Observable{T} widget::GtkSpinButtonLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function SpinButton{T}(observable::Observable{T}, widget, id, preserved) where T obj = new{T}(observable, widget, id, preserved) @@ -844,7 +849,7 @@ function spinbutton(range::AbstractRange{T}; Gtk.G_.value(widget, value) ## widget -> observable - id = signal_connect(widget, :value_changed) do w + id = signal_connect(widget, "value_changed") do w setindex!(observable, defaultgetter(w)) end @@ -878,7 +883,7 @@ struct CyclicSpinButton{T<:Number} <: InputWidget{T} observable::Observable{T} widget::GtkSpinButtonLeaf id::Culong - preserved::Vector + preserved::Vector{Any} function CyclicSpinButton{T}(observable::Observable{T}, widget, id, preserved) where T obj = new{T}(observable, widget, id, preserved) @@ -906,12 +911,12 @@ than the minimum of the range `carry_up` is updated with `false`. Optional argum - the `orientation` of the cyclicspinbutton. """ function cyclicspinbutton(range::AbstractRange{T}, carry_up::Observable{Bool}; - widget=nothing, - value=nothing, - observable=nothing, - orientation="horizontal", - syncsig=true, - own=nothing) where T + widget=nothing, + value=nothing, + observable=nothing, + orientation="horizontal", + syncsig=true, + own=nothing) where T obsin = observable observable, value = init_wobsval(T, observable, value; default=range.start) if own === nothing @@ -933,7 +938,7 @@ function cyclicspinbutton(range::AbstractRange{T}, carry_up::Observable{Bool}; Gtk.G_.value(widget, value) ## widget -> observable - id = signal_connect(widget, :value_changed) do w + id = signal_connect(widget, "value_changed") do w setindex!(observable, defaultgetter(w)) end @@ -946,13 +951,13 @@ function cyclicspinbutton(range::AbstractRange{T}, carry_up::Observable{Bool}; ondestroy(widget, preserved) end - push!(preserved, on(observable) do val + push!(preserved, on(observable; weak=true) do val if val > maximum(range) observable.val = minimum(range) setindex!(carry_up, true) end end) - push!(preserved, on(observable) do val + push!(preserved, on(observable; weak=true) do val if val < minimum(range) observable.val = maximum(range) setindex!(carry_up, false) @@ -1010,10 +1015,10 @@ julia> for i = 1:n ``` """ function progressbar(interval::AbstractInterval{T}; - widget=nothing, - observable=nothing, - syncsig=true, - own=nothing) where T<:Number + widget=nothing, + observable=nothing, + syncsig=true, + own=nothing) where T<:Number value = minimum(interval) obsin = observable observable, value = init_wobsval(T, observable, value) @@ -1023,12 +1028,12 @@ function progressbar(interval::AbstractInterval{T}; if widget === nothing widget = GtkProgressBar() else - set_gtk_property!(widget, :fraction, interval2fraction(interval, value)) + set_gtk_property!(widget, "fraction", interval2fraction(interval, value)) end preserved = [] if syncsig - push!(preserved, on(observable) do val - set_gtk_property!(widget, :fraction, interval2fraction(interval, val)) + push!(preserved, on(observable; weak=true) do val + set_gtk_property!(widget, "fraction", interval2fraction(interval, val)) end) end if own diff --git a/test/runtests.jl b/test/runtests.jl index 504c26e..94bebe0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,9 +15,9 @@ include("tools.jl") l = label("Hello") @test observable(l) == l.observable @test observable(observable(l)) == l.observable - @test get_gtk_property(l, :label, String) == "Hello" + @test get_gtk_property(l, "label", String) == "Hello" l[] = "world" - @test get_gtk_property(l, :label, String) == "world" + @test get_gtk_property(l, "label", String) == "world" @test string(l) == string("Gtk.GtkLabelLeaf with ", string(observable(l))) # Test other elements of the Observables API counter = Ref(0) @@ -66,16 +66,16 @@ include("tools.jl") push!(bx, txt) push!(bx, num) Gtk.showall(win) - @test get_gtk_property(txt, :text, String) == "Type something" + @test get_gtk_property(txt, "text", String) == "Type something" txt[] = "ok" - @test get_gtk_property(txt, :text, String) == "ok" - set_gtk_property!(txt, :text, "other direction") - signal_emit(widget(txt), :activate, Nothing) + @test get_gtk_property(txt, "text", String) == "ok" + set_gtk_property!(txt, "text", "other direction") + signal_emit(widget(txt), "activate", Nothing) @test txt[] == "other direction" - @test get_gtk_property(num, :text, String) == "5" + @test get_gtk_property(num, "text", String) == "5" @test_throws ArgumentError num[] = 11 num[] = 8 - @test get_gtk_property(num, :text, String) == "8" + @test get_gtk_property(num, "text", String) == "8" meld = map(txt, num) do t, n join((t, n), 'X') end @@ -86,14 +86,14 @@ include("tools.jl") @test meld[] == "4X4" destroy(win) - # ## textarea (aka TextView) - # v = textarea("Type something longer") - # win = Window(v) - # Gtk.showall(win) - # @test v[] == "Type something longer" - # v[] = "ok" - # @test get_gtk_property(Gtk.G_.buffer(v.widget), :text, String) == "ok" - # destroy(win) + ## textarea (aka TextView) + v = textarea("Type something longer") + win = Window(v) + Gtk.showall(win) + @test v[] == "Type something longer" + v[] = "ok" + @test get_gtk_property(Gtk.G_.buffer(v.widget), "text", String) == "ok" + destroy(win) ## slider s = slider(1:15) @@ -127,7 +127,7 @@ include("tools.jl") dd = dropdown(("Strawberry", "Vanilla", "Chocolate")) @test dd[] == "Strawberry" dd[] = "Chocolate" - @test get_gtk_property(dd, :active, Int) == 2 + @test get_gtk_property(dd, "active", Int) == 2 destroy(dd.widget) r = Ref(0) @@ -358,7 +358,7 @@ end c = canvas() f = AspectFrame(c, "Some title", 0.5, 0.5, 3.0) @test isa(f, Gtk.GtkAspectFrameLeaf) - @test get_gtk_property(f, :ratio, Float64) == 3.0 + @test get_gtk_property(f, "ratio", Float64) == 3.0 destroy(f) end @@ -628,8 +628,6 @@ end g[1,1] = textbox("hello") end -# Ensure that the examples run (but the Observables queue is stopped, so -# they won't work unless one calls `@async Observables.run()` manually) examplepath = joinpath(dirname(dirname(@__FILE__)), "examples") include(joinpath(examplepath, "imageviewer.jl")) include(joinpath(examplepath, "widgets.jl"))