Skip to content

Commit

Permalink
Require Observables 0.4 & improve inference/specialization (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy authored Mar 12, 2021
1 parent 1f3fb7e commit 5101876
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 253 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions examples/widgets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions src/GtkObservables.jl
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
94 changes: 52 additions & 42 deletions src/extrawidgets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 ##########################
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"])
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Loading

2 comments on commit 5101876

@timholy
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/31830

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.0.0 -m "<description of version>" 51018767c082db1eb75700d363cfdfbd724e9f49
git push origin v1.0.0

Please sign in to comment.