Создано Воскресенье, 12 июня 2022 г.

Недавно Милан Николич, сопровождающий привязки Go для Raylib, выпустил обновленные привязки к кросс-платформенной IUP framework for Go. Я решил использовать его для проекта и сообщить о его плюсах и минусах. Проверьте этот пост для моего обзора других наборов инструментов GUI.

Монтаж

Установка очень проста, вы просто импортируете пакет в свой проект Go, как обычно. В файле Readme сказано, что первая компиляция может занять несколько минут, но на моей Linux-машине она прошла очень быстро. Последующие компиляции идут не заметно медленнее чистого Go на моей машине. Как указано в документах, вы должны убедиться, что зависимости выполняются в вашей системе. На моей машине это означало, что мне пришлось установить файлы разработчика GTK3 с помощью apt-get install libgtk-3-dev.

Как это выглядит

Вот как выглядит мой текущий, очень альфа-стадия проекта с IUP в Linux (в уменьшенном масштабе):

Это не так уж и плохо, учитывая, что позже будет больше элементов пользовательского интерфейса, а визуальный вид пока не является приоритетом. Он использует такой скрипт для запуска приложения с темой CBlack:

#!/bin/sh
GTK_THEME=CBlack ./giuprj

С моими настройками по умолчанию в моей текущей установке Linux это выглядит визуально менее привлекательным. Размер окна немного отличается, но в остальном никаких изменений не производилось:

Суть в том, что, по крайней мере, в Linux есть варианты стилизации приложения, если вы хотите переопределить настройки темы GTK пользователя. Пуристы могут ненавидеть вас за это, но я на самом деле рекомендую это, если графический интерфейс должен быть альтернативой другому, более современному варианту графического интерфейса или веб-интерфейсу. Обратите внимание, однако, что возможности рестайлинга в Windows могут быть ограничены, а в Linux IUP ясно показывает, что использует GTK в качестве серверной части. К сожалению, некоторые из вариантов GTK варьируются от раздражающих до уродливых, и вы ничего не можете с этим поделать. Например, заметили пунктирные линии в полях списка? Они рисуются GTK, чтобы указать, что справа есть больше контента, который был «отрезан» размерами управления, и нет никакого способа удалить их, кроме как обрезать текст в списке.

В Windows и Mac нельзя изменить дизайн некоторых элементов пользовательского интерфейса, а параметры могут быть более ограниченными. В IUP есть новые элементы управления с префиксом «Flat», которые позволяют больше настраивать и отрисовываются самим IUP, но они содержат ошибки и пока не всегда работают должным образом. Но в целом IUP выглядит хорошо, и во многих случаях желательно иметь собственные элементы управления, которые IUP рисует по умолчанию в Windows и Mac (и Linux, если вы считаете GTK родным). Элементы управления можно настроить на автоматическое изменение размера практически любым мыслимым способом, и это большой плюс по сравнению с такими фреймворками, как Fyne, которые будут постоянно раздражать вас своими ограничениями макета. Разделители/разделители не видны на снимках экрана, потому что я сделал их невидимыми. Имеется четыре разных разделителя, которые позволяют динамически изменять размер областей содержимого при перетаскивании их мышью. Это работает достаточно хорошо и иллюстрирует зрелость фреймворка. IUP существует с 90-х годов и до сих пор регулярно развивается и обновляется.

Применение

IUP используется очень просто, и на самом деле простота использования является одной из причин его постоянной популярности в качестве облегченной среды графического интерфейса. Тем не менее, этот путь явно не путь Go. Можно сказать, что IUP совершенно чужд способу ведения дел в Go. В IUP все свойства пользовательского интерфейса получаются и устанавливаются с помощью функций GetAttribute и SetAttribute, которые принимают и возвращают строки — это верно, даже если атрибут является числом, цветом или image, вы обычно устанавливаете строку. Поэтому вам нужно правильно конвертировать в Go, например. с помощью strconv.Atoi для преобразования строки в целое число и strconv.Itoa для преобразования целого числа в строку. Очевидно, что это крайне неэффективно, и действительно, в моих тестах список работал намного медленнее, чем тот, который я использовал в непосредственном режиме GUI Giu. Вы можете устанавливать и получать строковые дескрипторы для объектов, а иногда, например, в случае с изображениями, это необходимо делать, но вы можете хранить дескрипторы непосредственно в Golang. На самом деле все элементы управления и объекты IUP имеют тип iup.Ihandle, так что забудьте о безопасности типов. IUP имеет TCL/Tk, подобный слабо типизированному, основанному на строках API.

