diff --git a/examples/gui/overview.jl b/examples/gui/overview.jl new file mode 100644 index 0000000..782a3ac --- /dev/null +++ b/examples/gui/overview.jl @@ -0,0 +1,166 @@ +using GLVisualize, Colors, GeometryTypes, Reactive, GLAbstraction, GLFW, GLWindow +import GLVisualize: mm + +if !isdefined(:runtests) + window = glscreen() +end + +text_scale = 6mm +function text_with_background(txt; + background_color = RGBA(1f0, 1f0, 1f0, 1f0), gap = 1mm, + size = nothing, + kw_args... + ) + robj = visualize(txt; kw_args...).children[] + rect = if size == nothing + map(boundingbox(robj)) do bb + mini, w = minimum(bb), widths(bb) + [Point2f0(mini[1] - gap, mini[2] - gap)], Vec2f0(w[1] + 3gap, w[2] + 2gap) + end + else + Signal(([Point2f0(-gap, -gap)], Vec2f0(size[1] + 3gap, size[2] + 2gap))) + end + bg = visualize( + (RECTANGLE, map(first, rect)), + scale = map(last, rect), + offset = Vec2f0(0), + color = background_color, + glow_color = RGBA(0f0, 0f0, 0f0, 0.1f0), + glow_width = 3f0 + ) + Context(bg, robj) +end +slider_val = Signal(1) +slider_str = map(string, slider_val) +color = Signal(RGBA(1f0, 1f0, 1f0, 1f0)) +x = text_with_background( + slider_str, relative_scale = text_scale, + background_color = color, + size = (8*text_scale, text_scale) +) +GLAbstraction.translate!(x, Vec3f0(4mm, 4mm, 0)) +_view(x, window, camera = :fixed_pixel) + +ids = (map(x-> x.id, extract_renderable(x))..., ) +@materialize mouse_buttons_pressed, buttons_pressed, unicode_input = window.inputs +mouseclick = const_lift(GLAbstraction.singlepressed, mouse_buttons_pressed, GLFW.MOUSE_BUTTON_LEFT) +const enterkey = Set([GLFW.KEY_ENTER]) +text_edit = droprepeats(foldp(false, doubleclick(mouseclick, 0.2), buttons_pressed) do v0, clicked, kb + kb == enterkey && return false + clicked && is_same_id(value(mouse2id(window)), ids) && return !v0 + v0 +end) +backspace = Set([GLFW.KEY_BACKSPACE]) +arrowleft = Set([GLFW.KEY_LEFT]) +arrowright = Set([GLFW.KEY_RIGHT]) +ctrl_keyes = (backspace, arrowleft, arrowright) + + +function dont_repeat_value{T}(val, input::Signal{T}) + n = Signal(value(input)) + connect_dont_repeat_value(val, n, input) + n +end + +function connect_dont_repeat_value(val, output, input) + let prev_value = value(input) + Reactive.add_action!(input, output) do output, timestep + nval = value(input) + println(nval) + if prev_value != val + Reactive.send_value!(outputm, nval, timestep) + end + prev_value = nval + end + end +end + +empty_or_ctrl = map(buttons_pressed) do buttons + if buttons in ctrl_keyes + buttons + else + Set{Int}() + end +end +# only let controls through +ctrl_buttons = empty_or_ctrl#dont_repeat_value(Set{Int}(), empty_or_ctrl) +println(x.children[2].id) +txt_edit_s = foldp((false, [' '], 1), unicode_input, text_edit, ctrl_buttons) do v0, chars, edit, ctrl + was_edit, str, cursor = v0 + move(num) = (cursor = clamp(cursor + num, 0, length(str))) + if edit + if !was_edit && isempty(ctrl)# reset + id, idx = value(mouse2id(window)) + empty!(str) + append!(str, string(value(slider_val))) + @show id idx + cursor = if id == x.children[2].id && checkbounds(str, idx) + idx + else + 1 + end + + end + if !isempty(ctrl) + if ctrl == backspace + if !isempty(str) && checkbounds(Bool, str, cursor) + splice!(str, cursor) + move(-1) + end + elseif ctrl == arrowleft + move(-1) + elseif ctrl == arrowright + move(1) + end + else + for char in chars + if isnumber(char) + push!(str, char) + move(1) + end + end + end + strstr = join(str) + if isempty(strstr) # GLVisualize needs to get patched to work with emtpy strings........ + push!(slider_str, " ") + else + push!(slider_str, strstr) + end + push!(color, RGBA(0.8f0, 0.8f0, 0.8f0, 0.4f0)) + else + strstr = replace(join(str), ' ', "") + if !isempty(str) + num = parse(eltype(value(slider_val)), strstr) + push!(slider_val, num) + end + str = [' '] + push!(color, RGBA(1f0, 1f0, 1f0, 1f0)) + end + edit, str, cursor +end + +function calc_position(glyphs, idx; scale = text_scale, start_pos = Point2f0(0)) + if checkbounds(Bool, glyphs, idx) + fonts, atlas = GLVisualize.defaultfont(), GLVisualize.get_texture_atlas() + GLVisualize.calc_position(glyphs[1:idx], start_pos, scale, fonts, atlas)[end:end] + elseif idx == 0 + [Point2f0(-1, 1mm)] + else + [start_pos] + end +end + +cursor_pos = map(txt_edit_s) do edit_tup + edit, str, cursor = edit_tup + calc_position(str, cursor, scale = text_scale, start_pos = Point2f0(text_scale/2 + 1, 1mm)) +end +_view(visualize( + "|", + position = cursor_pos, visible = text_edit, + model = transformation(x.children[2]), + relative_scale = 5mm, +), camera = :fixed_pixel) + +if !isdefined(:runtests) + renderloop(window) +end diff --git a/examples/introduction/video_recording.jl b/examples/introduction/video_recording.jl index 55a6e8f..8ad7f5e 100644 --- a/examples/introduction/video_recording.jl +++ b/examples/introduction/video_recording.jl @@ -6,37 +6,43 @@ end description = """ Example of how to record a video from GLVisualize """ + kitty = visualize(loadasset("cat.obj")) _view(kitty, window) # save video to report dir, or in some tmp dir we'll delete later -path = if haskey(ENV, "CI_REPORT_DIR") +name = if haskey(ENV, "CI_REPORT_DIR") ENV["CI_REPORT_DIR"] * "/videorecord.mkv" else - homedir() -end -name = "" -while true # for some reason, folder retured by mktempdir isn't usable -.- - name = path * "/$(randstring()).mkv" - isfile(name) || break + path = homedir() + while true # for some reason, folder retured by mktempdir isn't usable -.- + name = path * "/$(randstring()).mkv" + isfile(name) || break + end + name end -# create a stream to which we can add frames -io, buffer = GLVisualize.create_video_stream(name, window) -for i=1:10 # record 10 frames - # do something - GLAbstraction.set_arg!(kitty, :color, RGBA{Float32}(1, 0, 1-(i/10), i/10)) - #render current frame - # if you call @async renderloop(window) you can replace this part with yield - GLWindow.render_frame(window) - GLWindow.swapbuffers(window) - GLWindow.poll_reactive() +# only try recording when ffmpeg is installed +if success(`ffmpeg -h`) + # create a stream to which we can add frames + io, buffer = GLVisualize.create_video_stream(name, window) + for i=1:10 # record 10 frames + # do something + GLAbstraction.set_arg!(kitty, :color, RGBA{Float32}(1, 0, 1-(i/10), i/10)) + #render current frame + # if you call @async renderloop(window) you can replace this part with yield + GLWindow.render_frame(window) + GLWindow.swapbuffers(window) + GLWindow.poll_reactive() - # add the frame from the current window - GLVisualize.add_frame!(io, window, buffer) + # add the frame from the current window + GLVisualize.add_frame!(io, window, buffer) + end + # closing the stream will trigger writing the video! + close(io) +else + info("skipped ffmpged video recording, since ffmpeg is not installed!") end -# closing the stream will trigger writing the video! -close(io) if !isdefined(:runtests) renderloop(window) diff --git a/examples/text/annotations.jl b/examples/text/annotations.jl index 925835b..4f93547 100644 --- a/examples/text/annotations.jl +++ b/examples/text/annotations.jl @@ -1,4 +1,6 @@ using GLVisualize, GeometryTypes, Colors, GLAbstraction, Reactive, Images, ModernGL +import GLVisualize: mm, calc_position, glyph_bearing!, glyph_uv_width!, glyph_scale! + import GLVisualize: mm, annotated_text if !isdefined(:runtests) @@ -32,13 +34,13 @@ fbuffer = foldp(rand(RGB{N0f8}, 500, 500), timesignal) do v0, t end end frame_viz = visualize(fbuffer) - # instead of circle you can also use unicode charactes (e.g. '+') position_viz = visualize( (Circle{Float32}(0, 1.5mm), positions), color = RGBA(1f0, 0f0, 0f0, 0.6f0) ) gpu_position = position_viz.children[][:position] + bg_viz = visualize( (ROUNDED_RECTANGLE, gpu_position), scale = widths, diff --git a/src/gui/buttons.jl b/src/gui/buttons.jl index fb80b18..dbc3a50 100644 --- a/src/gui/buttons.jl +++ b/src/gui/buttons.jl @@ -1,15 +1,27 @@ -function button(a, screen; kw_args...) +function button( + a, screen; + background_color = RGBA(1f0, 1f0, 1f0, 0.0f0), + gap = 0.5mm, + kw_args... + ) robj = visualize(a; kw_args...).children[] - const m2id = mouse2id(screen) + m2id = mouse2id(screen) + result = [robj] + bb = value(boundingbox(robj)) + mini, w = minimum(bb), widths(bb) + rect = SimpleRectangle(mini[1] - 2gap, mini[2] - 2gap, w[1] + 2gap, w[2] + 2gap) + bg = visualize(rect, color = background_color, model = robj[:model]).children[] + ids = (robj.id, bg.id) is_pressed = droprepeats(map(screen.inputs[:key_pressed]) do isclicked - isclicked && value(m2id).id == robj.id + id = value(m2id) + isclicked && is_same_id(id, ids) end) robj[:model] = const_lift(is_pressed, robj[:model]) do ip, old ip && return old scalematrix(Vec3f0(0.95))*old end - robj, is_pressed + Context(bg, robj), is_pressed end diff --git a/src/gui/numbers.jl b/src/gui/numbers.jl index 59adb60..cd19c8e 100644 --- a/src/gui/numbers.jl +++ b/src/gui/numbers.jl @@ -93,35 +93,36 @@ function widget{T <: FixedVector}( last_x += w[1] + gap vizz end - Context(le_tuple...), map(T, le_sigs...) end function widget{T <: Real}( slider_value::Signal{T}, window; text_scale = 4mm, - numberwidth = 5, range = range_default(T), kw_args... - ) - @materialize mouse_buttons_pressed, mouseposition = window.inputs - startvalue = value(slider_value) - slider_value_str = map(printforslider, slider_value) - vizz = visualize( - slider_value_str; relative_scale=text_scale, + numberwidth = 5, range = range_default(T), color = RGBA{Float32}(0.1, 0.1, 0.1), glow_color = RGBA{Float32}(0.97, 0.97, 0.97), stroke_color = RGBA{Float32}(0.97, 0.97, 0.97), - scale_primitive = true, kw_args... ) - bb = value(boundingbox(vizz)) + @materialize mouse_buttons_pressed, mouseposition = window.inputs + startvalue = value(slider_value) + slider_value_str = map(printforslider, slider_value) + slider_robj = visualize( + slider_value_str; + relative_scale = text_scale, + color = color, glow_color = glow_color, stroke_color = stroke_color, + scale_primitive = true, + kw_args... + ).children[] + bb = value(boundingbox(slider_robj)) mini, maxi = minimum(bb)-5f0, widths(bb)+10f0 - bb_rect = SimpleRectangle{Float32}(mini[1],mini[2], maxi[1], maxi[2]) - bb_vizz = visualize( + bb_rect = SimpleRectangle{Float32}(mini[1], mini[2], maxi[1], maxi[2]) + bb_vizz = visualize( bb_rect; - color=RGBA{Float32}(0.97, 0.97, 0.97, 0.4), - offset=Vec2f0(0), kw_args... + color = RGBA{Float32}(0.97, 0.97, 0.97, 0.4), + offset = Vec2f0(0), kw_args... ).children[] - slider_robj = vizz.children[] # current tuple of renderobject id and index into the gpu array m2id = GLWindow.mouse2id(window) ids = (slider_robj.id, bb_vizz.id) diff --git a/src/texture_atlas.jl b/src/texture_atlas.jl index eae53b6..04093e9 100644 --- a/src/texture_atlas.jl +++ b/src/texture_atlas.jl @@ -190,7 +190,7 @@ function sdistancefield(img, downsample = 8, pad = 8*downsample) in_or_out = Array(Bool, w, h) @inbounds for i=1:w, j=1:h - x, y = i-pad, j-pad + x, y = i - pad, j - pad in_or_out[i,j] = checkbounds(Bool, img, x, y) && img[x,y] > 0.5*255 end yres, xres = div(w, downsample), div(h, downsample) @@ -198,13 +198,15 @@ function sdistancefield(img, downsample = 8, pad = 8*downsample) map(Float16, sd) end +const _downsample_rate = parse(Int, get(ENV, "GLVISUALIZE_DOWNSAMPLE_RATE", "5")) + function GLAbstraction.render(atlas::TextureAtlas, glyph::Char, font) #select_font_face(cc, font) if glyph == '\n' # don't render newline glyph = ' ' end - downsample = 5 - pad = 8 + downsample = _downsample_rate + pad = 20 bitmap, extent = renderface(font, glyph, (50*downsample, 50*downsample)) sd = sdistancefield(bitmap, downsample, downsample*pad) extent = extent ./ Vec2f0(downsample) diff --git a/src/visualize/particles.jl b/src/visualize/particles.jl index 0c80a8f..aa504f7 100644 --- a/src/visualize/particles.jl +++ b/src/visualize/particles.jl @@ -515,7 +515,7 @@ function _default{S <: AbstractString}(main::TOrSignal{S}, s::Style, data::Dict) uv_offset_width = const_lift(main) do str Vec4f0[glyph_uv_width!(atlas, c, font) for c = str] end - scale = const_lift(main, relative_scale) do str, s + scale = const_lift(main, relative_scale) do str, s Vec2f0[glyph_scale!(atlas, c, font, s) for c = str] end end diff --git a/src/visualize/text.jl b/src/visualize/text.jl index d0fa957..90afa84 100644 --- a/src/visualize/text.jl +++ b/src/visualize/text.jl @@ -3,7 +3,7 @@ immutable SpriteElem{N,T} offset::Vec{2,T} uv_offset_width::Vec{4,T} position::Point{N, T} - color::RGBA{Float32} + color::RGBA{N0f8} scale::Vec{2,T} end type Text @@ -202,7 +202,7 @@ texttexttexttext\n texttexttext\n =# function down_after_newline(text, current_position) - i = current_position + i = current_position pnl = previous_newline(text, i) nnl = next_newline(text, i) nl_distance = i-pnl # distance from previous newline diff --git a/test/runtests.jl b/test/runtests.jl index 7f8dcdc..4fd252d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,3 @@ -include("micro.jl") - function isheadless() get(ENV, "TRAVIS", "") == "true" || get(ENV, "APPVEYOR", "") == "true" || @@ -7,6 +5,7 @@ function isheadless() end if isheadless() + ENV["GLVISUALIZE_DOWNSAMPLE_RATE"] = 1 # need this branch for better coverage report! cd(Pkg.dir("GLAbstraction")) do run(`git fetch origin`) diff --git a/test/test_interactive.jl b/test/test_interactive.jl index a475594..43a555b 100644 --- a/test/test_interactive.jl +++ b/test/test_interactive.jl @@ -1,4 +1,5 @@ using GLVisualize +include("micro.jl") include(GLVisualize.dir("examples", "ExampleRunner.jl")) using ExampleRunner import ExampleRunner: flatten_paths diff --git a/test/test_static.jl b/test/test_static.jl index fa7fa24..0654dbf 100644 --- a/test/test_static.jl +++ b/test/test_static.jl @@ -1,5 +1,10 @@ using GLVisualize +include("micro.jl") include(GLVisualize.dir("examples", "ExampleRunner.jl")) + + +@show GLVisualize._downsample_rate + using ExampleRunner importall ExampleRunner using GLAbstraction, GLWindow, Colors @@ -18,7 +23,7 @@ function create_mosaic(io, folder, width = 150) images = filter(x-> endswith(x, ".jpg"), readdir(folder)) for im in images println(io, """$(im) + alt="$(im)" width=$(width)px/> """) end end @@ -124,7 +129,7 @@ if recording println(io, "### Failures:") for (k, dict) in failures println(io, "file: $k") - Base.showerror(io, "$(dict[:exception])") + Base.showerror(io, dict[:exception]) println("\n") end else