最近一直想开发一些桌面(Windows为主,也可以考虑macOS或者Linux)的应用。虽然Go不是开发UI应用程序时的首选语言,但该语言的简单性和健壮性仍然使它成为我的首选语言。是的,这是我的锤子……但它是一把非常漂亮的锤子。之前,我用Ebiten的Go图形库和孩子们一起写了一些游戏。然而,对于一般的“应用程序”开发(即需要按钮、菜单等“小部件”),Ebiten并不是很适合。所以我一直在寻找一个我可以使用的GoUI库。然而大多数使用cgo,但我真的希望我的应用程序是纯Go的。这纯粹是个人喜好,我不知道使用cgo代替纯Go会有什么效果。如果我要使用cgo,我认为选择的UI库肯定是Fyne[1]。Fyne看起来是一个非常全面的框架,如果你不介意cgo,我绝对会推荐看一下Fyne。还有一些其他图书馆,但没有一个对我有吸引力。他们说你永远不应该编写自己的安全代码,我相信你永远不应该编写自己的UI库。但是我忽略了这个建议...所以,我写了一个GoUI[2]!!这纯粹是那些“从头开始”的项目之一。现在还处于早期阶段,但UI库(实际上由Ebiten呈现)的基础正在慢慢融合。在进入下面的细节之前,我想声明我认为编程在CLI中达到顶峰。其次,我不是UI程序员……写这篇文章是我个人的兴趣。GoUI的基本思想是两种图形元素。一个是可以包含其他面板或小部件的面板。另一个是小部件,它们是基本的UI元素(按钮、文本输入等)。我们目前使用的面板类型是HPanel(水平添加)和VPanel(我让你猜)。从技术上讲,我们确实有其他面板,例如工具栏,但这实际上只是一个HPanel,需要一些自定义工作。总的来说,如果我可以结合其他现有的面板/小工具来构建一些“新的”东西,那么我会去做。如果我需要优化或与我已有的不一致,那么我将做一些全新的事情。我们目前拥有的小部件是:ImageButton:(应用程序提供的点击/未点击图像)。TextButton:一个基本的彩色矩形,里面有你想要的任何文本。Checkbox:同TextButton,但旁边有个小方框,可以勾选。EmptySpace:完全在我的控制之下。用于强制其他小部件之间的空间。添加适当的填充后,此填充可能会消失。Label:文本标签,不可输入。文本输入:文本输入框。RadioButtonGroup:这是一个面板,其中包含一个vpanel或hpanel(取决于标志),然后是其中的一些复选框。复选框用常规单选按钮替换图像(带有勾号)。这是重用现有小部件的一个很好的例子。如果事实证明我需要充分修改复选框以使其不适合用作单选按钮,我将不得不放入一个实际的单选按钮。但就目前而言,它工作正常。我还没有完成菜单、模态窗口等,但就像我说的……现在还处于早期阶段。现在,让我们尝试一个超级简单的演示。packagemainimport("github.com/hajimehoshi/ebiten""github.com/kpfaulkner/goui/pkg""github.com/kpfaulkner/goui/pkg/widgets"log"github.com/sirupsen/logrus""图像/颜色")typeMyAppstruct{windowpkg.Window}funcNewMyApp()*MyApp{a:=MyApp{}a.window=pkg.NewWindow(800,600,"testapp",false,false)return&a}func(m*MyApp)SetupUI()error{vPanel:=widgets.NewVPanel("mainvpanel",&color.RGBA{0,0,0,0xff})m.window.AddPanel(vPanel)button1:=widgets.NewTextButton("textbutton1","mybutton1",true,0,0,nil,nil,nil,nil)vPanel.AddWidget(button1)returnnil}func(m*MyApp)Run()error{m.SetupUI()ebiten.SetRunnableInBackground(true)ebiten.SetWindowResizable(true)m.window.MainLoop()returnnil}funcmain(){log.SetLevel(log.DebugLevel)app:=NewMyApp()app.Run()}我们来解释一下上面的代码。首先,程序的核心还是直接调用Ebiten。这些还没有打包。因此,正如您将在main和Run函数中看到的那样,我们基本上创建了MyApp结构的实例,然后调用SetupUI,设置一些Ebiten标志,然后调用MainLoop。NewMyApp函数调用pkg.NewWindow函数。这是应用程序的主窗口。一旦添加了模式/其他窗口,这可能会改变,但现在这将创建给定大小的主UI窗口。SetupUI是需要注意的地方。我们做的第一件事是创建一个VPanel。请记住,VPanel垂直堆叠小部件。我们在主窗口中添加一个vPanel。实际上(目前)我们应该只向主窗口添加1个面板,其他所有内容都应该适合该面板。所以在这种情况下,我们创建button1(新TextButton)并将其添加到vPanel。花时间学习和理解上述UI技巧。接下来,让我们做一些更有趣的事情。假设我们想要在按下按钮时做出响应。创建TextButton的行是:button1:=widgets.NewTextButton("textbutton1","mybutton1",true,0,0,nil,nil,nil,nil)所有的细节都没有描述,但最后一个参数是带func(eventIEvent)错误签名的事件处理程序。因此,如果我们创建一个具有该签名的方法并将NewTextButton作为最后一个参数传递给它。func(m*MyApp)ButtonAction1(eventevents.IEvent)error{log.Debugf("Mybutton1action1!!!")returnnil}然后我们修改按钮创建为button1:=widgets.NewTextButton("textbutton1","mybutton1",true,0,0,nil,nil,nil,m.ButtonAction1)现在,当单击按钮时,将调用ButtonAction1函数,我们可以触发任何我们想要的功能。是不是很简单。现在,如果我想在按钮旁边放点东西怎么办?我们在这里要做的是先创建一个HPanel并将其放入VPanel中。然后,将按钮添加到HPanel。如果我们这样做,我们将得到这样的代码:func(m*MyApp)SetupUI()error{vPanel:=widgets.NewVPanel("mainvpanel",&color.RGBA{0,0,0,0xff})m。窗户。AddPanel(vPanel)hPanel:=widgets.NewHPanel("hpanel1",&color.RGBA{0,100,0,255})vPanel.AddWidget(hPanel)button1:=widgets.NewTextButton("textbutton1","mybutton1",true,0,0,nil,nil,nil,m.ButtonAction1)hPanel.AddWidget(button1)returnnil}在视觉上,什么都不会改变。我们仍然只显示1个小部件。现在,如果我们向同一个HPanel添加一个复选框会怎么样?func(m*MyApp)SetupUI()error{vPanel:=widgets.NewVPanel("mainvpanel",&color.RGBA{0,0,0,0xff})m.window.AddPanel(vPanel)hPanel:=widgets.NewHPanel("hpanel1",&color.RGBA{0,100,0,255})button1:=widgets.NewTextButton("textbutton1","mybutton1",true,0,0,nil,nil,nil,m.ButtonAction1)hPanel.AddWidget(button1)cb1:=widgets.NewCheckBox("mycheckbox1","checkmeplease","","",nil)hPanel.AddWidget(cb1)vPanel.AddWidget(hPanel)returnnil}所以,和以前完全一样,但是有了这两个额外的东西:创建复选框,然后将其添加到hPanel。现在UI看起来像这样:现在不用担心这里的间距。现在让我们在TextButton下面添加一个ImageButton。这意味着我们将向vPanel添加第二个项目(第一个项目是hPanel)(imageButton)效果是这样的:此时,我们有2张图片(截图中只显示了一张)。一种是按下按钮,一种是未按下(仅更改阴影)。知道了。好了,介绍到此为止。UI库虽然不完善,但可以实现GUI的基本功能。再放一遍库的地址:https://github.com/kpfaulkner/goui。
