Здесь будет пост с большим количеством скучного исходного кода. Я покажу базовый механизм, как строить оконные приложения (GUI) для мака. Кто не интересуется - смело пролистывайте.
Раньше "родные" маковские приложения положено было писать на языке Objective-C. Это такой гибридужа и ежа Си и Смолтока. У языка своя интересная история, как и у мака, но он был компромиссом со своими проблемами, и потихоньку сошёл на нет. Эппл придумал другой язык в замену, называется Swift. Он быстро развивается, и в последней версии достиг достаточной совместимости с Си++. Я ждал этого момента, чтобы попробовать поиграться со Swift.
Я ведь в GUI совсем не знаток. За всю жизнь не наваял ни одной приличной GUI-программы. Мои потребности в оконных интерфейсах ограничены примитивными случаями. Но механизмы знать полезно.
Обычно оконные интерфейсы принято "рисовать" графически. В смысле, строить из блоков в навороченных средах разработки типа Visual Studio или XCode. Долго и нудно возюкать мышкой и к каждому элементу настраивать сотни каких-то маловразумительных параметров. К счастью, для Swift появилась возможность всё делать прямо в исходном коде. Так называемый тулкит SwiftUI. Его я и покажу. Впрочем, он успешно уживается с визуальной средой XCode. То есть можно GUI и мышкой набрасывать при желании.
Swift хорошо уживается с Си++. Можно из Swift создавать объекты классов Си++ и обращаться к его полям и методам. И в обратную сторону: обращаться к классам и функциям Swift из кода Си++. Это крайне важная фича, и без неё Swift для меня малоинтересен. По простой причине: переносимость. Софт, которым я обычно занимаюсь, должен работать на всех платформах Linux (в том числе Андроид), мак (часто также iOS), Windows. По этой причине суть приложения (часто называют "бизнес-логика") пишется безотносительно к среде выполнения. Си++ обычный выбор. И потом эта бизнес-логика через хорошо определённые стыки привязывается к юзерскому интерфейсу конкретной платформы. Чтобы приложение смотрелось как "родное", лучше его делать на языке, родном для платформы. Для Windows это нынче C#, для Андроида Kotlin или Dart, для мака Swift. Скриптовые языки я тут обхожу стороной: от них больше проблем чем пользы.
Рассмотрим простейший пример приложения: целочисленный счётчик и пара кнопок "+" и "-", его изменяющих.
Раньше "родные" маковские приложения положено было писать на языке Objective-C. Это такой гибрид
Я ведь в GUI совсем не знаток. За всю жизнь не наваял ни одной приличной GUI-программы. Мои потребности в оконных интерфейсах ограничены примитивными случаями. Но механизмы знать полезно.
Обычно оконные интерфейсы принято "рисовать" графически. В смысле, строить из блоков в навороченных средах разработки типа Visual Studio или XCode. Долго и нудно возюкать мышкой и к каждому элементу настраивать сотни каких-то маловразумительных параметров. К счастью, для Swift появилась возможность всё делать прямо в исходном коде. Так называемый тулкит SwiftUI. Его я и покажу. Впрочем, он успешно уживается с визуальной средой XCode. То есть можно GUI и мышкой набрасывать при желании.
Swift хорошо уживается с Си++. Можно из Swift создавать объекты классов Си++ и обращаться к его полям и методам. И в обратную сторону: обращаться к классам и функциям Swift из кода Си++. Это крайне важная фича, и без неё Swift для меня малоинтересен. По простой причине: переносимость. Софт, которым я обычно занимаюсь, должен работать на всех платформах Linux (в том числе Андроид), мак (часто также iOS), Windows. По этой причине суть приложения (часто называют "бизнес-логика") пишется безотносительно к среде выполнения. Си++ обычный выбор. И потом эта бизнес-логика через хорошо определённые стыки привязывается к юзерскому интерфейсу конкретной платформы. Чтобы приложение смотрелось как "родное", лучше его делать на языке, родном для платформы. Для Windows это нынче C#, для Андроида Kotlin или Dart, для мака Swift. Скриптовые языки я тут обхожу стороной: от них больше проблем чем пользы.
Рассмотрим простейший пример приложения: целочисленный счётчик и пара кнопок "+" и "-", его изменяющих.
Всё приложение будет состоять из шести файлов. Исходники берите с Гитхаба.
В целом смотрится очень неплохо. Надо будет какую-нибудь простую штукенцию на Swift сбацать, типа калькулятора MK-61.
- MainApp.swift - главная процедура приложения (main).
- ContentView.swift - главное окно приложения. Задаёт расположение графических элементов интерфейса в окне.
- ModelProxy.swift - определяет реакцию на действия юзера. Когда кликаете на кнопочку - вызывается один из методов этого класса.
- ModelCxx.cpp - реализация логики приложения. На самом деле класс ModelProxy сам ничего не делает, а вызывает методы Си++.
- ModelCxx.h - определение интерфейса к логике приложения.
- Package.swift - скрипт для сборки всего этого в кучу. Отдалённый аналог makefile, но на том же языке Swift.
$ tree
.
├── Package.swift
└── Sources
├── Application
│ └── MainApp.swift
├── Gui
│ ├── ContentView.swift
│ └── ModelProxy.swift
└── ModelCxx
├── ModelCxx.cpp
└── ModelCxx.h
MainApp.swift
Определяем процедуру main(), с которой стартует приложение. Здесь вся суть в создании экземпляра класса ContentView, который реализует главное окно приложения. Остальное обёртка.import SwiftUI
import Gui
@main
struct MainApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
ContentView.swift
Определяем класс, задающий в декларативном стиле компоновку главного окна приложения. Контейнер HStack располагает элементы интерфейса горизонтально. Ставим кнопочку "-" (Button), затем значение счётчика (Text), затем кнопочку "+". Заметьте, как для действий и значений вызываются методы и поля объекта proxy класса ModelProxy.import SwiftUI
public struct ContentView: View {
@ObservedObject var proxy: ModelProxy = ModelProxy.shared
public var body: some View {
HStack {
Button("-") {
proxy.decrement()
}
Text("\(proxy.count)")
Button("+") {
proxy.increment()
}
}
.padding()
}
public init() {
proxy.setup()
}
}
#Preview {
ContentView()
}
ModelProxy.swift
Здесь мы определяем синглтон, куда приходят действия юзера, и откуда отображается нужная юзеру информация в главном окне. Но поскольку мы хотим всю бизнес-логику приложения иметь на Си++, здесь в классе ModelProxy мы просто пробрасываем все вызовы в объект model класса ModelCxx. И в обратную сторону: код из Си++ будет вызывать процедуру updateCount() для изменения отображаемого значения счётчика.import Foundation
import ModelCxx
class ModelProxy: ObservableObject {
@Published var count: Int = 0
static let shared = ModelProxy()
// Allocate C++ object.
private var model = ModelCxx()
func setup() {
model.setup()
}
func increment() {
model.increment()
}
func decrement() {
model.decrement()
}
}
public func updateCount(val: Int) {
ModelProxy.shared.count = val
}
ModelCxx.h
Определяем интерфейс к бизнес-логике нашего приложения. Целочисленный счётчик и методы для инициализации, увеличения и уменьшения.
class ModelCxx {
private:
int count{ 123 };
public:
void setup();
void increment();
void decrement();
};ModelCxx.cpp
Собственно бизнес-логика. Заметьте как вызывается метод Gui::updateCount() из Swift для обновления счётчика в окошке. Файл "Gui-Swift.h" автоматически создаётся компилятором Swift. В нём присутствуют все public классы и функции из нашего модуля Gui. В данном случае public func updateCount(val: Int).#include "ModelCxx.h"
#include "Gui-Swift.h"
void ModelCxx::setup()
{
Gui::updateCount(count);
}
void ModelCxx::increment()
{
count++;
Gui::updateCount(count);
}
void ModelCxx::decrement()
{
count--;
Gui::updateCount(count);
}
Package.swift
Теперь опишем структуру нашего проекта для компилятора. Никаких специальных скриптов: используется тот же язык Swift. Инициализируем структуру специального класса Package. Исходники разбиты на три модуля: два свифтовых (Application и Gui) и один сиплюсплюсный (ModelCxx).С этой версией компилятора Swift не всё оказалось гладко. По какой-то причине при компиляции кода Си++ не ставится путь к файлу Gui-Swift.h. Пришлось прописать его в конфигурации Project. Надеюсь, в следующей версии исправят этот глюк.// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "CountCxx",
platforms: [
.macOS(.v14),
],
targets: [
.target(
name: "Gui",
swiftSettings: [.interoperabilityMode(.Cxx)]
),
.target(
name: "ModelCxx",
dependencies: ["Gui"],
publicHeadersPath: ".",
cxxSettings: [.unsafeFlags([
// Quick hack to find includes generated by Swift
"-I", ".build/arm64-apple-macosx/debug/Gui.build",
"-I", ".build/x86_64-apple-macosx/debug/Gui.build",
])]
),
.executableTarget(
name: "Application",
dependencies: ["ModelCxx", "Gui"],
swiftSettings: [.interoperabilityMode(.Cxx)]
),
],
cxxLanguageStandard: .cxx20
)
Package.swift
Компилируем, смотрим размер бинарника:Вполне компактно, даже при сборке с отладкой: меньше 50 килобайт кода. Смотрим зависимости:$ swift build
Building for debugging...
[7/7] Linking Application
Build complete! (39.81s)
$ size .build/x86_64-apple-macosx/debug/Application
__TEXT __DATA __OBJC others dec hex
49152 16384 0 4295081984 4295147520 10002c000
Обширный список, но всё это базовый рантайм MacOS нынче.$ otool -L .build/x86_64-apple-macosx/debug/Application
.build/x86_64-apple-macosx/debug/Application:
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1600.151.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0)
/System/Library/Frameworks/Combine.framework/Versions/A/Combine (compatibility version 1.0.0, current version 311.0.0)
/System/Library/Frameworks/DeveloperToolsSupport.framework/Versions/A/DeveloperToolsSupport (compatibility version 1.0.0, current version 21.0.15)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 2048.1.255, weak)
/System/Library/Frameworks/SwiftUI.framework/Versions/A/SwiftUI (compatibility version 1.0.0, current version 5.0.83)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 5.9.0)
/usr/lib/swift/libswiftCoreFoundation.dylib (compatibility version 1.0.0, current version 120.100.0)
/usr/lib/swift/libswiftCoreImage.dylib (compatibility version 1.0.0, current version 2.0.0, weak)
/usr/lib/swift/libswiftDarwin.dylib (compatibility version 1.0.0, current version 0.0.0, weak)
/usr/lib/swift/libswiftDispatch.dylib (compatibility version 1.0.0, current version 34.0.2, weak)
/usr/lib/swift/libswiftIOKit.dylib (compatibility version 1.0.0, current version 1.0.0, weak)
/usr/lib/swift/libswiftMetal.dylib (compatibility version 1.0.0, current version 341.16.1, weak)
/usr/lib/swift/libswiftOSLog.dylib (compatibility version 1.0.0, current version 4.0.0, weak)
/usr/lib/swift/libswiftObjectiveC.dylib (compatibility version 1.0.0, current version 8.0.0, weak)
/usr/lib/swift/libswiftQuartzCore.dylib (compatibility version 1.0.0, current version 3.0.0, weak)
/usr/lib/swift/libswiftUniformTypeIdentifiers.dylib (compatibility version 1.0.0, current version 790.0.0, weak)
/usr/lib/swift/libswiftXPC.dylib (compatibility version 1.0.0, current version 29.0.2, weak)
/usr/lib/swift/libswiftos.dylib (compatibility version 1.0.0, current version 1040.0.0, weak)
В целом смотрится очень неплохо. Надо будет какую-нибудь простую штукенцию на Swift сбацать, типа калькулятора MK-61.


