JuliaGraphics / Luxor.jl

Simple drawings using vector graphics; Cairo "for tourists!"

Home Page:http://juliagraphics.github.io/Luxor.jl/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Another successful experiment

oheil opened this issue · comments

commented

This opens two windows which can be used as drawing canvas independently:

(@v1.7) pkg> add https://github.com/oheil/Luxor.jl#multi_drawing
using MiniFB, Luxor, Colors

WIDTH=800
HEIGHT=600

mutable struct MfbState
	state::mfb_update_state
	mousePressed::Int	
end

stateArray=[
	MfbState( MiniFB.STATE_OK, 0 ),
	MfbState( MiniFB.STATE_OK, 0 ),
	]

function windowUpdateTask(window,windowIndex,buffer,stateArray)
    stateArray[windowIndex].state=mfb_update(window,buffer)
	while stateArray[windowIndex].state == MiniFB.STATE_OK
		stateArray[windowIndex].state=mfb_update(window,buffer)
        sleep(1.0/120.0)
    end
	println("\nWindow closed\n")
end

window1 = mfb_open_ex("MiniFB", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE)
buffer1 = zeros(ARGB32, WIDTH, HEIGHT)
d1=Drawing(buffer1)
@async windowUpdateTask(window1,1,buffer1,stateArray)

window2 = mfb_open_ex("MiniFB", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE)
buffer2 = zeros(ARGB32, WIDTH, HEIGHT)
Luxor.CURRENTDRAWINGINDEX[1]=2
d2=Drawing(buffer2)
@async windowUpdateTask(window2,2,buffer2,stateArray)

origin()
setcolor("red")
circle(0,0,150,:fill)

Luxor.CURRENTDRAWINGINDEX[1]=1
origin()
setcolor("green")
circle(0,0,150,:fill)

Just a proof of concept.
Except for a ignorable error message from Cairo this works pretty well.

You're doing interesting things here... :)

What's ERROR: UndefVarError: CURRENTDRAWINGINDEX not defined mean though? Is this to have multiple drawings on the go at once? Sounds cool...

commented

You need to add my branch "multi_drawing" to make it work. First line in example.

Yes, I thought I was accessing that too...

Pkg.add(path="https://github.com/oheil/Luxor.jl#multi_drawing")

...

ERROR: UndefVarError: CURRENTDRAWINGINDEX not defined
Stacktrace:
 [1] getproperty(x::Module, f::Symbol)
   @ Base ./Base.jl:31
 [2] top-level scope
   @ Untitled-1:41

(@MiniFB) pkg> st
Status `~/.julia/environments/MiniFB/Project.toml`
  [5ae59095] Colors v0.12.8
  [ae8d54c2] Luxor v3.5.0 `https://github.com/oheil/Luxor.jl#multi_drawing#master`
  [cc649173] MiniFB v0.1.1

(I get the two windows OK...)

commented

The two windows are easy. The independent Luxor/Cairo surfaces are the thing ;-)
This looks strange: ...oheil/Luxor.jl#multi_drawing#master
I tried from scratch with the following, and it works (on windows):

(@v1.7) pkg> activate test
(test) pkg> add MiniFB, Colors
(test) pkg> add https://github.com/oheil/Luxor.jl#multi_drawing
(test) pkg> st
      Status `C:\Users\Oli\test\Project.toml`
  [5ae59095] Colors v0.12.8
  [ae8d54c2] Luxor v3.5.0 `https://github.com/oheil/Luxor.jl#multi_drawing`
  [cc649173] MiniFB v0.1.1

julia> using MiniFB, Luxor, Colors
...

and the rest of the code.

commented

Here is a video of what is possible with that:

luxor.mp4

It's not a virtual larger surface splitted over two windows (but this can be created), it's just two independent surfaces on two windows and the code calculates the lines accordingly.

This is the code (needs above branch again):


using MiniFB, Luxor, Colors

