Add support for macOS
All checks were successful
SwiftLint / SwiftLint (push) Successful in 3s

This commit is contained in:
david-swift 2025-01-31 17:48:44 +01:00
parent 640cf90b63
commit 4eff4d4587
22 changed files with 553 additions and 63 deletions

3
.gitignore vendored
View File

@ -4,8 +4,7 @@
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
/.swiftpm
.netrc
/Package.resolved
.Ulysses-Group.plist

10
Bundler.toml Normal file
View File

@ -0,0 +1,10 @@
format_version = 2
[apps.Demo]
product = 'Demo'
version = '0.1.0'
identifier = "dev.aparoksha.Demo"
minimum_macos_version = '13'
[apps.Demo.plist]
CFBundleLocalizations = ["en", "de"]

View File

@ -14,6 +14,8 @@ enum Framework: String {
/// The WinUI backend.
case winui = "WINUI"
/// The AppKit backend.
case appkit = "APPKIT"
/// The libadwaita backend.
case adwaita = "ADWAITA"
@ -32,6 +34,9 @@ var targetDependencies: [Target.Dependency] = []
#if os(Windows)
/// The framework used for rendering.
let framework = environmentFramework ?? .winui
#elseif os(macOS)
/// The framework used for rendering.
let framework = environmentFramework ?? .appkit
#else
/// The framework used for rendering.
let framework = environmentFramework ?? .adwaita
@ -41,6 +46,9 @@ switch framework {
case .winui:
dependencies.append(.package(url: "https://git.aparoksha.dev/aparoksha/winui-swift", branch: "main"))
targetDependencies.append(.product(name: "Core", package: "winui-swift"))
case .appkit:
dependencies.append(.package(url: "https://git.aparoksha.dev/aparoksha/macbackend", branch: "main"))
targetDependencies.append(.product(name: "Core", package: "macbackend"))
case .adwaita:
dependencies.append(.package(url: "https://git.aparoksha.dev/aparoksha/adwaita-swift", branch: "main"))
targetDependencies.append(.product(name: "Core", package: "adwaita-swift"))
@ -50,7 +58,7 @@ case .adwaita:
let package = Package(
name: "Aparoksha",
platforms: [
.macOS(.v10_15),
.macOS(.v13),
.iOS(.v13)
],
products: [

View File

@ -0,0 +1,46 @@
//
// Submenu.swift
// Aparoksha
//
// Created by david-swift on 07.12.2024.
//
import Core
/// A submenu widget.
public struct Submenu: SimpleView {
/// The button's label.
var label = ""
/// An optional icon, displayed on WinUI.
var icon: Icon?
/// The button's action handler.
var content: Body
// TODO: Add support for other platforms
/// The view.
public var view: Body {
#if APPKIT
Menu(label) {
content
}
#endif
}
/// Initialize a submenu.
/// - Parameters:
/// - label: The buttons label.
/// - handler: The button's action handler.
public init(_ label: String, @ViewBuilder content: () -> Body) {
self.label = label
self.content = content()
}
/// Set the menu's icon.
/// - Parameter icon: The icon.
/// - Returns: The menu button.
public func icon(_ icon: Icon?) -> Self {
modify { $0.icon = icon }
}
}

View File

@ -33,7 +33,7 @@ public enum HorizontalAlignment {
.stretch
}
}
#else
#elseif ADWAITA
/// The native alignment.
var nativeAlignment: Core.Alignment {
switch self {

View File

@ -11,7 +11,7 @@ import Core
public enum Icon: Equatable {
/// A custom icon.
case custom(winui: Character, adwaita: String)
case custom(winui: Character, adwaita: String, appkit: String)
/// The airplane icon.
case airplane
/// The edit icon.
@ -69,15 +69,50 @@ public enum Icon: Equatable {
"\u{F8AC}"
case .forward:
"\u{F8AD}"
case let .custom(winui, _):
case let .custom(winui, _, _):
winui
}
return .systemIcon(unicode: unicode)
}
#elseif APPKIT
/// The native icon.
var nativeIcon: Core.Icon {
let name = switch self {
case .airplane:
"airplane"
case .edit:
"pencil"
case .headphones:
"headphones"
case .plus:
"plus"
case .copy:
"doc.on.doc"
case .navigation:
"sidebar.left"
case .warning:
"exclamationmark.triangle"
case .trash:
"trash"
case .play:
"play"
case .document:
"doc"
case .repeat:
"repeat"
case .back:
"backward"
case .forward:
"forward"
case let .custom(_, _, appkit):
appkit
}
return .system(name: name)
}
#else
/// The native icon.
var nativeIcon: Core.Icon {
if case let .custom(_, adwaita) = self {
if case let .custom(_, adwaita, _) = self {
return .custom(name: adwaita)
}
let icon: Core.Icon.DefaultIcon = switch self {

View File

@ -33,7 +33,7 @@ public enum VerticalAlignment {
.stretch
}
}
#else
#elseif ADWAITA
/// The native alignment.
var nativeAlignment: Core.Alignment {
switch self {

View File

@ -17,6 +17,9 @@ public class Aparoksha: AppStorage {
#if WINUI
/// The native app type.
typealias App = WinUIApp
#elseif APPKIT
/// The native app type.
typealias App = MacApp
#else
/// The native app type.
typealias App = AdwaitaApp
@ -41,13 +44,17 @@ public class Aparoksha: AppStorage {
/// - Parameters:
/// - id: The reverse DNS style identifier.
/// - name: The app name.
/// - settings: The settings label.
/// - settings: The settings label, usually "Settings".
/// - about: The text for the app information, usually "About <App>".
/// - developer: The developer.
/// - version: The app's version.
/// - quit: The quit label, usually "Quit <App>".
/// - icon: The app icon.
/// - website: The app's website.
/// - websiteLabel: The label for the website button on macOS and Windows, for example "Website".
/// - issues: The app's URL for issues.
/// - issuesLabel: The label for the issues button on macOS, for example "Issues".
/// - services: The text for the services menu, usually "Services".
public init(
id: String,
name: String,
@ -55,9 +62,13 @@ public class Aparoksha: AppStorage {
about: String,
developer: String,
version: String,
quit: String,
icon: Icon? = nil,
website: URL? = nil,
issues: URL? = nil
websiteLabel: String? = nil,
issues: URL? = nil,
issuesLabel: String? = nil,
services: String? = nil
) {
#if WINUI
app = .init()
@ -69,10 +80,14 @@ public class Aparoksha: AppStorage {
developer: developer,
version: version,
website: website,
websiteLabel: websiteLabel,
issues: issues,
issuesLabel: issuesLabel,
icon: icon,
about: about,
settings: settings
settings: settings,
services: services,
quit: quit
)
}
@ -87,4 +102,21 @@ public class Aparoksha: AppStorage {
app.quit()
}
/// The hide menu's labels on macOS.
/// - Parameters:
/// - hideApp: Hide the app, usually "Hide <App>".
/// - hideOthers: Hide other apps, usually "Hide Others".
/// - showAll: Show all apps, usually "Show All".
/// - Returns: The app object.
public func hideMenu(
hideApp: String,
hideOthers: String,
showAll: String
) -> Self {
appInformation.hide = hideApp
appInformation.hideOthers = hideOthers
appInformation.showAll = showAll
return self
}
}

View File

@ -19,14 +19,28 @@ struct AppInformation {
var version: String
/// The app's website.
var website: URL?
/// The label for the website.
var websiteLabel: String?
/// The app's URL for issues.
var issues: URL?
/// The label for the issues.
var issuesLabel: String?
/// The app icon.
var icon: Icon?
/// The about action's label.
var about: String
/// The settings label.
var settings: String
/// The services label.
var services: String?
/// The hide app label.
var hide: String?
/// The hide others label.
var hideOthers: String?
/// The show all label.
var showAll: String?
/// The quit label.
var quit: String
#if ADWAITA
/// The main menu for GNOME.

114
Sources/Scene/MenuBar.swift Normal file
View File

@ -0,0 +1,114 @@
//
// MenuBar.swift
// Aparoksha
//
// Created by david-swift on 07.12.2024.
//
#if APPKIT
import AppKit
#endif
import Core
/// The menu bar control which will be displayed on macOS.
public struct MenuBar: AparokshaSceneElement {
/// The menu bar's identifier.
public var id = "menu"
/// The menu bar's content.
var content: Body
/// Initialize a menu bar.
/// - Parameter content: The menu bar's content.
public init(@ViewBuilder content: () -> Body = { [] }) {
self.content = content()
}
#if APPKIT
/// Get the menu bar on macOS.
/// - Parameter app: The app.
/// - Returns: The menu bar.
func menuBar(app: Aparoksha) -> Core.MenuBar {
.init(id: id) {
content
} app: {
MenuButton(app.appInformation.about) {
app.app.showAboutWindow()
}
if let services = app.appInformation.services {
Divider()
ServicesMenu(services)
}
if let hide = app.appInformation.hide,
let others = app.appInformation.hideOthers,
let all = app.appInformation.showAll {
Divider()
Core.MenuButton(hide) {
app.app.hide()
}
.keyboardShortcut("h")
Core.MenuButton(others) {
app.app.hideOthers()
}
.keyboardShortcut(.init("h", alt: true))
MenuButton(all) {
app.app.showAll()
}
}
Divider()
Core.MenuButton(app.appInformation.quit) {
app.quit()
}
.keyboardShortcut("q")
} help: {
if let website = app.appInformation.website, let label = app.appInformation.websiteLabel {
Core.MenuButton(label) {
_ = NSWorkspace.shared.open(website)
}
.keyboardShortcut(.init("?"))
}
if let issues = app.appInformation.issues, let label = app.appInformation.issuesLabel {
MenuButton(label) {
_ = NSWorkspace.shared.open(issues)
}
}
}
}
#endif
/// Set up the initial scene storages
/// - Parameter app: The app storage.
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
#if APPKIT
if let aparoksha = app as? Aparoksha {
menuBar(app: aparoksha).setupInitialContainers(app: aparoksha.app)
}
#endif
}
/// The scene storage.
/// - Parameter app: The app storage.
/// - Returns: The scene storage.
public func container<Storage>(app: Storage) -> SceneStorage where Storage: AppStorage {
.init(id: "", pointer: nil) { }
}
/// Update stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - app: The app storage.
/// - updateProperties: Whether to update the properties.
public func update<Storage>(
_ storage: SceneStorage,
app: Storage,
updateProperties: Bool
) where Storage: AppStorage {
#if APPKIT
if let aparoksha = app as? Aparoksha {
menuBar(app: aparoksha).update(storage, app: app, updateProperties: updateProperties)
}
#endif
}
}

View File

@ -10,13 +10,16 @@ import Core
/// A window.
public struct Window: AparokshaSceneElement {
#if WINUI
/// The native window type.
typealias NativeWindow = WinUIWindow
#else
/// The native window type.
typealias NativeWindow = AdwaitaWindow
#endif
#if WINUI
/// The native window type.
typealias NativeWindow = WinUIWindow
#elseif APPKIT
/// The native window type.
typealias NativeWindow = MacWindow
#else
/// The native window type.
typealias NativeWindow = AdwaitaWindow
#endif
/// The window's identifier.
public var id: String
@ -62,6 +65,7 @@ public struct Window: AparokshaSceneElement {
/// The native window.
var native: NativeWindow
#if !APPKIT
/// Whether the default title bar is visible.
var defaultTitlebar: Bool {
get {
@ -71,6 +75,7 @@ public struct Window: AparokshaSceneElement {
native.fields["default-titlebar"] = newValue
}
}
#endif
/// Initialize a window.
/// - Parameter native: The native window.
@ -213,6 +218,21 @@ public struct Window: AparokshaSceneElement {
.extendContentIntoTitleBar(true, gap: app.appInformation.gap)
.frame(width: width?.windowsValue, height: height?.windowsValue)
}
#elseif APPKIT
/// The native window.
func nativeWindow(app: Aparoksha) -> Core.Window {
.init(title, id: id, open: open) { window in
WindowMainView(
content: content,
window: .init(native: window),
app: app,
toolbarItems: toolbarItems,
contentLabel: title,
contentIcon: icon
)
}
.frame(width: width, height: height)
}
#else
/// The native window.
func nativeWindow(app: Aparoksha) -> Core.Window {
@ -283,6 +303,9 @@ struct WindowMainView: View {
} else {
fullContent(app: app)
}
#elseif APPKIT
// TODO: Toolbar for apps without navigation view
fullContent(app: app)
#else
if !window.defaultTitlebar {
fullContent(app: app)
@ -332,6 +355,13 @@ struct ToolbarItem {
visible: active && !settings
)
}
#elseif APPKIT
/// The native button.
func button() -> AnyView {
DisabledWrapper(disabled: !active) {
Core.Button(title, icon: icon.nativeIcon, action: action)
}
}
#else
/// The native button.
func button() -> AnyView {

View File

@ -17,6 +17,8 @@ extension AnyView {
public func padding(_ padding: Int, edges: Set<Edge> = .all) -> AnyView {
#if WINUI
ModifierWrapper(view: Grid { self }, margin: edges.nativeEdges.set(.init(padding.windowsValue)))
#elseif APPKIT
PaddingView(padding: .init(padding), edges: edges.nativeEdges, child: self)
#else
ModifierWrapper(content: self, padding: padding, edges: edges.nativeEdges)
#endif
@ -28,6 +30,9 @@ extension AnyView {
public func valign(_ alignment: VerticalAlignment) -> AnyView {
#if WINUI
ModifierWrapper(view: self, verticalAlignment: alignment.nativeAlignment)
#elseif APPKIT
// TODO: Set alignment macOS
self
#else
ModifierWrapper(content: self, valign: alignment.nativeAlignment)
#endif
@ -39,6 +44,9 @@ extension AnyView {
public func halign(_ alignment: HorizontalAlignment) -> AnyView {
#if WINUI
ModifierWrapper(view: self, horizontalAlignment: alignment.nativeAlignment)
#elseif APPKIT
// TODO: Set alignment macOS
self
#else
ModifierWrapper(content: self, halign: alignment.nativeAlignment)
#endif
@ -50,6 +58,8 @@ extension AnyView {
public func vexpand(_ expand: Bool = true) -> AnyView {
#if WINUI
self
#elseif APPKIT
self
#else
ModifierWrapper(content: self, vexpand: expand)
#endif
@ -61,6 +71,8 @@ extension AnyView {
public func hexpand(_ expand: Bool = true) -> AnyView {
#if WINUI
self
#elseif APPKIT
self
#else
ModifierWrapper(content: self, hexpand: expand)
#endif
@ -80,6 +92,10 @@ extension AnyView {
minWidth: .init(optional: minWidth?.windowsValue),
minHeight: .init(optional: minHeight?.windowsValue)
)
#elseif APPKIT
FrameWrapper(minWidth: .init(optional: minWidth), minHeight: .init(optional: minHeight)) {
self
}
#else
ModifierWrapper(
content: self,
@ -99,6 +115,10 @@ extension AnyView {
view: self,
maxWidth: .init(optional: maxWidth?.windowsValue)
)
#elseif APPKIT
FrameWrapper(maxWidth: .init(optional: maxWidth)) {
self
}
#else
Clamp(vertical: false)
.child {
@ -118,6 +138,10 @@ extension AnyView {
view: self,
maxHeight: .init(optional: maxHeight?.windowsValue)
)
#elseif APPKIT
FrameWrapper(maxHeight: .init(optional: maxHeight)) {
self
}
#else
Clamp(vertical: true)
.child {
@ -134,6 +158,10 @@ extension AnyView {
Card {
self
}
#elseif APPKIT
Card {
self
}
#else
style("card")
#endif

View File

@ -19,7 +19,7 @@ public struct Alert: SimpleView {
var title: String
/// The content.
var content: String
// TODO: Implement on Windows
// TODO: Restrict to certain controls (e.g. text fields) and implement on macOS & Windows
/// The view content.
var extraChild: Body?
/// The identifier.
@ -61,6 +61,33 @@ public struct Alert: SimpleView {
close.1()
visible = false
}
#elseif APPKIT
var dialog = Core.Alert(
title: title,
description: content,
isPresented: $visible,
child: child
)
if let primary {
if primary.1 == .destructive {
dialog = dialog.destructiveButton(primary.0.0, default: primary.1 == .suggested, action: primary.0.1)
} else {
dialog = dialog.button(primary.0.0, default: primary.1 == .suggested, action: primary.0.1)
}
}
if let secondary {
if secondary.1 == .destructive {
dialog = dialog.destructiveButton(
secondary.0.0,
default: secondary.1 == .suggested,
action: secondary.0.1
)
} else {
dialog = dialog.button(secondary.0.0, default: secondary.1 == .suggested, action: secondary.0.1)
}
}
dialog = dialog.cancelButton(close.0, action: close.1)
return dialog
#else
var dialog = AlertDialog(
visible: $visible,

View File

@ -19,6 +19,18 @@ public struct Carousel<Element>: SimpleView where Element: Identifiable {
public var view: Body {
#if WINUI
Core.FlipView(elements, content: content)
#elseif APPKIT
Core.ScrollView(.horizontal) {
Core.HStack {
Core.Spacer()
Core.ForEach(elements, horizontal: true) { element in
content(element)
}
Core.Spacer()
}
.padding(20, edges: .horizontal)
}
.hideScrollIndicators()
#else
Core.Carousel(elements, content: content)
#endif

View File

@ -50,6 +50,34 @@ public struct FlatNavigation<Item>: View where Item: FlatNavigationItem {
.onAppear {
app?.removeDefaultTitlebar()
}
#elseif APPKIT
Core.NavigationSplitView {
Core.List(
items.map { ListItem(item: $0) },
selection: .init {
selectedItem.description
} set: { newValue in
if let item = items.first(where: { $0.description == newValue }) {
selectedItem = item
}
}
) { item in
Core.Label(item.item.description, icon: item.item.icon.nativeIcon)
}
} detail: {
ToolbarView(
child: ToolbarView(child: content, type: .end) {
(toolbarItems ?? []).filter { !$0.leftAlign }.map { item in
item.button()
}
},
type: .start
) {
(toolbarItems ?? []).filter { $0.leftAlign }.map { item in
item.button()
}
}
}
#else
if useViewSwitcher {
AdwViewSwitcher(
@ -198,12 +226,11 @@ public struct FlatNavigation<Item>: View where Item: FlatNavigationItem {
/// - stack: The hierarchical navigation stack.
/// - content: The hierarchical content.
/// - Returns: The complex view.
@ViewBuilder
public func complexNavigation<StackItem>(
stack: Binding<NavigationStack<StackItem>>,
@ViewBuilder content: @escaping (StackItem) -> Body
) -> Body where StackItem: Equatable, StackItem: CustomStringConvertible {
#if WINUI
#if WINUI || APPKIT
let flat = Self(items, selection: $selectedItem) {
HierarchicalNavigation(stack: stack, initialPageLabel: navigationTitle ?? "") { item in
content(item)
@ -214,12 +241,14 @@ public struct FlatNavigation<Item>: View where Item: FlatNavigationItem {
.forceSplitView(forceSidebar)
.navigationTitle(navigationTitle)
if let primary = primaryActionProperties {
flat
.primaryAction(primary.title, icon: primary.icon, disabled: !primary.active) {
primary.action()
}
return [
flat
.primaryAction(primary.title, icon: primary.icon, disabled: !primary.active) {
primary.action()
}
]
} else {
flat
return [flat]
}
#else
HierarchicalNavigation(stack: stack, initialPageLabel: navigationTitle ?? "") { item in
@ -368,7 +397,7 @@ struct WinUINavigationItem: NavigationViewItem {
}
}
#else
#elseif ADWAITA
/// The view switcher for GNOME.
struct AdwViewSwitcher<Item>: View where Item: FlatNavigationItem {
@ -503,13 +532,13 @@ struct AdwNavigationSplitView<Item>: View where Item: FlatNavigationItem {
var sidebar: AnyView {
Core.ScrollView {
let selectedItem: Binding<String> = Binding {
AdwListItem(item: self.selectedItem).id
ListItem(item: self.selectedItem).id
} set: { newValue in
if let item = items.first(where: { $0.description == newValue }) {
self.selectedItem = item
}
}
List(items.map { AdwListItem(item: $0) }, selection: selectedItem) { item in
List(items.map { ListItem(item: $0) }, selection: selectedItem) { item in
let standardPadding = 6
let doublePadding = 12
HStack {
@ -568,17 +597,19 @@ struct AdwViewSwitcherItem<Item>: ViewSwitcherOption where Item: FlatNavigationI
}
}
#endif
/// An item for the Adwaita list in the sidebar.
struct AdwListItem<Item>: Identifiable where Item: FlatNavigationItem {
/// An item for the list in the sidebar.
struct ListItem<Item>: Identifiable, CustomStringConvertible where Item: FlatNavigationItem {
/// The item.
var item: Item
/// The item's identifier.
var id: String { item.description }
/// The item's description.
var description: String { item.description }
}
#endif
/// A dynamic item for the flat navigation view.
public protocol DynamicFlatNavigationItem: CustomStringConvertible {

View File

@ -21,7 +21,23 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
/// Hide the toolbar.
var hideToolbar = false
#if ADWAITA
#if WinUI
/// The navigation path.
@Environment("path")
private var path: Binding<[NavigationSelection<WinUINavigationItem>]>?
/// The currently selected item.
var selectedItem: Item? {
if case let .custom(item: item) = path?.wrappedValue.last {
return item.item as? Item
}
return nil
}
#elseif APPKIT
/// The navigation stack.
@State private var macStack: Core.NavigationPath<Item> = .init()
/// The item presented in a dialog.
@State private var dialog: Item?
#else
/// The Adwaita stack.
@State private var adwStack: NavigationView.NavigationStack<Item> = .init()
/// The item presented in a dialog.
@ -37,17 +53,6 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
/// The window object.
@Environment("window")
private var window: Window.Window?
#else
/// The navigation path.
@Environment("path")
private var path: Binding<[NavigationSelection<WinUINavigationItem>]>?
/// The currently selected item.
var selectedItem: Item? {
if case let .custom(item: item) = path?.wrappedValue.last {
return item.item as? Item
}
return nil
}
#endif
/// The view.
@ -74,6 +79,33 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
initialView
}
}
#elseif APPKIT
/// The native AppKit view.
@ViewBuilder var nativeView: Body {
SheetWrapper(
isPresented: .init {
dialog != nil
} set: { newValue in
if !newValue { dialog = nil }
}
) {
Core.NavigationStack(path: $macStack) { component in
childView(component)
} initialView: {
initialView
}
.onUpdate {
update()
}
} dialog: {
VStack {
if let dialog {
childView(dialog)
}
}
.freeze(dialog == nil)
}
}
#else
/// The native Adwaita view.
@ViewBuilder var nativeView: Body {
@ -145,6 +177,8 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
case .pop:
_ = path?.wrappedValue.popLast()
}
#elseif APPKIT
executeMacAction(action: action)
#else
Task {
switch action {
@ -166,13 +200,35 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
#endif
}
#if APPKIT
/// Execute the action on macOS.
/// - Parameter action: The action.
func executeMacAction(action: NavigationStack<Item>.Action) {
Task { @MainActor in
switch action {
case let .push(item: item, target: target):
switch target {
case .page:
macStack.push(item)
case .dialog:
dialog = item
}
case .pop:
if dialog != nil {
dialog = nil
} else {
macStack.pop()
}
}
}
}
#endif
#if ADWAITA
/// The content for an item.
/// - Parameter item: The item.
/// - Returns: The content.
func content(item: Item?) -> AnyView {
#if WINUI
[]
#else
ToolbarView()
.content {
if let item {
@ -182,8 +238,9 @@ public struct HierarchicalNavigation<Item>: View where Item: Equatable, Item: Cu
.top {
HeaderBar.empty()
}
#endif
.freeze(item == nil)
}
#endif
/// Hide the toolbar on Adwaita.
/// - Returns: The view.

View File

@ -24,18 +24,24 @@ public struct Button: SimpleView {
if let label, let icon {
#if WINUI
return [Core.Button(icon: icon.nativeIcon, label: label, action: handler)]
#elseif APPKIT
return [Core.Button(label, icon: icon.nativeIcon, action: handler)]
#else
return [Core.Button(label, icon: icon.nativeIcon, handler: handler).style("pill", active: pill)]
#endif
} else if let label {
#if WINUI
return [Core.Button(label, action: handler)]
#elseif APPKIT
return [Core.Button(label, action: handler)]
#else
return [Core.Button(label, handler: handler).style("pill", active: pill)]
#endif
} else if let icon {
#if WINUI
return [Core.Button(icon: icon.nativeIcon, action: handler)]
#elseif APPKIT
return [Core.Button(icon: icon.nativeIcon, action: handler)]
#else
return [Core.Button(icon: icon.nativeIcon, handler: handler).style("pill", active: pill)]
#endif

View File

@ -15,15 +15,9 @@ public struct HStack: SimpleView {
/// The content view.
public var view: Body {
#if WINUI
Core.HStack {
content
}
#else
Core.HStack {
content
}
#endif
}
/// Initialize the horizontal stack.

View File

@ -8,8 +8,12 @@
import Core
/// The button view.
public struct Menu: SimpleView {
public struct Menu: View {
#if APPKIT
/// Whether the menu is visible.
@State private var showMenu: Signal = .init()
#endif
/// The button's label.
var label: String?
/// The button's icon.
@ -21,6 +25,19 @@ public struct Menu: SimpleView {
/// The content view.
public var view: Body {
#if APPKIT
// swiftlint:disable trailing_closure
MenuWrapper(
content: {
Core.Button(label ?? "", icon: icon?.nativeIcon) {
showMenu.signal()
}
},
present: $showMenu,
menu: { menu }
)
// swiftlint:enable trailing_closure
#else
if let label, let icon {
#if WINUI
// TODO: Menu with icon
@ -43,6 +60,7 @@ public struct Menu: SimpleView {
#endif
}
return []
#endif
}
/// Initialize a button.

View File

@ -21,6 +21,9 @@ public struct Text: SimpleView {
#if WINUI
Core.Text(label)
.style(style.style)
#elseif APPKIT
Core.Text(label)
.font(style.font)
#else
Core.Text(label)
.style(style.style)
@ -59,6 +62,22 @@ public struct Text: SimpleView {
.caption
}
}
#elseif APPKIT
/// The AppKit font.
var font: Core.Font {
switch self {
case .title1:
.title
case .title2:
.title2
case .title3:
.title3
case .body:
.body
case .caption:
.caption
}
}
#else
/// The Adwaita style.
var style: String {

View File

@ -15,15 +15,9 @@ public struct VStack: SimpleView {
/// The content view.
public var view: Body {
#if WINUI
Core.VStack {
content
}
#else
Core.VStack {
content
}
#endif
}
/// Initialize the vertical stack.

View File

@ -19,11 +19,23 @@ struct Demo: App {
about: "About Demo",
developer: "Aparoksha",
version: "main",
quit: "Quit Demo",
website: .init(string: "https://aparoksha.dev/")!,
issues: .init(string: "https://forums.aparoksha.dev/")!
websiteLabel: "Website",
issues: .init(string: "https://forums.aparoksha.dev/")!,
issuesLabel: "Issues",
services: "Services"
)
.hideMenu(hideApp: "Hide Demo", hideOthers: "Hide Others", showAll: "Show All")
var scene: Scene {
MenuBar {
Submenu("Test") {
MenuButton("Test") {
print("Test")
}
}
}
Window("Demo", icon: .headphones, id: "main") {
ContentView()
}
@ -31,6 +43,10 @@ struct Demo: App {
Text("Hi")
.padding(10)
}
.frame(width: .constant(300), height: .constant(200))
.leadingToolbarItem("Test", icon: .airplane) {
print("Plane")
}
}
}
@ -141,7 +157,7 @@ enum Item: String, FlatNavigationItem, CaseIterable, Equatable, Codable {
case .navigation:
.navigation
case .carousel:
.custom(winui: "🤣", adwaita: "dev.aparoksha.Demo.carousel-symbolic")
.custom(winui: "🤣", adwaita: "dev.aparoksha.Demo.carousel-symbolic", appkit: "slider.horizontal.below.rectangle")
case .alert:
.warning
}