Skip to content
Snippets Groups Projects
Select Git revision
  • ec8bca0286e4ae742dfa1a4ce78d2f85fae4c61c
  • main default protected
  • dev protected
  • dev-doc
  • v0.20.2
  • v0.20.1
  • v0.20
  • v0.19.1
  • v0.19
  • v0.18.4
  • v0.18.3
  • v0.18.1
  • v0.18
  • v0.17.1
  • v0.17
  • v0.16.2
  • v0.16.1
  • v0.16
  • v0.15.2
  • v0.15.1
  • v0.15
  • v0.14.1
  • v0.14
  • v0.13.1
24 results

controllers.jl

Blame
  • controllers.jl 14.52 KiB
    struct ControllerHub
        controllers::Dict{Symbol, Any}
    end
    
    ControllerHub() = ControllerHub(Dict{Symbol, Any}())
    
    Base.isempty(hub::ControllerHub) = isempty(hub.controllers)
    Base.getindex(hub::ControllerHub, ref::Symbol) = hub.controllers[ref]
    Base.setindex!(hub::ControllerHub, val, ref::Symbol) = (hub.controllers[ref] = val)
    Base.haskey(hub::ControllerHub, ref::Symbol) = haskey(hub.controllers, ref)
    Base.get!(hub::ControllerHub, ref::Symbol, val) = get!(hub.controllers, ref, val)
    Base.get(hub::ControllerHub, ref::Symbol, val) = get(hub.controllers, ref, val)
    
    function TidyObservables.newcontroller(main::ControllerHub, key::Symbol, child, args...)
        controller = child(main, args...)
        main[key] = controller
        return controller
    end
    
    gethub(c) = c.hub
    gethub(hub::ControllerHub) = hub
    
    # events - definitions
    
    const NoLarva = typemax(LarvaID)
    
    struct LarvaEvents
        hovered::Observable{LarvaID}
        activated::Observable{LarvaID}
        deactivated::Observable{LarvaID}
    end
    
    LarvaEvents() = LarvaEvents(Observable(NoLarva), Observable(NoLarva), Observable(NoLarva))
    
    larvaevents(c) = get!(gethub(c), :larvaevents, LarvaEvents())
    
    const UserTag = String
    const NoTag = ""
    
    struct TagEvents
        created::Observable{UserTag}
        renamed::Observable{Pair{UserTag, UserTag}}
        renamefailed::Observable{Pair{UserTag, UserTag}}
        activated::Observable{UserTag}
        deactivated::Observable{UserTag}
    end
    
    TagEvents() = TagEvents(Observable(NoTag), Observable(NoTag => NoTag), Observable(NoTag => NoTag), Observable(NoTag), Observable(NoTag))
    
    tagevents(c) = get!(gethub(c), :tagevents, TagEvents())
    
    struct TaggingEvents
        assignmentfailed::Observable{String}
    end
    
    TaggingEvents() = TaggingEvents(Observable(""))
    
    taggingevents(c) = get!(gethub(c), :taggingevents, TaggingEvents())
    
    struct FilterEvents
        discard_larva_edits::Observable{LarvaID}
        allow_multiple_tags::Observable{Bool}
    end
    
    FilterEvents() = FilterEvents(Observable{LarvaID}(0), Observable(false))
    
    function filterevents(controller)
        events = get!(gethub(controller), :filterevents, FilterEvents())
        if isempty(Observables.listeners(events.discard_larva_edits))
            on(events.discard_larva_edits) do id
                @assert 0 < id
                larva = getlarva(controller, id)
                empty!(larva.usertags[])
            end
        end
        return events
    end
    
    # events - implementations
    
    function createtag!(controller, tag::ObservableTag)
        tagname = tag.name[]
        if isempty(tagname)
            # tag name was undefined; it is still undefined => do nothing
            @warn "empty tag name"
            return false
        elseif exists(tag, controller)
            failure = tagname
            # invalidate
            tag.name[] = NoTag
            renamefailed!(controller, tag, failure)
            return false
        else
            @info "Tag \"$(tagname)\" created"
            push!(transactions(controller), CreateTag(tag))
            tagevents(controller).created[] = tag
            @info "Tag \"$(tagname)\" activated"
            tagevents(controller).activated[] = tag
        end
        return true
    end
    
    function renametag!(controller, tag::ObservableTag, newtag::Union{Nothing, UserTag}=nothing)
        tagname = tag.name[]
        newtag = @something newtag tagname
        if isempty(newtag)
            @warn "empty tag name"
            return false
        elseif newtag != tagname
            if exists(tag, controller)
                renamefailed!(controller, tag, newtag)
                return false
            else
                @info "Tag \"$(tagname)\" renamed \"$(newtag)\""
                transaction = tagname => newtag
                push!(transactions(controller), RenameTag(transaction))
                tagevents(controller).renamed[] = transaction
            end
        end
        return true
    end
    
    function renamefailed!(controller,
            current::ObservableTag,
            failure::UserTag,
        )
        tagname = current.name[]
        @info "Renaming tag \"$(tagname)\" failed"
        tagevents(controller).renamefailed[] = tagname => failure
        Observables.notify(gettags(controller))
    end
    
    function activatetag!(controller, tag::ObservableTag)
        tag.active[] && return false
        tagname = tag.name[]
        @info "Tag \"$(tagname)\" activated"
        push!(transactions(controller), ActivateTag(tagname))
        tagevents(controller).activated[] = tagname
        tag.active[] = true
    end
    
    function deactivatetag!(controller, tag::ObservableTag)
        tag.active[] || return false
        tagname = tag.name[]
        @info "Tag \"$(tagname)\" deactivated"
        push!(transactions(controller), DeactivateTag(tagname))
        tagevents(controller).deactivated[] = tagname
        tag.active[] = false
        return true
    end
    
    function exists(tagname::String, tagsource, lut)
        # note: source observable `tagsource` already contains the `tagname` value;
        #       the present function is called to determine whether to revert `tagsource`
        #       to its previous value (not passed to this function)
        for tag in Observables.to_value(lut)
            if tag !== tagsource && tag.name[] == tagname
                return true
            end
        end
        return false
    end
    
    function assignmentfailed!(controller, reason)
        taggingevents(controller).assignmentfailed[] = reason
    end
    
    struct LarvaController
        hub::ControllerHub
        model::Dict{<:Integer, LarvaModel}
        tag_lut::AbstractObservable{<:TagLUT}
        activelarva::AbstractObservable{<:ActiveLarva}
        player::AbstractAnimator
        # could make LarvaController mutable instead...
        boundingbox::Ref{<:NTuple{4, <:AbstractFloat}}
    end
    
    function LarvaController(main::ControllerHub,
            model::Dict{<:Integer, LarvaModel},
            tag_lut::AbstractObservable{<:TagLUT},
            player::AbstractAnimator)
        activelarva = Observable{ActiveLarva}(nothing)
        boundingbox = Ref((0.0, 10.0, 0.0, 10.0))
        LarvaController(main, model, tag_lut, activelarva, player, boundingbox)
    end
    
    function LarvaController(main::ControllerHub,
            model::Dict{<:Integer, LarvaModel},
            tag_lut::TagLUT,
            times::Vector{PlanarLarvae.Time})
        player = playcontroller(times)
        LarvaController(main, model, Observable(tag_lut), player)
    end
    
    function LarvaController(main::ControllerHub, model::Vector{LarvaModel}, args...)
        model′ = Dict{LarvaID, LarvaModel}()
        for larva in model
            model′[larva.id] = larva
        end
        LarvaController(main, model′, args...)
    end
    
    getactivelarva(c) = getactivelarva(gethub(c)[:larva])
    getactivelarva(c::LarvaController) = c.activelarva
    activatelarva!(c, id) = activatelarva!(gethub(c)[:larva], id)
    deactivatelarva!(c) = deactivatelarva!(gethub(c)[:larva])
    
    getlarvae(c) = gethub(c)[:larva].model
    getlarva(c, id) = getlarvae(c)[id]
    
    setboundingbox!(c, bb) = setboundingbox!(gethub(c)[:larva], bb)
    setboundingbox!(c::LarvaController, bb) = (c.boundingbox[] = bb)
    
    getplayer(c::LarvaController) = c.player
    getplayer(c) = getplayer(gethub(c))
    getplayer(hub::ControllerHub) = haskey(hub, :player) ? hub[:player] : getplayer(hub[:larva])
    
    gettags(c) = gettags(gethub(c)[:larva])
    gettags(c::LarvaController) = c.tag_lut
    
    # events
    
    function hoverlarva!(controller::LarvaController, larva_id::LarvaID)
        @debug "larva #$(larva_id) hovered"
        larvaevents(controller).hovered[] = larva_id
    end
    
    function activatelarva!(controller::LarvaController, larva_id::Union{Nothing, LarvaID})
        deactivatelarva!(controller)
        larva = larvaevents(controller)
        if isnothing(larva_id) || larva_id == NoLarva
            larva.activated.val = NoLarva
        else
            larva.deactivated.val = NoLarva # optional
            controller.activelarva[] = larva_id
            @info "Larva #$(larva_id) activated"
            larva.activated[] = larva_id
        end
    end
    
    function deactivatelarva!(controller::LarvaController)
        larva = larvaevents(controller)
        current = larva.activated[]
        current == NoLarva && return
        @info "Larva #$(current) deactivated"
        larva.activated.val = NoLarva # required
        larva.deactivated[] = current
    end
    
    # to be implemented for each GUI "backend" (e.g. for WGL in wgl.jl)
    function setedited! end
    function addtosavequeue! end
    
    setedited!(c, id) = setedited!(gethub(c)[:larvafilter], id)
    addtosavequeue!(c, id) = addtosavequeue!(gethub(c)[:larvafilter], id)
    
    function flag_active_larva_as_edited(controller; exclude=false)
        id = getactivelarva(controller)[]
        setedited!(controller, id)
        if !exclude
            addtosavequeue!(controller, id)
        end
    end
    
    #
    
    function computebbox(data; resolution=1)
        bb = Meshes.boundingbox(data)
        xy0 = coordinates(minimum(bb))
        x0, y0 = @. floor(xy0 / resolution - 0.1) * resolution
        xy1 = coordinates(maximum(bb))
        x1, y1 = @. ceil(xy1 / resolution + 0.1) * resolution
        return (x0, x1, y0, y1)
    end
    
    function setbounds!(view::Axis, ctrl::Union{LarvaController, ControllerHub}, data)
        bbox = computebbox(data; resolution=10)
        setboundingbox!(ctrl, bbox)
        limits!(view, bbox...)
    end
    
    setbounds!(view::Axis, lb, ub) = limits!(view, lb[1], ub[1], lb[2], ub[2])
    
    function slave(master::Observable, policy::ObservationPolicy=IndependentObservables())
        newobservable(policy, master)
    end
    
    function slave(model::Dict{ID, LarvaModel},
            policy::ObservationPolicy=IndependentObservables(),
        ) where {ID<:Integer}
        model′ = Dict{ID, LarvaModel}()
        for (id, larva) in pairs(model)
            larva′ = LarvaModel(larva.id,
                                larva.alignedsteps,
                                larva.path,
                                larva.fullstates,
                                newobservable(policy, larva.usertags))
            model′[id] = larva′
        end
        return model′
    end
    
    function slave(tag_lut::TagLUT,
            policy::ObservationPolicy=IndependentObservables())
        tag_lut′ = TagLUT()
        for tag in tag_lut
            push!(slave(tag, policy))
        end
        return newobservable(tag_lut′, policy)
    end
    
    function slave(tag::ObservableTag, policy::ObservationPolicy)
        ObservableTag(tag.original,
                      newobservable(tag.name, policy),
                      newobservable(tag.color, policy),
                      newobservable(tag.active, policy))
    end
    
    function slave(master::LarvaController,
            policy::ObservationPolicy=IndependentObservables(),
        )
        model = slave(master.model, policy)
        tag_lut = slave(master.tag_lut, policy)
        player = slave(master.player, policy)
        LarvaController(master.hub,
                        model,
                        tag_lut,
                        master.activelarva,
                        player,
                        master.boundingbox)
    end
    
    stop!(controller::LarvaController) = stop!(controller.player)
    
    # history
    
    transactions(c) = get!(gethub(c), :transactions, Transaction[])
    
    function setevents!(controller, tagvalidator::ValidatorBundle{ObservableTag})
        tag = tagvalidator.model
        on(tag.active) do b
            if b
                activatetag!(controller, tag)
            else
                deactivatetag!(controller, tag)
            end
        end
        on(viewfailed(tagvalidator, :name)) do (_, failure)
            tagevents(controller).renamefailed[] = (tag.name[] => failure)
        end
        on(tagvalidator, :name) do newname, curname
           #renametag!(controller, tag)
           transaction = curname => newname
           push!(transactions(controller), RenameTag(transaction))
           tagevents(controller).renamed[] = transaction
           return Validate
        end
        onany(tag.active, tag.name, tag.color) do _, _, _
            Observables.notify(gettags(controller))
        end
    end
    
    # file metadata
    struct MetadataItem
        name::Validator{String}
        value::Validator{String}
    end
    
    MetadataItem() = MetadataItem("", "")
    MetadataItem(name::String, value::String) = MetadataItem(Validator(name), Validator(value))
    MetadataItem(name::Symbol, args...) = MetadataItem(String(name), args...)
    
    struct MetadataTable
        entries::Vector{MetadataItem}
    
        function MetadataTable(entries::Vector{MetadataItem};
                connect::Bool=true)
            table = new(entries)
            if connect
                for entry in entries
                    connect!(table, entry)
                end
            end
            return table
        end
    end
    
    MetadataTable() = MetadataTable(MetadataItem[])
    MetadataTable(metadata::AbstractDict) = MetadataTable([MetadataItem(key, value) for (key, value) in pairs(metadata) if value isa String])
    MetadataTable(run::Run) = MetadataTable(run.attributes[:metadata])
    
    function asdict(metadata::MetadataTable)
        dict = OrderedDict{String, String}()
        for entry in metadata.entries
            key, value = entry.name.model[], entry.value.model[]
            if !isempty(key) && !isempty(value)
                dict[key] = value
            end
        end
        dict
    end
    
    Base.length(table::MetadataTable) = length(table.entries)
    Base.haskey(table::MetadataTable, name::String) = any(item -> name == item.name.model[], table.entries)
    
    function Base.push!(table::MetadataTable, item::MetadataItem)
        push!(table.entries, item)
        connect!(table, item)
        return table
    end
    
    function connect!(table::MetadataTable, item::MetadataItem)
        on(item.name.model) do val
            if isempty(val)
                item.value.model[] = ""
            end
        end
        on(item.name, Val(1)) do newvalue
            if !isempty(newvalue) && haskey(table, newvalue)
                Invalidate
            else
                Validate
            end
        end
        on(item.value, Val(1)) do newvalue
            if !isempty(newvalue) && isempty(item.name.model[])
                Invalidate
            else
                Validate
            end
        end
        return table
    end
    
    function getmetadatatable(controller, nentries=nothing)
        hub = gethub(controller)
        table = nothing
        try
            table = hub[:metadatatable][]
        catch
            metadata = OrderedDict{Symbol, Any}()
            if haskey(hub, :output)
                dataset = hub[:output]
                if haskey(dataset.attributes, :metadata)
                    merge!(metadata, dataset.attributes[:metadata])
                end
                if !isempty(dataset)
                    run = first(values(dataset))
                    if haskey(run.attributes, :metadata)
                        merge!(metadata, run.attributes[:metadata])
                    end
                end
            end
            table = MetadataTable(metadata)
            hub[:metadatatable] = Observable(table)
        end
        if !isnothing(nentries)
            while length(table) < nentries
                push!(table, MetadataItem())
            end
        end
        return table
    end
    
    function Base.pairs(table::MetadataTable)
        [(getview!(item.name), getview!(item.value)) for item in table.entries]
    end
    
    # session
    
    function identifyclient(controller)
        try
            session = gethub(controller)[:session]
            @logmsg Stub "Identifying client" session
        catch
            @warn "Cannot retrieve session and identify the client"
        end
        return "??"
    end
    
    # load animation
    
    turn_load_animation_off(controller) = (gethub(controller)[:loadanimation][] = false)
    turn_load_animation_on(controller) = (gethub(controller)[:loadanimation][] = true)