Как создавать легкие платформонезависимые приложения на Go — без JS и BS

Как создавать легкие платформонезависимые приложения на Go — без JS и BS

https://t.me/Golang_google

C помощью Go можно создавать как платформонезависимые приложения, так и настольные и мобильные.

На динамичном рынке, где срок внедрения ценнее поставки полностью готового продукта, инструменты для создания платформонезависимых приложений все популярнее.

С ними сокращается не только время до вывода на рынок, но и стоимость разработки: приложения создаются компаниями лишь раз, а запускаются везде.

Но что, если вы не крупная компания, цель которой — экономия денег, а разработчик Go с идеей создать запускаемый в любой ОС продукт с минимальным функционалом?

Возвращаться к JavaScript или изучать инструменты клиентских приложений JavaScript типа Vue и React?

Ни в коем случае: когда есть Go, не нужно ни это, ни чего-либо другое!

Благодаря сторонней библиотеке Gio платформонезависимые приложения для Linux, Windows, macOS, Android и iOS создаются полностью на Go без JS, HTML, CSS.

Итак, сделаем приложение GoGiggles с chatGPT для генерирования шуток о разработчиках Go. Почему именно Go? У них отличное чувство юмора. При этих словах разработчики Rust покинули чат.

Но сначала пара слов о Gio.

Что такое Gio?

Это библиотека с непосредственным режимом реализации графического интерфейса для создания легковесных приложений MacOs, Windows, Linux, FreeBSD, OpenBSD, Android, iOS и WebAssembly. Она и сама легковесна из-за малого числа зависимостей, проста в освоении и использовании.

В отличие от платформонезависимых фреймворков Electron и Wails с применением в них веб-технологий для интерфейса, приложения на Gio рисуются самой библиотекой, из-за чего меньше потребление памяти.

С момента выпуска Gio задействована в создании нескольких эффективных, производительных приложений, например легкого, платформонезависимого настольного криптокошелька Godcr для управления DCR-токенами и регулирования, а также Tailscale Android, Android-клиента с открытым исходным кодом для Tailscale — альтернативы ячеистой сети VPN.

Gio универсальна и не ограничивается этими сценариями. Благодаря надежному материальному пользовательскому интерфейсу и гибкой архитектуре на Gio создаются различные приложения под уникальные задачи и требования.

Что понадобится для создания приложения:

  • базовые знания Go;
  • Go 1.20;
  • ОС Windows, Linux или Mac.

Создание нового проекта Go

Сначала включаем модуль Go:

export GO111MODULE=on

А этими тремя командами:

mkdir go_giggles &&
cd go_giggles &&
go mod init go_giggles

создаем каталог go_giggles, переходим в него и создаем модуль Go go_giggles, настраивая в созданном каталоге новый проект.

Создав проект Go Giggles, добавим в его зависимости библиотеку Gio.

Установка Gio

Устанавливаем Gio:

go get gioui.org@latest

Этой командой добавляем Gio в файл go.mod и загружаем библиотеку в кеш модуля Go.

Создание первого приложения GioUI

Чтобы ознакомиться с методами, виджетами и функционалом Gio, создадим простое приложение Gio с приветственным сообщением, затем запустим приложение в ОС и добавим функционал chatGPT.

Сначала создаем в каталоге проекта файл main.go:

touch main.go

Добавляем в этот файл код:

package main

import (
 "image/color"
 "log"
 "os"

 "gioui.org/app"
 "gioui.org/font/gofont"
 "gioui.org/io/system"
 "gioui.org/layout"
 "gioui.org/op"
 "gioui.org/text"
 "gioui.org/widget/material"
)

var theme  = material.NewTheme(gofont.Collection()) 

func main() {
  go func() {
    w := app.NewWindow()
    err := run(w)
    if err != nil {
      log.Fatal(err)
    }
    os.Exit(0)
  }()
  app.Main()
}

func run(w *app.Window) error {
 var ops op.Ops
 for {
   e := <-w.Events()
   switch e := e.(type) {
     case system.DestroyEvent:
     return e.Err
     case system.FrameEvent:
     gtx := layout.NewContext(&ops, e)

     title := material.H1(theme, "Hi, I'm Giggles")
     maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255}
     title.Color = maroon
     title.Alignment = text.Middle
     title.Layout(gtx)

     e.Frame(gtx.Ops)
  }
 }
}

