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

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()
}

效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容