WIDTH=800
HEIGHT=600

mutable struct MfbState
    state::mfb_update_state
    mousePressed::Int   
end

stateArray=[
    MfbState( MiniFB.STATE_OK, 0 ),
    MfbState( MiniFB.STATE_OK, 0 ),
    ]

function windowUpdateTask(window,windowIndex,buffer,stateArray)
    stateArray[windowIndex].state=mfb_update(window,buffer)
    while stateArray[windowIndex].state == MiniFB.STATE_OK
        stateArray[windowIndex].state=mfb_update(window,buffer)
        sleep(1.0/120.0)
    end
    println("\nWindow closed\n")
end

window1 = mfb_open_ex("Left", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE)
buffer1 = zeros(ARGB32, WIDTH, HEIGHT)
d1=Drawing(buffer1)
@async windowUpdateTask(window1,1,buffer1,stateArray)

window2 = mfb_open_ex("Right", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE)
buffer2 = zeros(ARGB32, WIDTH, HEIGHT)
Luxor.CURRENTDRAWINGINDEX[1]=2
d2=Drawing(buffer2)
@async windowUpdateTask(window2,2,buffer2,stateArray)


mutable struct Ball
    position::Point
    velocity::Point
end
function stick(w, h)
    channel = Channel(10)
    @async while true
        kb = readline(stdin)
        if contains(kb, "q")
            put!(channel, 1)
            break
        end
    end
    colors=[rand(1:255),rand(1:255),rand(1:255)]
    newcolors=[rand(1:255),rand(1:255),rand(1:255)]
    c=ARGB(colors[1]/255,colors[2]/255,colors[3]/255,1.0)
    balls=[Ball( rand(BoundingBox(Point(-w/2, -h/2), Point(w/2, h/2))), rand(BoundingBox(Point(-10, -10), Point(10, 10))) ) for _ in 1:2] 
    while true
        if colors == newcolors
            newcolors=[rand(1:255),rand(1:255),rand(1:255)]
        end
        for (index,(col,newcol)) in enumerate(zip(colors,newcolors))
            if col != newcol
                col > newcol ? col-=1 : col+=1
                colors[index]=col
            end
        end
        c=ARGB(colors[1]/255,colors[2]/255,colors[3]/255,1.0)
        for ball in balls
            if !(-w/2 < ball.position.x < w/2)
                ball.velocity = Point(-ball.velocity.x,ball.velocity.y)
            end
            if !(-h/2 < ball.position.y < h/2)
                ball.velocity = Point(ball.velocity.x,-ball.velocity.y)
            end
            ball.position = ball.position + ball.velocity
        end
        
		Luxor.CURRENTDRAWINGINDEX[1]=1
        background(0,0,0,0.05)
        setcolor(c)
		Luxor.CURRENTDRAWINGINDEX[1]=2
        background(0,0,0,0.05)
        setcolor(c)

        pos1=balls[1].position
        pos2=balls[2].position
		pos3=pos2
		if ( pos1.x < 0.0 && pos2.x > 0.0 ) || ( pos1.x > 0.0 && pos2.x < 0.0 )
			m=(pos1.y-pos2.y)/(pos1.x-pos2.x)
			posmx=0.0
			posmy=pos1.y-m*pos1.x
			pos3=Point(posmx,posmy)
		end
		if ( pos1.x < 0.0 && pos2.x > 0.0 )
			Luxor.CURRENTDRAWINGINDEX[1]=1
			line(Point(pos1.x+w/4,pos1.y),Point(pos3.x+w/4,pos3.y),:stroke)
			Luxor.CURRENTDRAWINGINDEX[1]=2
			line(Point(pos3.x-w/4,pos3.y),Point(pos2.x-w/4,pos2.y),:stroke)
		end
		if ( pos1.x > 0.0 && pos2.x < 0.0 )
			Luxor.CURRENTDRAWINGINDEX[1]=1
			line(Point(pos3.x+w/4,pos3.y),Point(pos2.x+w/4,pos2.y),:stroke)
			Luxor.CURRENTDRAWINGINDEX[1]=2
			line(Point(pos1.x-w/4,pos1.y),Point(pos3.x-w/4,pos3.y),:stroke)
		end
		if ( pos1.x < 0.0 && pos2.x < 0.0 )
			Luxor.CURRENTDRAWINGINDEX[1]=1
			line(Point(pos1.x+w/4,pos1.y),Point(pos2.x+w/4,pos2.y),:stroke)
		end
		if ( pos1.x > 0.0 && pos2.x > 0.0 )
			Luxor.CURRENTDRAWINGINDEX[1]=2
			line(Point(pos1.x-w/4,pos1.y),Point(pos2.x-w/4,pos2.y),:stroke)
		end

        sleep(1.0/120.0)
        if isready(channel)
            break
        end
    end