Пара незнакомых функций и методов? Сейчас разберемся.

Разбор кода

Создание окна

Каждому приложению с графическим интерфейсом обычно требуется минимум одно окно. Функцией main запускается цикл приложения для взаимодействия с ОС, в отдельной горутине инициируется логика окна:

func main() {
 go func() {
  w := app.NewWindow()
  err := run(w)
  if err != nil {
   log.Fatal(err)
  }
  os.Exit(0)
 }()
 app.Main()
}

В контексте окна выполняется логика конкретного приложения и обрабатываются любые ошибки, с завершением логики приложения процесс завершается.

Создание темы

Чтобы определить внешний вид приложения Gio, инициализируем тему для создания стилизованных виджетов. Этой строкой кода создаем новую тему с семейством шрифтов Go для всех текстовых элементов:

var theme = material.NewTheme(gofont.Collection())
...

Оно применяется в material.H1(theme, "Hi, I'm Giggles") функции run для создания стилизованного заголовка с текстом Hi, I'm Giggles.

Прослушивание событий

Этот блок кода — основной цикл событий окна для обработки в приложении Gio разных событий, таких как запросы перерисовки и удаление окна:

for {
 e := <-w.Events()
 switch e := e.(type) {
 case system.DestroyEvent:
  return e.Err
 case system.FrameEvent:
  ...
 }
}

При получении system.FrameEvent в приложении создается новый контекст макета, заполняемый определенными виджетами пользовательского интерфейса. В случае нашего приложения рисуется виджет заголовка H1.

В цикле продолжается обработка этих событий, чем обеспечивается необходимая реактивность графического интерфейса пользователя.

Если получено system.DestroyEvent, окно приложения находится в процессе закрытия из-за возможного взаимодействия с пользователем, например нажатия значка закрытия. Любая ошибка, связанная с этим процессом, возвращается после на обработку.

Отрисовка текста

В этом блоке кода на событие системного фрейма прореагировано инициализацией нового контекста макета, созданием большой текстовой подписи, стилизованной темно-бордовым цветом, выравниванием текста по середине его области, перемещением его в графический контекст и отображением результата в графическом процессоре:

var ops op.Ops
for {
 ...
 case system.FrameEvent:
  // Этот контекст макета используется для управления состоянием рендеринга.
  gtx := layout.NewContext(&ops, e)

  // Определяется большая подпись с текстом:
  title := material.H1(theme, "Hi, I'm Giggles")

  // Меняется цвет подписи.
  maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255}
  title.Color = maroon

  // Меняется положение подписи.
  title.Alignment = text.Middle

  // Подпись перемещается в контекст макета.
  title.Layout(gtx)

  // Операции рисования передаются в графический процессор.
  e.Frame(gtx.Ops)
 }
}

С кодом разобрались, теперь создадим и запустим это простое приложение Gio в операционной системе.

Создание и запуск приложения Gio в ОС

Для запуска может потребоваться установка зависимостей.

Linux

На Linux устанавливаем библиотеки Waylandx11xkbcommonGLESEGL и libXcursor:

apt install gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev

Для оптимальной производительности отдельно устанавливаем драйвер Vulkan. Понадобится он, например, в дистрибутиве Arch.

Установлен ли Vulkan, проверяем в терминале командой vulkaninfo: если драйвер настроен, вернется информация о версии его экземпляра.

Установив зависимости, запускаем приложение:

go mod tidy && go run .

Mac OS

Здесь нужен только xcode. Установив его, запускаемся:

go mod tidy && go run .

Windows

Для запуска приложения на Windows достаточно настроек по умолчанию:

go mod tidy && go run .

Если при запуске приложения Gio отображаются консоли, запускаемся такой командой:

go run -ldflags="-H windowsgui" .

Если все зависимости установлены корректно и приложение Gio запущено, появится приветственное окно:

Поздравляем, вы только что создали свое первое платформонезависимое приложение полностью на Go.

Теперь сделаем его интереснее.

Добавление функционала ChatGPT

Вызовем конечную точку chatGPT в OpenAI.

