美文网首页
使用opengl为golang写一个简易版UI框架

使用opengl为golang写一个简易版UI框架

作者: Tenny1225 | 来源:发表于2018-04-23 11:29 被阅读978次

    go语言目前没有官方版本的UI库,可能是谷歌将go语言定位成服务器语言,认为没有必要给go写一套ui框架,不过在github上有不少大牛为go写了UI库效果都还不错.
    本人之前是做Android开发的,有幸接触Android的Gallery2源码,这套代码是用opengl绘制的整个相册界面;自己就想是否能用类似的方式为go写一个ui库呢,庆幸的是,在github上有很多go和opengl的绑定框架,那就站在巨人的肩膀上,说做就做吧!
    项目地址https://github.com/tenny1225/go-ui
    首先说说整个架构吧,因为本人之前是做Android开发的,所以很多地方有模仿Android的痕迹.


    对于opengl的绑定这里使用了github.com/go-gl这个库,具体介绍可以去github查看.
    • canvas进行绘制的控制操作
    type ZCanvas interface {
        DrawCircle(dx, dy, radius float64, p ZPaint)
        DrawLine(x1, y1, x2, y2 float64, p ZPaint)
        DrawRect(x1, y1, x2, y2, rounCorner float64, p ZPaint)
        DrawImage(x, y float64, img image.Image)
    
        SetTranslate(x, y float64)
        SetScale(sx, sy float64)
        SetRotate(angle, dx, dy, x, y, z float64)
    
        Save()
        Restore()
    
        SetAlpha(a float64)
    
        GetTranslate() *ZTranslate
        GetScale() *ZScale
        GetRotate() *ZRotate
        GetAlpha() float64
    }
    

    这里主要定义了一些简单的绘制控制操作,具体绘制方式是通过github.com/fogleman/gg这个库实现的,这里提取出接口,可以根据不同的绘制方式进行扩展.

    • texture进行opengl的绘制
      texture主要是接受canvas层传递过来的像素数据,进行纹理和坐标的转换后,最后进行绘制.
    func (t *Texture) Draw(c ZCanvas, x, y float64) {
        tran := c.GetTranslate()
        if tran != nil {
            x = x + tran.X
            y = y + tran.Y
        }
           //获取窗口真实宽高
        winWidth, winHeight := (t.ctx.Value("window").(*glfw.Window)).GetSize()
           //将纹理左上角坐标转换成opengl坐标
        x, y = AppCoordinate2OpenGL(winWidth, winHeight, x, y)
           //将纹理宽高转换成相对opengl坐标宽高
        w, h := AppWidthHeight2OpenGL(winWidth, winHeight, (float64(t.Width)), (float64(t.Height)))
           //判断是否需要绘制透明度
        if t.IsAlpha {
            gl.Enable(gl.BLEND);
            gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        }
    
        gl.MatrixMode(gl.MODELVIEW)
        gl.LoadIdentity()
    
        gl.BindTexture(gl.TEXTURE_2D, t.texture)
        gl.LineWidth(0)
        gl.PointSize(0)
        gl.Begin(gl.QUADS)
            //左下角坐标
        gl.Normal3f(float32(x), float32(y-h), 0) //
        gl.TexCoord2f(1, 1)
            //右下角坐标
        gl.Vertex3f(float32(x+w), float32(y-h), 0) //
    
        gl.TexCoord2f(1, 0)
            //右上角坐标
        gl.Vertex3f(float32(x+w), float32(y), 0) //
        gl.TexCoord2f(0, 0)
           //左上角坐标
        gl.Vertex3f(float32(x), float32(y), 0) //
        gl.TexCoord2f(0, 1)
        gl.Vertex3f(float32(x), float32(y-h), 0) //
    
        gl.End()
    
        gl.PopMatrix()
    
    }
    
    • view是界面控件的最小单位
    type Viewer interface {
        Init()
        Measure(w, h int)
        SetMeasureSize(w, h int)
        GetMeasureSize() (w, h int)
        GetBounds() *Rect
        GetChildren() []Viewer
        GetChildrenSize() int
        AddChild(v Viewer)
        RemoveChild(v Viewer)
        Layout(x, y, w, h int)
        Draw(canvas ZCanvas)
        Recycle()
    
        setParent(v Viewer)
        getParent() Viewer
    }
    

    这里主要实现一系列方法,对view进行子view的添加和移除,测量和摆放等操作.通过Draw(canvas ZCanvas)方法传递过来的canvas接口,可以对view进行自定义操作.

    • page类似Android的activity,主要管理每个界面的生命周期
    type Pager interface {
        init(panel *ZPanel, ctx *ZContext)
        setBundle(b map[string]interface{})
    
        getBundle() map[string]interface{}
        GetSate() PageState
        GetContentView() Viewer
        SetContentView(v Viewer)
        Create(bundle map[string]interface{})
        Resume()
        Pause()
        Destroy()
        Finish()
        StartPage(p Pager)
    }
    

    这里主要定义了create,resume,pause,destroy等方法,在页面切换是进行生命周期的调用

    • window主要是用来组织Page,例如界面的跳转切换
    func (w *ZPanel) Run() {
        if err := glfw.Init(); err != nil {
            log.Fatalln("failed to initialize glfw:", err)
        }
        defer glfw.Terminate()
    
        glfw.WindowHint(glfw.Resizable, glfw.False)
        glfw.WindowHint(glfw.ContextVersionMajor, 2)
        glfw.WindowHint(glfw.ContextVersionMinor, 1)
        var err error
        w.Window, err = glfw.CreateWindow(int(w.Width), int(w.Height), w.Title, nil, nil)
        if err != nil {
            panic(err)
        }
        w.Context = &ZContext{}
        w.Context.Context = context.WithValue(context.Background(), "window", w.Window)
        w.Canvas = NewGL2Canvas(w.Context)
        w.Window.MakeContextCurrent()
        if err := gl.Init(); err != nil {
            panic(err)
        }
    
        gl.ClearColor(0.5, 0.5, 0.5, 0.0)
        gl.ClearDepth(1)
        gl.DepthFunc(gl.LEQUAL)
        ambient := []float32{0.5, 0.5, 0.5, 1}
        diffuse := []float32{1, 1, 1, 1}
        lightPosition := []float32{-5, 5, 10, 0}
        gl.Lightfv(gl.LIGHT0, gl.AMBIENT, &ambient[0])
        gl.Lightfv(gl.LIGHT0, gl.DIFFUSE, &diffuse[0])
        gl.Lightfv(gl.LIGHT0, gl.POSITION, &lightPosition[0])
        gl.Enable(gl.LIGHT0)
    
        gl.MatrixMode(gl.PROJECTION)
        gl.LoadIdentity()
        //gl.Frustum(-1, 1, -1, 1, 1.0, 5.0)
        gl.Ortho(-3, 3, -3, 3, -3.0, 100.0)
        gl.MatrixMode(gl.MODELVIEW)
        gl.LoadIdentity()
    
        for !w.Window.ShouldClose() {
    
            gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
            gl.ClearColor(1, 1, 1, 0)
    
            if len(w.Pages) > 0 {
    
                p := w.Pages[len(w.Pages)-1]
                if p.GetSate() == PageStateInit {
                    p.Create(p.getBundle())
                }
                rootView := p.GetContentView()
                if rootView != nil {
                    b := rootView.GetBounds()
                    c := w.Canvas
                    c.Save()
                    c.SetTranslate(float64(b.X), float64(b.Y))
                    rootView.Draw(c)
                    c.Restore()
                }
                if p.GetSate() == PageStateCreated {
                    p.Resume()
                } else if p.GetSate() == PageStatePaused {
                    p.Resume()
                }
            }
    
            w.Window.SwapBuffers()
            glfw.PollEvents()
        }
    }
    
    

    window主要调用了go-gl库进行窗口的创建,根据当前的界面,进行不同生命周期的调用操作等.

    下面是一个例子,主要是绘制一个绿色的按钮,不过还没有点击事件,因为还未把input集成进来

    package main
    
    import (
        ."GO-UI/content"
        "image"
        "github.com/fogleman/gg"
        "image/color"
    )
    
    func main() {
        p := NewZPanel(500, 500, "test");
        page:=&TestPage{}
        p.StartPage(page)
        p.Run()
    }
    
    //自定义的TestButtonView,只需要组合View结构体
    type TestButtonView struct {
        View
    }
    
    func (this *TestButtonView) Draw(canvas ZCanvas) {
        this.View.Draw(canvas)
        rect:=this.GetBounds()
        paint:=NewPaint()
        paint.Color = color.RGBA{0,225,225,255}
        canvas.DrawRect(0,0,float64(rect.Width),float64(rect.Height),10,paint)
    
        img:=image.NewRGBA(image.Rect(0,0,rect.Width,rect.Height))
        c:=gg.NewContextForRGBA(img)
        c.SetColor(color.White)
        c.DrawStringAnchored("button1",float64(rect.Width/2),float64(rect.Height/2),0.5,0.5)
        c.Fill()
        canvas.DrawImage(0,0,img)
    }
    //自定义的TestPage,只需要组合Page结构体
    type TestPage struct {
        Page
    }
    
    func (this *TestPage) Create(bundle map[string]interface{}) {
        this.Page.Create(bundle)
        v := &TestButtonView{}
        v.Init()
        v.Layout(20, 20, 130, 60)
        this.SetContentView(v)
    
    }
    func (this *TestPage) Resume() {
        this.Page.Resume()
    }
    
    
    效果图

    相关文章

      网友评论

          本文标题:使用opengl为golang写一个简易版UI框架

          本文链接:https://www.haomeiwen.com/subject/mkeklftx.html