Skip to content

Commit

Permalink
better CancasSelect (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaqz authored Aug 27, 2024
1 parent 1489855 commit 1cdcd8a
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 38 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ point_canvas.active[] = true
poly_canvas = GeometryCanvas{Polygon}(; figure, axis)

layers = Dict(
:point=>point_canvas.active,
:line=>line_canvas.active,
:poly=>poly_canvas.active,
:point=>point_canvas,
:line=>line_canvas,
:poly=>poly_canvas,
)

MakieDraw.CanvasSelect(figure[3, 1], axis; layers)
Expand Down
115 changes: 96 additions & 19 deletions src/canvas_select.jl
Original file line number Diff line number Diff line change
@@ -1,64 +1,141 @@
abstract type AbstractCanvasSelect <: Makie.Block end

const LayerDict = Dict{Symbol,Observable{Bool}}

"""
CanvasSelect <: AbstractCanvasSelect
CanvasSelect <: AbstractCanvasSelect
CanvasSelect(figure; [layers])
A menu widget for selecting active canvases.
It will deactivate all non-selected canvases, and select the active one.
# Arguments
- `figure::Union{Figure,GridPosition}` a Figure or `GridPosition`.
- `ax::Axis`: the `Axis` the canvases are on.
# Keywords
- `layers`: Dict{Symbol,Orbservable{bool}
- `layers`: A `Dict{Symbol,Orbservable{Bool}}` where the Symbols are
the names that will appear in the `Menu`, and the Observables are
the initial
# Example
```julia
using MakieDraw, GLMakie
layers = Dict(
:paint=>paint_canvas.active,
:point=>point_canvas.active,
:paint=>paint_canvas.active,
:point=>point_canvas.active,
:line=>line_canvas.active,
:poly=>poly_canvas.active,
)
MakieDraw.CanvasSelect(figure[2, 1]; layers)
fig = Figure()
cs = CanvasSelect(fig[2, 1]; layers)
```
"""
struct CanvasSelect{L} <: AbstractCanvasSelect
layers::L
struct CanvasSelect <: AbstractCanvasSelect
layers::LayerDict
menu::Menu
end
function CanvasSelect(m::Menu; layers=Dict{Symbol,Observable{Bool}}())
function CanvasSelect(m::Menu; layers::LayerDict=LayerDict())
on(m.selection) do selected
for (key, active) in layers
for (key, active) in layers
active[] = key == Symbol(selected)
notify(active)
end
end
CanvasSelect(layers, m)
end
function CanvasSelect(fig::Union{Figure,GridPosition}; layers=Dict{Symbol,Observable{Bool}}())
function CanvasSelect(fig::Union{Figure,GridPosition};
layers::Dict{Symbol,<:Any}=LayerDict()
)
bool_layers = _get_active_observables(layers)
found_active = false
default = "none"
for (key, active) in pairs(layers)
for (key, active) in pairs(bool_layers)
# Only the first active layer can stay active
if !found_active && active[]
if !found_active && active[]
found_active = true
default = string(key)
else
active[] = false
end
end
options = map(string, collect(keys(layers)))
options = map(string, collect(keys(bool_layers)))
m = Menu(fig; options, default)
CanvasSelect(m; layers)
CanvasSelect(m; layers=bool_layers)
end

layers(ls::AbstractCanvasSelect) = ls.layers
layers(cs::AbstractCanvasSelect) = cs.layers
menu(cs::AbstractCanvasSelect) = cs.menu
menuoptions(cs::AbstractCanvasSelect) = menu(cs).options
menuselection(cs::AbstractCanvasSelect) = menu(cs).selection

Base.push!(ls::AbstractCanvasSelect, x::Pair{Symbol,Observable{Bool}}) = push!(layers(ls), x)
Base.getindex(ls::AbstractCanvasSelect, key::Symbol) = layers(ls)[key]
Base.setindex!(ls::AbstractCanvasSelect, x::Observable{Bool}, key::Symbol) = layers(ls)[key] = x
function Base.push!(cs::AbstractCanvasSelect, p::Pair{Symbol,<:AbstractCanvas})
push!(cs, p[1] => p[2].active)
end
function Base.push!(cs::AbstractCanvasSelect, p::Pair{Symbol,Observable{Bool}})
k, v = p
opts = menuoptions(cs)
ls = layers(cs)
if haskey(layers(cs), k)
if ls[k][]
# Turn of the canvas before we remove it
ls[k][] = false
else
# Turn off the new canvas before we add it
v[] = false
notify(v)
end
push!(ls, p)
else
v[] = false
notify(v)
push!(ls, p)
push!(opts[], string(k))
end
notify(opts)
return v
end
Base.getindex(cs::AbstractCanvasSelect, k::Symbol) = layers(cs)[k]
function Base.setindex!(cs::AbstractCanvasSelect, c::AbstractCanvas, k::Symbol)
cs[k] = c.active
return c
end
function Base.setindex!(cs::AbstractCanvasSelect, v::Observable{Bool}, k::Symbol)
opts = menuoptions(cs)
ls = layers(cs)
if haskey(layers(cs), k)
# Turn of the canvas before we remove it
ls[k][] = false
notify(ls[k])
else
v[] = false
notify(v)
end
ls[k] = v
if !(k in opts[])
push!(opts[], string(k))
end
notify(opts)
return v
end

# Get the `active` observables from any `AbstractCanvas` passed in the Dict.
function _get_active_observables(layers::Dict)
bool_layers = LayerDict()
for (k, v) in pairs(layers)
active_obs = if v isa AbstractCanvas
v.active
elseif v isa Observable{Bool}
v
else
throw(ArgumentError("$(typeof(v)) not an allowed layer type: pass an `Observable{Bool}` or any `AbstractCanvas`"))
end
bool_layers[k] = active_obs
end

return bool_layers
end
34 changes: 18 additions & 16 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,27 @@ figure = Figure()
axis = Axis(figure[1, 1])

paint_canvas = PaintCanvas(falses(100, 100); figure, axis)
paint_canvas.active[] = false

line_canvas = GeometryCanvas{LineString}(; figure, axis)
line_canvas.active[] = false

point_canvas = GeometryCanvas{Point}(; figure, axis)
point_canvas.active[] = false

# poly_canvas = GeometryCanvas{Polygon}(; figure, axis)
# poly_canvas.active[] = false

polys = [Polygon([Point(1.0, 2.0), Point(2.0, 3.0), Point(3.0, 1.0), Point(1.0, 2.0)])]
polys = [Polygon([Point(10.0, 50.0), Point(50.0, 70.0), Point(70.0, 10.0), Point(10.0, 50.0)])]
poly_canvas = GeometryCanvas(polys; figure, axis);

layers = Dict(
:paint=>paint_canvas.active,
:point=>point_canvas.active,
:line=>line_canvas.active,
:poly=>poly_canvas.active,
:paint=>paint_canvas, # Passing any AbstractCanvas works
:poly=>poly_canvas.active, # an Observable{Bool} also works
)

MakieDraw.CanvasSelect(figure[2, 1]; layers)
# Add a Canvas selector
cs = MakieDraw.CanvasSelect(figure[2, 1]; layers)

# We can push to it
line_canvas = GeometryCanvas{LineString}(; figure, axis)
push!(cs, :line=>line_canvas)

# line_canvas it should be active now

# And also set values
point_canvas = GeometryCanvas{Point}(; figure, axis)
cs[:point] = point_canvas

# Write the polygons to JSON
# Have to convert here because GeometryBasics `isgeometry` has a bug, see PR #193
Expand All @@ -48,3 +47,6 @@ polys = [Polygon([Point(1.0, 2.0), Point(2.0, 3.0), Point(3.0, 1.0), Point(1.0,
poly!(polys)
poly_canvas = GeometryCanvas(geojson_polys; figure, axis);

# TODO: click and keypress testing
# event.keyboard[] = value
# scene.events.mousebutton[] = ...

0 comments on commit 1cdcd8a

Please sign in to comment.