Чтобы дать вам представление о том, как это выглядит, вот часть инициализации моего окна:

a.previewName = iup.MultiLine().
SetAttributes(“VISIBLELINES=1,EXPAND=YES,BORDER=NO,MARGIN=0x0,CGAP=0x0,WORDWRAP=YES,BGCOLOR=” + dlgbgcolor)
a.previewLink = iup.Link(“”, “”).SetAttributes(“EXPAND=YES”)
a.previewSize = iup.Label(“”)
a.previewTags = iup.MultiLine().SetAttributes(“VISIBLELINES=2,EXPAND=HORIZONTAL,BGCOLOR=” + dlgbgcolor)
a.previewInfo = iup.MultiLine().SetAttributes(“VISIBLELINES=4,EXPAND=YES,BGCOLOR=” + dlgbgcolor)

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

https://pkg.go.dev/github.com/gen2brain/iup-go/iup

Каждый элемент управления имеет ссылку на оригинальную документацию, а поскольку API в значительной степени не зависит от языка и основан на строках, вы напрямую используете эти документы для поиска всего, что вам нужно. Нет никаких странных предположений, как, например, в. некоторые привязки Qt или GTK4 для Go, которые требуют немного больше работы для перевода туда и обратно между исходными документами и тем, как это сделать в Go. В любом случае это всего лишь строки и дескрипторы в IUP.

Ограничения

Параллелизм очень плох для IUP

Есть одно огромное предостережение: параллелизм. Как вы, вероятно, уже знаете, большинство графических интерфейсов не допускают параллельного рисования или доступа к другим элементам управления/виджетам. Общие механизмы рисования, такие как SDL и GLFW, также требуют последовательного рисования. Это имеет смысл, учитывая, что в операциях рисования порядок обычно имеет значение.

Однако проблема в IUP заключается не только в отсутствии поддержки параллелизма, но и в том, что фреймворк очень плохо и загадочно вылетает на рабочий стол при любомодновременном доступе к элементам управления. Это включает в себя чтение и запись их внутреннего состояния или установку других атрибутов, и не имеет значения, контролируете ли вы доступ на стороне Go с помощью мьютексов или других примитивов синхронизации. По крайней мере, некоторый одновременный доступ к элементам графического интерфейса приводит к сбоям на рабочем столе, и я не смог точно определить, какие именно. Изначально было предложено использовать часто используемый для этой цели пакет mainthread, но в своих тестах я понял, что это не работает, и Милан убрал это предложение из документации. IUP будет аварийно и неожиданно падать, когда вы разрешаете различным go-процедурам доступ к его объектам.

По сути, у этой проблемы есть два решения, оба громоздкие в Go и сильно противоречащие его философии. Во-первых, вы можете использовать iup.Timer для периодического захвата замыканий из параллельной безопасной очереди и выполнения этого кода в функции действия таймера. Во-вторых, можно сделать то же самое, но установив в iup.IdleFunc петлю вытягивания. Эти два подхода примерно эквивалентны. Подход с таймером имеет тот недостаток, что функции выполняются со скоростью, заданной его интервалом, а подход с функцией простоя имеет недостаток, заключающийся в трате циклов ЦП, которые, согласно документам, уже потрачены впустую на простую установку функции. Я выбрал подход с таймером, и моя рассылка выглядит так:

type Scheduler struct {
	mainContext context.Context         // the master context, from which others are derived
	wg          sync.WaitGroup          // for waiting until all goroutines have finished
	cancel      func()                  // for canceling the master context, stopping all derived goroutines
	once        map[string]func()       // for each key, only one goroutines can run at a time
	idleQueue   *goconcurrentqueue.FIFO // for idle functions
	timer       iup.Ihandle             // IUP timer for processing queue
	mutex       sync.Mutex              // for synchronization
}
// NewScheduler returns a new scheduler with a cancelable context based on the given context.
func NewScheduler(ctx context.Context) *Scheduler {
	c, done := context.WithCancel(ctx)
	scheduler := &Scheduler{
		mainContext: c,
		cancel:      done,
		once:        make(map[string]func()),
		idleQueue:   goconcurrentqueue.NewFIFO(),
		timer:       iup.Timer(),
	}
	idleFunc := func(ih iup.Ihandle) int {
		select {
		case <-scheduler.mainContext.Done():
			scheduler.timer.SetAttribute("RUN", "NO")
		default:
			for scheduler.idleQueue.GetLen() > 0 {
				fn, err := scheduler.idleQueue.Dequeue()
				if err != nil {
					return iup.DEFAULT
				}
				f, ok := fn.(func())
				if ok {
					f()
				}
				iup.LoopStep()
			}
		}
		return iup.DEFAULT
	}
	scheduler.timer.SetCallback("ACTION_CB", iup.TimerActionFunc(idleFunc))
	scheduler.timer.SetAttributes("TIME=50,RUN=YES")
	return scheduler
}

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