Чтобы получить секретный ключ OpenAI, создаем учетную запись на сайте OpenAI или входим в уже имеющуюся. На странице API-ключей создаем новый секретный ключ и копируем его в безопасное место. Созданный ключ появляется в списке на той же странице:

Секретный ключ не обнародуем: удаляем из кода, прежде чем коммитить и добавлять в публичный репозиторий вроде Git.

С помощью этого ключа в API chatGPT на OpenAI отправляются запросы. Интегрируем этот функционал в приложение.

Добавление функции для вызова конечной точки GPT

Чтобы добавить функцию для вызова конечной точки chatGPT, вместо стандартной библиотеки Go HTTP используем стороннюю go-gpt3: у нее меньше кода и проще реализация.

Извлекаем библиотеку в локальный кеш и включаем ее в файл go.mod такой командой терминала:

go get github.com/sashabaranov/go-openai

Добавляем в файл main.go функцию getGPTResponse:

func getGPTResponse(client *openai.Client, prompt string) (string, error) {
 resp, err := client.CreateChatCompletion(
  context.Background(),
  openai.ChatCompletionRequest{
   Model: openai.GPT3Dot5Turbo,
   Messages: []openai.ChatCompletionMessage{
    {
     Role:    openai.ChatMessageRoleUser,
     Content: prompt,
    },
   },
  },
 )
 if err != nil {
  return "", err
 }
 return resp.Choices[0].Message.Content, nil
}

Функцией getGPTResponse принимается два аргумента: экземпляр клиента OpenAI и строка prompt. А после из модели ИИ возвращается сгенерированный текст или ошибка запроса.

С помощью указанного клиента и входных данных prompt в API отправляется запрос Chat Completion, в который включается сообщение от роли пользователя с содержимым подсказки.

Если запрос успешен, функцией из модели возвращается содержимое первого сообщения-ответа. При ошибке запроса первым значением возвращается пустая строка, вторым — ошибка.

Чтобы запрашивать через API chatGPT новые шутки, в приложении нужен интерактивный элемент. В идеале — кнопка. Добавим в GoGiggles кнопку, которой вызывается getGPTResponse, при каждом ее нажатии отображается новая шутка.

Добавление кнопки

Импортируем библиотеку виджетов Gio и инициализируем виджет кнопки, добавив в файл main.go такой код:

import (
 .
 .
 "gioui.org/widget"
 .
 .
)

var (
 theme  = material.NewTheme(gofont.Collection())
 button = new(widget.Clickable)
)

type (
 C = layout.Context
 D = layout.Dimensions
)

Переменная button  — это экземпляр интерактивного виджета, которым при взаимодействии с пользователем запускается действие.

Псевдонимы типов C и D  — это типы layout.Context и layout.Dimensions соответственно. Так к этим типам удобнее обращаться в коде.

Инициализируем переменной стандартный текст подписи для отображения шутки и новый клиент OpenAI, добавив в функцию run такой код:

var labelText = "Hi, I'm Giggles, I can tell you jokes about Go developers"
var client = openai.NewClient("open-ai-secret-key")

Заменяем строку open-ai-secret-key на секретный ключ OpenAI.

Теперь для отображения кнопки и виджетов макета меняем в функции run код макета, применяем вертикальный макет Flex и центрируем в нем label и button:

for {
  .
  .
  case system.FrameEvent:
    gtx := layout.NewContext(&ops, e)
    layout.Flex{Axis: layout.Vertical}.Layout(gtx,
      layout.Flexed(1, func(gtx C) D {
          return layout.Center.Layout(gtx, func(gtx C) D {
          lbl := material.Label(theme, unit.Sp(20), labelText)
          lbl.Alignment = text.Middle
          return lbl.Layout(gtx)
          })
      }),
      layout.Flexed(1, func(gtx C) D {
          return layout.Center.Layout(gtx, func(gtx C) D {
          btn := material.Button(theme, button, "Tell me a joke")
          return btn.Layout(gtx)
         })
      }),
     )
     e.Frame(gtx.Ops)
    .
    .
}

Здесь в макете Flex имеется два дочерних блока flex, которыми центрируются элементы label и button: каждому элементу дается 50% доступного пространства. Первым дочерним элементом flexed отображается подпись для показа шуток, вторым — кнопки.

Чтобы при нажатии кнопки вызывалась функция getGPTResponse, добавим прослушиватель событий.