end
Luxor.CURRENTDRAWINGINDEX[1]=1
origin()
Luxor.CURRENTDRAWINGINDEX[1]=2
origin()
stick(2*WIDTH, HEIGHT)
commented

The code changes in this branch
https://github.com/oheil/Luxor.jl/tree/multi_drawing
is not breaking any user code. All tests are running fine.

Yet it's not a good interface for users of Luxor, but I would like to evolve this into something which could be brought into your official Luxor package. But I am not quite sure in which way I should do this. Currently it's more like a hidden feature for experimental people like me.

If you like this I would be happy to discuss the right way.

The long goal for all this playing around could be some GUI package in Julia based on MiniFB + Luxor(Cairo).

Very cool stuff!

When I started Luxor (2014!) it was because I simply wanted to draw coloured shapes on a PNG/SVG 2D canvas using Julia 🤣 - Cairo.jl was too verbose, Compose.jl was a bit weird, the plotting packages weren't very flexible... Luxor was merely some syntactic sugar to simplify calls to Cairo - the original tagline was something like "A sugary-sweet interface to Cairo.jl!". Since then Luxor has got a bit, ... er ... bloated?; image buffers, snapshots, SVG surfaces, etc.. It wasn't well-designed to start with, and adding lots of extra pieces hasn't necessarily made it better, In the meantime, Julia has grown to offer powerful interactive 2D/3D layout features in packages like Makie and Pluto, and big capable geometry libraries in Meshes and all the stuff in JuliaGeometry.

Perhaps, with hindsight, there was a demand for a more carefully designed interface to Cairo, taking into account multiple drawings, windows, interactive widgets, updating screen buffers, etc.. But now I wonder whether it would be easier to take whatever parts of the Luxor user interface are useful and interface them to a better-designed graphics system, similar to something like CairoMakie perhaps. (I read that Manim (as used by 3Blue1Brown) switched from using Cairo to OpenGL - I wonder whether Javis.jl should do the same.)

All of which is to say that Luxor.jl is intended to be simple and easy to use for new Julia users (and easy to maintain!) rather than powerful or versatile or an engine designed for interfacing with other libraries. It's possible - but not always easy - to make it do all those things, and that's a challenge!

commented

Luxor is perfectly fine for my needs and easy enough to use, except for a few minor things. I don't think it's bloated and the design is appropriate as far as I can tell. I prefer the as-simple-as-needed design principle, not the fancy-ingenious-fit-for-all-future-scenarios designs, nobody can understand :-) There has been a discussion about Julia native GUIs on discord and together with small Julia executables, these are the things I am missing desperately for Julia. I did some further research on MiniFB and it's probably not enough for a GUI system as a platform independent window manager. So, for now this little research project of mine is on pause again, I think. Actually I came from the wish to express some creativity with Julia, stumbled over the idea of some future GUI system, and now I am back to express myself using Luxor or whatever suits my needs. This non-issue can be closed.

Thanks for the contributions anyway. I hope your creativity will continue to be expressed!