Однако у IUP есть проблемы с обновлением в узких циклах, и по какой-то причине в порте Go рекомендация использовать iup.LoopStep или iup.Flush в узких циклах, чтобы дать время для обработки IUP, не работает должным образом. IUP имеет внутреннюю очередь отрисовки, которая блокируется, когда Go запускает замкнутый цикл, даже если этот цикл выполняется в отдельной горутине. Я не знаю, как это возможно, но мои тесты это ясно подтверждают!

Пока что я не нашел решения этой проблемы. При определенных условиях элементы управления IUP зависают и никакие попытки обновить их вручную (iup.Refresh,iup.LoopStep,iup.Flush ,iup.Update) решает проблему. Это приводящее в бешенство воспоминание о 90-х, когда у меня была эта проблема в последний раз.

Извините, Дэйв, нет форматированного текста

Привязки Go IUP не имеют элементов управления, поддерживающих разные стили текста, шрифты или цвета в одном и том же многострочном текстовом поле редактирования. Забудьте о форматированном тексте, выделении синтаксиса и HTML-подобных стилях. Вы получаете базовые многострочные поля редактирования и можете копировать и устанавливать выделение и т. д., но не форматированный текст.

Существует расширение Scintilla для IUP, которое позволяет редактировать большую часть форматированного текста, хотя Scintilla в первую очередь ориентирована на быстрое окрашивание синтаксиса и редактирование текста. Привязки IUP позволяют довольно обширное редактирование стилей и не дотягивают только до произвольного встраивания изображений. Однако эти привязки недоступны в порте Go IUP, и сопровождающий упомянул в проблеме Github, что он не планирует добавлять поддержку Scintilla в будущем. Зависимости все усложняют, поэтому выбор понятен, но отсутствие какой-либо поддержки форматированного текста является большим недостатком.

Нет быстрого рисования на холсте

Точно так же необязательные примитивы быстрого рисования холста также были исключены из привязок Go, а также другие пользовательские расширения на основе рисования, такие как быстрый элемент управления, похожий на электронную таблицу. Вы получите базовые примитивы рисования изображений на базе процессора и сможете использовать все, что может предложить Go в image.Image, например. использовать стороннюю библиотеку для отрисовки изображений, но в IUP нет отрисовки с GPU-ускорением. Вместе с упомянутыми выше одновременными проблемами обновления/обновления это означает, что IUP нельзя использовать ни для чего игрового или любого другого быстрого обновления графики, например, для покадровой отрисовки фильмов. Я имею в виду, что вы можете попробовать, но маловероятно, что это сработает.

Мелкие ошибки и аномалии

Я столкнулся с рядом мелких ошибок и визуальных аномалий во время преобразования моего проекта. Например, мне пришлось ввести разделитель фиксированной ширины в элементе управления iup.Split, потому что элементы управления справа не учитывали свойства границ и уродливым образом выпрыгивали из окна. Другая, более серьезная ошибка заключается в том, что, по крайней мере, на моей машине разработчика с Gtk3 свойство BGCOLOR iup.Split не было установлено правильно, аномалия, которую можно исправить только с помощью пользовательской темы. Тем не менее, в целом фреймворк очень стабильный и зрелый, и вы можете получить большинство вариантов макета, немного поэкспериментировав. В частности, свойства границы и промежутка управления с наследованием очень гибкие и позволяют вам точно настраивать внешний вид способами, которые не позволяют вам сделать более похожие на Go параметры графического интерфейса.

Краткое содержание

Новые привязки Go IUP довольно хороши и в целом являются хорошим выбором для небольших и средних проектов с умеренными требованиями к графическому интерфейсу. Он очень зрелый и использует собственные элементы управления, в отличие от специальных стилей, которые имеют различные недостатки, такие как отсутствие поддержки чтения с экрана. Это, безусловно, выбор, который имеет смысл только для очень традиционных настольных приложений с графическим интерфейсом и не слишком высокими требованиями. Если вы планируете встраивать фильмы или веб-представления и тому подобное, вам придется использовать Qt или GTK4. Но я могу порекомендовать его для более простых проектов, и его интересно использовать — за исключением ужасных проблем параллелизма/обновления из 90-х.

[https://slothblog.org]