Обработка событий нажатия

Для обработки событий нажатия добавляем в case system.FrameEvent в функции run такой код:

if button.Clicked() {
    go func() {
       response, err := getGPTResponse(client, "Tell me a joke about Go developers")
       if err != nil {
        log.Printf("GPT Err: %s", err)
        return
       }
       labelText = response
       w.Invalidate()
    }()
}

При нажатии кнопки создается горутина, которой для генерирования моделью ИИ GPT-3 шутки вызывается функция getGPTResponse.

Благодаря фоновому выполнению функции в горутине, событие нажатия кнопки продолжается без блокировки основного потока выполнения.

Если эта операция успешна и нет ошибки, шутка присваивается переменной labelText и для запуска события фрейма, которым в пользовательский интерфейс привносится новый текст, вызывается метод Invalidate.

Добавив эту часть кода, мы завершаем приложение GoGiggles.

Собираем все вместе

В итоге в файле main.go получается такой код:

package main

import (
 "context"
 "gioui.org/app"
 "gioui.org/font/gofont"
 "gioui.org/io/system"
 "gioui.org/layout"
 "gioui.org/op"
 "gioui.org/text"
 "gioui.org/unit"
 "gioui.org/widget"
 "gioui.org/widget/material"
 "github.com/sashabaranov/go-openai"
 "log"
 "os"
)

var (
 theme  = material.NewTheme(gofont.Collection())
 button = new(widget.Clickable)
)

type (
 C = layout.Context
 D = layout.Dimensions
)

func getGPTResponse(client *openai.Client, prompt string) (string, error) {
 resp, err := client.CreateChatCompletion(
  context.Background(),
  openai.ChatCompletionRequest{
   Model: openai.GPT3Dot5Turbo,
   Messages: []openai.ChatCompletionMessage{
    {
     Role:    openai.ChatMessageRoleUser,
     Content: prompt,
    },
   },
  },
 )
 if err != nil {
  return "", err
 }
 return resp.Choices[0].Message.Content, nil
}

func run(w *app.Window) error {
 var ops op.Ops
 var labelText = "Hi, I'm Giggles, I can tell you jokes about Go developers"
 var client = openai.NewClient("open-ai-secret-key")
 for {
  e := <-w.Events()
  switch e := e.(type) {
  case system.DestroyEvent:
   return e.Err
  case system.FrameEvent:
   gtx := layout.NewContext(&ops, e)

   if button.Clicked() {
    go func() {
     response, err := getGPTResponse(client, "Tell me a joke about Go developers")
     if err != nil {
      log.Printf("GPT Err: %s", err)
      return
     }
     labelText = response
     w.Invalidate()
    }()
   }

   layout.Flex{Axis: layout.Vertical}.Layout(gtx,
    layout.Flexed(1, func(gtx C) D {
     return layout.Center.Layout(gtx, func(gtx C) D {
      lbl := material.Label(theme, unit.Sp(20), labelText)
      lbl.Alignment = text.Middle
      return lbl.Layout(gtx)
     })
    }),
    layout.Flexed(1, func(gtx C) D {
     return layout.Center.Layout(gtx, func(gtx C) D {
      btn := material.Button(theme, button, "Tell me a joke")
      return btn.Layout(gtx)
     })
    }),
   )

   e.Frame(gtx.Ops)
  }
 }
}

func main() {
 go func() {
  w := app.NewWindow()
  err := run(w)
  if err != nil {
   log.Fatal(err)
  }
  os.Exit(0)
 }()
 app.Main()
}

Запускаем приложение так же, как простую реализацию Gio, и видим:

Поздравляем, вы только что создали свое первое платформонезависимое приложение на Gio с непосредственным режимом реализации графического интерфейса.

Дополнительные возможности

Кроме того, благодаря обширному инструментарию Gio, дополнительным библиотекам и виджетам пользовательского интерфейса добавили функционал, сделав приложение GoGiggles интереснее.

Исходный код приложения GoGiggles доступен на GitHub. Используйте его как отправную точку для своих экспериментов с Gio.

Чтобы копировать шутки в буфер обмена, добавьте кнопку копирования. А чтобы делиться ими — кнопки соцсетей. Сделайте приложение для устройств iOS и Android.


Источник


Report Page