/
rubberband.jl
149 lines (139 loc) · 5.16 KB
/
rubberband.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
signals = init_zoom_rubberband(canvas::GtkReactive.Canvas,
zr::Signal{ZoomRegion},
initiate = btn->(btn.button == 1 && btn.clicktype == BUTTON_PRESS && btn.modifiers == CONTROL),
reset = btn->(btn.button == 1 && btn.clicktype == DOUBLE_BUTTON_PRESS && btn.modifiers == CONTROL),
minpixels = 2)
Initialize rubber-band selection that updates `zr`. `signals` is a
dictionary holding the Reactive.jl signals needed for rubber-banding;
you can push `true/false` to `signals["enabled"]` to turn rubber
banding on and off, respectively. Your application is responsible for
making sure that `signals` does not get garbage-collected (which would
turn off rubberbanding).
`initiate(btn)` returns `true` when the condition for starting a
rubber-band selection has been met (by default, clicking mouse button
1). The argument `btn` is a [`MouseButton`](@ref) event. `reset(btn)`
returns true when restoring the full view (by default, double-clicking
mouse button 1). `minpixels` can be used for aborting rubber-band
selections smaller than some threshold.
"""
function init_zoom_rubberband(canvas::Canvas{U},
zr::Signal{ZoomRegion{T}},
initiate::Function = zrb_init_default,
reset::Function = zrb_reset_default,
minpixels::Integer = 2) where {U,T}
enabled = Signal(true)
active = Signal(false)
function update_zr(widget, bb)
push!(active, false)
fv = value(zr).fullview
push!(zr, ZoomRegion(fv, XY(interior(bb.xmin..bb.xmax, fv.x),
interior(bb.ymin..bb.ymax, fv.y))
))
nothing
end
rb = RubberBand(XY{U}(-1,-1), XY{U}(-1,-1), false, minpixels)
dummybtn = MouseButton{U}()
local ctxcopy
init = map(filterwhen(enabled, dummybtn, canvas.mouse.buttonpress)) do btn
if initiate(btn)
push!(active, true)
ctxcopy = copy(getgc(canvas))
rb.pos1 = rb.pos2 = btn.position
elseif reset(btn)
push!(active, false) # double-clicks need to cancel the previous single-click
push!(zr, GtkReactive.reset(value(zr)))
end
nothing
end
drag = map(filterwhen(active, dummybtn, canvas.mouse.motion)) do btn
btn.button == 0 && return nothing
rubberband_move(canvas, rb, btn, ctxcopy)
end
finish = map(filterwhen(active, dummybtn, canvas.mouse.buttonrelease)) do btn
btn.button == 0 && return nothing
push!(active, false)
rubberband_stop(canvas, rb, btn, ctxcopy, update_zr)
end
Dict("enabled"=>enabled, "active"=>active, "init"=>init, "drag"=>drag, "finish"=>finish)
end
zrb_init_default(btn) = btn.button == 1 && btn.clicktype == BUTTON_PRESS && (btn.modifiers & 0x0f) == CONTROL
zrb_reset_default(btn) = btn.button == 1 && btn.clicktype == DOUBLE_BUTTON_PRESS && (btn.modifiers & 0x0f) == CONTROL
# For rubberband, we draw the selection region on the front canvas, and repair
# by copying from the back.
mutable struct RubberBand{U}
pos1::XY{U}
pos2::XY{U}
moved::Bool
minpixels::Int
end
const dash = Float64[3.0,3.0]
const nodash = Float64[]
function rb_erase(r::GraphicsContext, rb::RubberBand, ctxcopy)
# Erase the previous rubberband by copying from back surface to front
rb_set(r, rb)
# Because line widths are expressed in pixels, let's go to device units
save(r)
reset_transform(r)
save(ctxcopy)
reset_transform(ctxcopy)
set_source(r, ctxcopy)
set_line_width(r, 3)
set_dash(r, nodash)
stroke(r)
restore(r)
restore(ctxcopy)
end
function rb_draw(r::GraphicsContext, rb::RubberBand)
rb_set(r, rb)
save(r)
reset_transform(r)
set_line_width(r, 1)
set_dash(r, dash, 3.0)
set_source_rgb(r, 1, 1, 1)
stroke_preserve(r)
set_dash(r, dash, 0.0)
set_source_rgb(r, 0, 0, 0)
stroke(r)
restore(r)
end
function rb_set(r::GraphicsContext, rb::RubberBand)
x1, y1 = rb.pos1.x, rb.pos1.y
x2, y2 = rb.pos2.x, rb.pos2.y
rectangle(r, x1, y1, x2 - x1, y2 - y1)
end
function rubberband_move(c::Canvas, rb::RubberBand, btn, ctxcopy)
if btn.button == 0
return nothing
end
r = getgc(c)
if rb.moved
rb_erase(r, rb, ctxcopy)
end
rb.moved = true
# Draw the new rubberband
rb.pos2 = btn.position
rb_draw(r, rb)
reveal(c, false)
nothing
end
function rubberband_stop(c::GtkReactive.Canvas, rb::RubberBand, btn, ctxcopy, callback_done)
if !rb.moved
return nothing
end
r = getgc(c)
rb_set(r, rb)
rb_erase(r, rb, ctxcopy)
reveal(c, false)
pos = btn.position
x, y = pos.x, pos.y
x1, y1 = rb.pos1.x, rb.pos1.y
xd, yd = convertunits(DeviceUnit, r, x, y)
x1d, y1d = convertunits(DeviceUnit, r, x1, y1)
if abs(x1d-xd) > rb.minpixels || abs(y1d-yd) > rb.minpixels
# It moved sufficiently, let's execute the callback
bb = BoundingBox(min(x1,x), max(x1,x), min(y1,y), max(y1,y))
callback_done(c, bb)
end
nothing
end