使用opengl為golang寫一個簡易版UI框架

go語言目前沒有官方版本的UI庫,可能是谷歌將go語言定位成服務(wù)器語言,認(rèn)為沒有必要給go寫一套u(yù)i框架,不過在github上有不少大牛為go寫了UI庫效果都還不錯.
本人之前是做Android開發(fā)的,有幸接觸Android的Gallery2源碼,這套代碼是用opengl繪制的整個相冊界面;自己就想是否能用類似的方式為go寫一個ui庫呢,慶幸的是,在github上有很多go和opengl的綁定框架,那就站在巨人的肩膀上,說做就做吧!
項(xiàng)目地址https://github.com/tenny1225/go-ui
首先說說整個架構(gòu)吧,因?yàn)楸救酥笆亲鯝ndroid開發(fā)的,所以很多地方有模仿Android的痕跡.


對于opengl的綁定這里使用了github.com/go-gl這個庫,具體介紹可以去github查看.

  • canvas進(jìn)行繪制的控制操作
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這個庫實(shí)現(xiàn)的,這里提取出接口,可以根據(jù)不同的繪制方式進(jìn)行擴(kuò)展.

  • texture進(jìn)行opengl的繪制
    texture主要是接受canvas層傳遞過來的像素數(shù)據(jù),進(jìn)行紋理和坐標(biāo)的轉(zhuǎn)換后,最后進(jìn)行繪制.
func (t *Texture) Draw(c ZCanvas, x, y float64) {
    tran := c.GetTranslate()
    if tran != nil {
        x = x + tran.X
        y = y + tran.Y
    }
       //獲取窗口真實(shí)寬高
    winWidth, winHeight := (t.ctx.Value("window").(*glfw.Window)).GetSize()
       //將紋理左上角坐標(biāo)轉(zhuǎn)換成opengl坐標(biāo)
    x, y = AppCoordinate2OpenGL(winWidth, winHeight, x, y)
       //將紋理寬高轉(zhuǎn)換成相對opengl坐標(biāo)寬高
    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)
        //左下角坐標(biāo)
    gl.Normal3f(float32(x), float32(y-h), 0) //
    gl.TexCoord2f(1, 1)
        //右下角坐標(biāo)
    gl.Vertex3f(float32(x+w), float32(y-h), 0) //

    gl.TexCoord2f(1, 0)
        //右上角坐標(biāo)
    gl.Vertex3f(float32(x+w), float32(y), 0) //
    gl.TexCoord2f(0, 0)
       //左上角坐標(biāo)
    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
}

這里主要實(shí)現(xiàn)一系列方法,對view進(jìn)行子view的添加和移除,測量和擺放等操作.通過Draw(canvas ZCanvas)方法傳遞過來的canvas接口,可以對view進(jìn)行自定義操作.

  • 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等方法,在頁面切換是進(jìn)行生命周期的調(diào)用

  • window主要是用來組織Page,例如界面的跳轉(zhuǎn)切換
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主要調(diào)用了go-gl庫進(jìn)行窗口的創(chuàng)建,根據(jù)當(dāng)前的界面,進(jìn)行不同生命周期的調(diào)用操作等.

下面是一個例子,主要是繪制一個綠色的按鈕,不過還沒有點(diǎn)擊事件,因?yàn)檫€未把input集成進(jìn)來

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結(jié)構(gòu)體
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結(jié)構(gòu)體
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()
}

效果圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容