no subject
Date: 2023-11-22 11:27 (UTC)Ой тоска.... 30 лет назад. До сих пор подташнивает.
no subject
Date: 2023-11-22 17:26 (UTC)no subject
Date: 2023-11-22 19:18 (UTC)Был просто ихний си. С прибамбасами и ресурсами.
no subject
Date: 2023-11-22 19:25 (UTC)no subject
Date: 2023-11-22 19:37 (UTC)Тоже до сих пор блевать хочется.
Qt, с другой стороны, очень нравился.
no subject
Date: 2023-11-22 19:57 (UTC)no subject
Date: 2023-11-22 21:07 (UTC)Удивительно, но как-то давненько, опять же, лет 15-20 назад делал для диплома прогу с гуём на C# и Win Forms или как там оно называется одному раздолбаю-коллеге на работе и как-то вполне тоже приятно было. Вообще впервые трогал и C# и Win Forms и как-то всё логично и сходу получилось без проблем.
Но я не настоящий сварщик - окошко для фреймбуффера на libSDL - это мой максимум нужды в гуях. Сейчас наверное бы вообще делал на node.js в браузере если надо гуй, весь этот Шалтай-болтай нативных гуёв маленько надоел. Ну либо вообще богопротивный Electron.
no subject
Date: 2023-11-22 21:23 (UTC)Курсе на первом дело было. Мы тогда все со Спектрума перешли на Писи, кто через Амигу опять же, но все со временем перешли на Писи в конце-концов. На Спектруме модно было кодить только на ассемблере чтобы хоть как-то быстро что-то работало, ну и на Писи тоже сидели в Досе и на Тасме кодировали всякие видеоэффекты для демосцены.
Инета не было и один из наших кодеров начал учить Си и купил на Митинском радиорынке диск с Watcom C.
Ну и начал задвигать мол что это будущее и вообще сам Джон Кармак на этом пишет.
Ну и как-то я у него по модему это дело качаю, скорость еле-еле. И он такой мол - не копируй директории с хэдерами и либами MFC, я, говорит, тебе гарантирую что тебе в жизни это никогда не пригодится.
Хаха, лет через 5 после тех событий сидел в конторе и писал гуй на MFC для CAD для полукосмической конторы одной.
no subject
Date: 2023-11-23 02:26 (UTC)Сделал отдельный пост: https://vak.dreamwidth.org/1126755.html
no subject
Date: 2023-11-23 03:14 (UTC)no subject
Date: 2023-11-23 08:26 (UTC)no subject
Date: 2023-11-23 17:55 (UTC)Самое подходящее слово для описания всего этого — "заповедник". Оно осталось примерно там же где было 20+ лет назад, правда, приросло парой новых виндовых API. Всё это обмазано какими-то custom control'ами (которые далеко не лучшие люди города делали, нет), полузаконченными theme engine на полуручном приводе... Есть какие-то коммерческие библиотеки, которые толи никто не покупает, толи никто просто не знает про них (там заходишь -- и сайт в стиле web 1.0, оч показательно). Итд итп.
Так что поддерживаться оно поддерживается, никто его не переводил в статус deprecated. Но вот то что за это время микрософты не сделали НИЧЕГО чтобы сделать его как-то проще, безопаснее или, там, удобнее — это правда.
no subject
Date: 2023-11-23 20:32 (UTC)no subject
Date: 2023-11-22 22:36 (UTC)Qt-приложение без изменений работает на Linux/Windows/macOs.
no subject
Date: 2023-11-22 22:55 (UTC)no subject
Date: 2023-11-23 18:02 (UTC)HStack { Button("-") { proxy.decrement() } Text("\(proxy.count)") Button("+") { proxy.increment() } }Это всё выглядит хорошо, и даже в древнем gtk каком-нибудь было (и не сильно геморройнее), но потом приходят UI-дизайнеры с вёрсткой в фигме и желают странного. Последний раз в моей практике они захотели аналог "flex-flow". На чём мы и разосрались успешно.