From ac0c77598570ae6ed20448d7a522ae76088b7641 Mon Sep 17 00:00:00 2001 From: david-swift Date: Sun, 22 Oct 2023 16:39:46 +0200 Subject: [PATCH] Add support for menus --- Documentation/Reference/README.md | 11 +- .../Reference/extensions/MenuItem.md | 8 ++ .../Reference/extensions/MenuItemGroup.md | 9 ++ Documentation/Reference/protocols/MenuItem.md | 14 +++ .../Reference/protocols/MenuItemGroup.md | 10 ++ Documentation/Reference/structs/Menu.md | 56 ++++++++++ Documentation/Reference/structs/MenuButton.md | 48 +++++++++ .../Reference/structs/MenuSection.md | 24 +++++ Documentation/Reference/structs/Submenu.md | 30 ++++++ Documentation/Reference/structs/Window.md | 26 ++++- .../Reference/typealiases/MenuBuilder.md | 5 + .../Reference/typealiases/MenuContent.md | 5 + Icons/Demo.png | Bin 26595 -> 25720 bytes README.md | 15 ++- Sources/Adwaita/Menu/MenuButton.swift | 58 +++++++++++ Sources/Adwaita/Menu/MenuSection.swift | 35 +++++++ Sources/Adwaita/Menu/Submenu.swift | 40 +++++++ .../Model/User Interface/{ => App}/App.swift | 0 .../User Interface/{ => App}/GTUIApp.swift | 0 .../Model/User Interface/Menu/MenuItem.swift | 27 +++++ .../User Interface/Menu/MenuItemGroup.swift | 37 +++++++ .../User Interface/{ => View}/View.swift | 0 .../{ => View}/ViewBuilder.swift | 0 .../{ => View}/ViewStorage.swift | 0 .../User Interface/{ => View}/Widget.swift | 0 .../{ => Window}/GTUIApplicationWindow.swift | 0 .../{ => Window}/GTUIWindow.swift | 0 .../{ => Window}/WindowScene.swift | 0 .../{ => Window}/WindowSceneGroup.swift | 0 .../{ => Window}/WindowStorage.swift | 0 Sources/Adwaita/View/Menu.swift | 98 ++++++++++++++++++ Tests/Demo.swift | 24 ++++- user-manual/Basics/KeyboardShortcuts.md | 23 ++++ 33 files changed, 593 insertions(+), 10 deletions(-) create mode 100644 Documentation/Reference/extensions/MenuItem.md create mode 100644 Documentation/Reference/extensions/MenuItemGroup.md create mode 100644 Documentation/Reference/protocols/MenuItem.md create mode 100644 Documentation/Reference/protocols/MenuItemGroup.md create mode 100644 Documentation/Reference/structs/Menu.md create mode 100644 Documentation/Reference/structs/MenuButton.md create mode 100644 Documentation/Reference/structs/MenuSection.md create mode 100644 Documentation/Reference/structs/Submenu.md create mode 100644 Documentation/Reference/typealiases/MenuBuilder.md create mode 100644 Documentation/Reference/typealiases/MenuContent.md create mode 100644 Sources/Adwaita/Menu/MenuButton.swift create mode 100644 Sources/Adwaita/Menu/MenuSection.swift create mode 100644 Sources/Adwaita/Menu/Submenu.swift rename Sources/Adwaita/Model/User Interface/{ => App}/App.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => App}/GTUIApp.swift (100%) create mode 100644 Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift create mode 100644 Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift rename Sources/Adwaita/Model/User Interface/{ => View}/View.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => View}/ViewBuilder.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => View}/ViewStorage.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => View}/Widget.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => Window}/GTUIApplicationWindow.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => Window}/GTUIWindow.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => Window}/WindowScene.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => Window}/WindowSceneGroup.swift (100%) rename Sources/Adwaita/Model/User Interface/{ => Window}/WindowStorage.swift (100%) create mode 100644 Sources/Adwaita/View/Menu.swift diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index 3bf0af7..b54e66b 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -3,6 +3,8 @@ ## Protocols - [App](protocols/App.md) +- [MenuItem](protocols/MenuItem.md) +- [MenuItemGroup](protocols/MenuItemGroup.md) - [StateProtocol](protocols/StateProtocol.md) - [View](protocols/View.md) - [Widget](protocols/Widget.md) @@ -11,7 +13,6 @@ ## Structs -- [ApplicationWindow](structs/ApplicationWindow.md) - [Binding](structs/Binding.md) - [Button](structs/Button.md) - [Clamp](structs/Clamp.md) @@ -20,11 +21,15 @@ - [HeaderBar](structs/HeaderBar.md) - [InspectorWrapper](structs/InspectorWrapper.md) - [List](structs/List.md) +- [Menu](structs/Menu.md) +- [MenuButton](structs/MenuButton.md) +- [MenuSection](structs/MenuSection.md) - [NavigationSplitView](structs/NavigationSplitView.md) - [ScrollView](structs/ScrollView.md) - [State](structs/State.md) - [StateWrapper](structs/StateWrapper.md) - [StatusPage](structs/StatusPage.md) +- [Submenu](structs/Submenu.md) - [Text](structs/Text.md) - [ToolbarView](structs/ToolbarView.md) - [UpdateObserver](structs/UpdateObserver.md) @@ -49,6 +54,8 @@ - [App](extensions/App.md) - [Array](extensions/Array.md) +- [MenuItem](extensions/MenuItem.md) +- [MenuItemGroup](extensions/MenuItemGroup.md) - [NativeWidgetPeer](extensions/NativeWidgetPeer.md) - [String](extensions/String.md) - [View](extensions/View.md) @@ -61,6 +68,8 @@ - [Body](typealiases/Body.md) - [GTUIApplicationWindow](typealiases/GTUIApplicationWindow.md) - [GTUIWindow](typealiases/GTUIWindow.md) +- [MenuBuilder](typealiases/MenuBuilder.md) +- [MenuContent](typealiases/MenuContent.md) - [Scene](typealiases/Scene.md) - [SceneBuilder](typealiases/SceneBuilder.md) diff --git a/Documentation/Reference/extensions/MenuItem.md b/Documentation/Reference/extensions/MenuItem.md new file mode 100644 index 0000000..81dfbdb --- /dev/null +++ b/Documentation/Reference/extensions/MenuItem.md @@ -0,0 +1,8 @@ +**EXTENSION** + +# `MenuItem` + +## Properties +### `content` + +The menu item's content is itself. diff --git a/Documentation/Reference/extensions/MenuItemGroup.md b/Documentation/Reference/extensions/MenuItemGroup.md new file mode 100644 index 0000000..435e04e --- /dev/null +++ b/Documentation/Reference/extensions/MenuItemGroup.md @@ -0,0 +1,9 @@ +**EXTENSION** + +# `MenuItemGroup` + +## Methods +### `addMenuItems(menu:app:window:)` + +Add the menu items described by the group to a menu. +- Parameter menu: The menu. diff --git a/Documentation/Reference/protocols/MenuItem.md b/Documentation/Reference/protocols/MenuItem.md new file mode 100644 index 0000000..c58eefa --- /dev/null +++ b/Documentation/Reference/protocols/MenuItem.md @@ -0,0 +1,14 @@ +**PROTOCOL** + +# `MenuItem` + +A structure representing the content for a certain menu item type. + +## Methods +### `addMenuItem(menu:app:window:)` + +Add the menu item to a certain menu. +- Parameters: + - menu: The menu. + - app: The application containing the menu. + - window: The application window containing the menu. diff --git a/Documentation/Reference/protocols/MenuItemGroup.md b/Documentation/Reference/protocols/MenuItemGroup.md new file mode 100644 index 0000000..eda9fe4 --- /dev/null +++ b/Documentation/Reference/protocols/MenuItemGroup.md @@ -0,0 +1,10 @@ +**PROTOCOL** + +# `MenuItemGroup` + +A structure conforming to `MenuItemGroup` can be added to the content accepting a menu. + +## Properties +### `content` + +The menu's content. diff --git a/Documentation/Reference/structs/Menu.md b/Documentation/Reference/structs/Menu.md new file mode 100644 index 0000000..6fddd0b --- /dev/null +++ b/Documentation/Reference/structs/Menu.md @@ -0,0 +1,56 @@ +**STRUCT** + +# `Menu` + +A menu button widget. + +## Properties +### `label` + +The button's label. + +### `icon` + +The button's icon. + +### `content` + +The menu's content. + +### `app` + +The application. + +### `window` + +The window. + +## Methods +### `init(_:icon:app:window:content:)` + +Initialize a menu button. +- Parameters: + - label: The button's label. + - icon: The button's icon. + - app: The application. + - window: The application window. + - content: The menu's content. + +### `init(_:app:window:content:)` + +Initialize a menu button. +- Parameters: + - label: The buttons label. + - app: The application. + - window: The application window. + - content: The menu's content. + +### `update(_:)` + +Update a button's view storage. +- Parameter storage: The view storage. + +### `container()` + +Get a button's view storage. +- Returns: The button's view storage. diff --git a/Documentation/Reference/structs/MenuButton.md b/Documentation/Reference/structs/MenuButton.md new file mode 100644 index 0000000..7cbe9db --- /dev/null +++ b/Documentation/Reference/structs/MenuButton.md @@ -0,0 +1,48 @@ +**STRUCT** + +# `MenuButton` + +A button widget for menus. + +## Properties +### `label` + +The button's label. + +### `handler` + +The button's action handler. + +### `shortcut` + +The keyboard shortcut. + +### `preferApplicationWindow` + +Whether to prefer adding the action to the application window. + +## Methods +### `init(_:window:handler:)` + +Initialize a menu button. +- Parameters: + - label: The buttons label. + - window: Whether to prefer adding the action to the application window. + - handler: The button's action handler. + +### `addMenuItem(menu:app:window:)` + +Add the button to a menu. +- Parameters: + - menu: The menu. + - app: The application containing the menu. + - window: The application window containing the menu. + +### `keyboardShortcut(_:)` + +Create a keyboard shortcut for an application from a button. + +Note that the keyboard shortcut is available after the view has been visible for the first time. +- Parameters: + - shortcut: The keyboard shortcut. +- Returns: The button. diff --git a/Documentation/Reference/structs/MenuSection.md b/Documentation/Reference/structs/MenuSection.md new file mode 100644 index 0000000..73e143b --- /dev/null +++ b/Documentation/Reference/structs/MenuSection.md @@ -0,0 +1,24 @@ +**STRUCT** + +# `MenuSection` + +A section for menus. + +## Properties +### `sectionContent` + +The content of the section. + +## Methods +### `init(content:)` + +Initialize a section for menus. +- Parameter content: The content of the section. + +### `addMenuItem(menu:app:window:)` + +Add the section to a menu. +- Parameters: + - menu: The menu. + - app: The application containing the menu. + - window: The application window containing the menu. diff --git a/Documentation/Reference/structs/Submenu.md b/Documentation/Reference/structs/Submenu.md new file mode 100644 index 0000000..19e302a --- /dev/null +++ b/Documentation/Reference/structs/Submenu.md @@ -0,0 +1,30 @@ +**STRUCT** + +# `Submenu` + +A submenu widget. + +## Properties +### `label` + +The submenu's label. + +### `submenuContent` + +The content of the submenu. + +## Methods +### `init(_:content:)` + +Initialize a submenu. +- Parameters: + - label: The submenu's label. + - content: The content of the submenu. + +### `addMenuItem(menu:app:window:)` + +Add the submenu to a menu. +- Parameters: + - menu: The menu. + - app: The application containing the menu. + - window: The application window containing the menu. diff --git a/Documentation/Reference/structs/Window.md b/Documentation/Reference/structs/Window.md index 8003790..0a56a26 100644 --- a/Documentation/Reference/structs/Window.md +++ b/Documentation/Reference/structs/Window.md @@ -2,7 +2,7 @@ # `Window` -A structure representing a simple window type. +A structure representing an application window type. Note that multiple instances of a window can be opened at the same time. @@ -19,6 +19,10 @@ The window's content. Whether an instance of the window type should be opened when the app is starting up. +### `shortcuts` + +The keyboard shortcuts. + ### `appShortcuts` The keyboard shortcuts on the app level. @@ -53,6 +57,22 @@ Get the storage of the content view. ### `update(_:app:)` Update a window storage's content. +- Parameter storage: The storage to update. + +### `keyboardShortcut(_:action:)` + +Add a keyboard shortcut. - Parameters: - - storage: The storage to update. - - app: The application. + - shortcut: The keyboard shortcut. + - action: The closure to execute when the keyboard shortcut is pressed. +- Returns: The window. + +### `updateShortcuts(window:)` + +Update the keyboard shortcuts. +- Parameter window: The application window. + +### `closeShortcut()` + +Add the shortcut "w" which closes the window. +- Returns: The window. diff --git a/Documentation/Reference/typealiases/MenuBuilder.md b/Documentation/Reference/typealiases/MenuBuilder.md new file mode 100644 index 0000000..566c55e --- /dev/null +++ b/Documentation/Reference/typealiases/MenuBuilder.md @@ -0,0 +1,5 @@ +**TYPEALIAS** + +# `MenuBuilder` + +A builder for the `MenuContent` \ No newline at end of file diff --git a/Documentation/Reference/typealiases/MenuContent.md b/Documentation/Reference/typealiases/MenuContent.md new file mode 100644 index 0000000..98f7574 --- /dev/null +++ b/Documentation/Reference/typealiases/MenuContent.md @@ -0,0 +1,5 @@ +**TYPEALIAS** + +# `MenuContent` + +`MenuContent` is an array of menu item groups. \ No newline at end of file diff --git a/Icons/Demo.png b/Icons/Demo.png index eb77fb1be346dd5b41c0e7234da8e974342bde9f..32f8e1cc553309fea0d1b5b739e289dafb5dd8e4 100644 GIT binary patch literal 25720 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 R+I>(lRzKKU(9Jv({tBtjB2@qY literal 26595 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%0A>@-5a@nG7g2A@T-Sel{;^Hd6HesO*3%X(Q}u z2;#)}bUvO&lPR4`Gdxkn9bfjO`m{rpcaw)D`Fi-uO^)Y6j}~fJmjfhgd8a&xs{znp z_kmPT2{aAjoq0EVdxEf{K2i}sfBamzkZoi9M3B^wP^(){FQ%9`xtx{K_6o$wZoK3R z6_tTA23`kQi2BfEhLds=DbfDbr&{UZ+GSNr+LZc|^2}>EGvkvva24y6>2oQ8+TOWR zaE`u?Bzt3<%k$ygI9m3%^Z@<$3)2JktnX4uIJ-*s+5@SQh7ZsGFt6&ocRsXg9ZtXu zYB%W)Hr!0C{C=os?G(<7PfkXe64&rFyDlQ;RO{5PnL+a3E+>kzsxqMQs4meiPr+{Q zJJ%HRvd5=6SPm-S0~sUy0~Km3o6u+0cgPttku7v(;Wix!Cyxgn!v~U?CzsQIGO>v+ z*W5`bYrF6&Bo$gA)21G^!x57|MZ5p zMzx)N4BEd`TF>fwG|x2^SV7&*G+0D_K=-ZuGN<=S=L>=8o-IzJy(E3iIM1&mNjw6* zhb4w~Xc}u46|nLAy#MvU)%SzCA}2@RuGV&cl^Tj9K#OO#k{)ktHMGmla(Z;eePQJ_ zB{Ax#EcA~wgCVOew)}PYdEs<5aX6rB2kX1dCU1w=WPcl#Uvf#ps05Rs74cS_J9|s% zYV<^@>O%%zh3qVeE9Yh` zoHE$kIiX3K5N%^%iFM+3qlBe5<75nDW zJRI*-EpY2RA@uZ2UJB3q4U2xK_4ja8UXMVWoly+H?uhi5sdPW1!aBq(ibH*oXWL3pwP&nETz`^yLIMzRzSngfa zZojLar!Q^&^OLxAcHUK^0yBI4LUr2<8OVz<8h1rR-!%11OI||K6SecAsbM7fy*v|) zV{*{>V?vViTU{ Void + /// The keyboard shortcut. + var shortcut = "" + /// Whether to prefer adding the action to the application window. + var preferApplicationWindow: Bool + + /// Initialize a menu button. + /// - Parameters: + /// - label: The buttons label. + /// - window: Whether to prefer adding the action to the application window. + /// - handler: The button's action handler. + public init(_ label: String, window: Bool = true, handler: @escaping () -> Void) { + self.label = label + preferApplicationWindow = window + self.handler = handler + } + + /// Add the button to a menu. + /// - Parameters: + /// - menu: The menu. + /// - app: The application containing the menu. + /// - window: The application window containing the menu. + public func addMenuItem(menu: GTUI.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + if let window, preferApplicationWindow { + _ = menu.append(label, window: window, shortcut: shortcut, handler: handler) + } else { + _ = menu.append(label, app: app, shortcut: shortcut, handler: handler) + } + } + + /// Create a keyboard shortcut for an application from a button. + /// + /// Note that the keyboard shortcut is available after the view has been visible for the first time. + /// - Parameters: + /// - shortcut: The keyboard shortcut. + /// - Returns: The button. + public func keyboardShortcut(_ shortcut: String) -> Self { + var newSelf = self + newSelf.shortcut = shortcut + return newSelf + } + +} diff --git a/Sources/Adwaita/Menu/MenuSection.swift b/Sources/Adwaita/Menu/MenuSection.swift new file mode 100644 index 0000000..9f0309f --- /dev/null +++ b/Sources/Adwaita/Menu/MenuSection.swift @@ -0,0 +1,35 @@ +// +// Submenu.swift +// Adwaita +// +// Created by david-swift on 22.10.23. +// + +import GTUI + +/// A section for menus. +public struct MenuSection: MenuItem { + + /// The content of the section. + var sectionContent: MenuContent + + /// Initialize a section for menus. + /// - Parameter content: The content of the section. + public init(@MenuBuilder content: () -> MenuContent) { + self.sectionContent = content() + } + + /// Add the section to a menu. + /// - Parameters: + /// - menu: The menu. + /// - app: The application containing the menu. + /// - window: The application window containing the menu. + public func addMenuItem(menu: GTUI.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + let section = GTUI.Menu() + _ = menu.append("", section: section) + for element in sectionContent { + element.addMenuItems(menu: section, app: app, window: window) + } + } + +} diff --git a/Sources/Adwaita/Menu/Submenu.swift b/Sources/Adwaita/Menu/Submenu.swift new file mode 100644 index 0000000..c125995 --- /dev/null +++ b/Sources/Adwaita/Menu/Submenu.swift @@ -0,0 +1,40 @@ +// +// Submenu.swift +// Adwaita +// +// Created by david-swift on 22.10.23. +// + +import GTUI + +/// A submenu widget. +public struct Submenu: MenuItem { + + /// The submenu's label. + var label: String + /// The content of the submenu. + var submenuContent: MenuContent + + /// Initialize a submenu. + /// - Parameters: + /// - label: The submenu's label. + /// - content: The content of the submenu. + public init(_ label: String, @MenuBuilder content: () -> MenuContent) { + self.label = label + self.submenuContent = content() + } + + /// Add the submenu to a menu. + /// - Parameters: + /// - menu: The menu. + /// - app: The application containing the menu. + /// - window: The application window containing the menu. + public func addMenuItem(menu: GTUI.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + let submenu = GTUI.Menu() + _ = menu.append(label, submenu: submenu) + for element in submenuContent { + element.addMenuItems(menu: submenu, app: app, window: window) + } + } + +} diff --git a/Sources/Adwaita/Model/User Interface/App.swift b/Sources/Adwaita/Model/User Interface/App/App.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/App.swift rename to Sources/Adwaita/Model/User Interface/App/App.swift diff --git a/Sources/Adwaita/Model/User Interface/GTUIApp.swift b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/GTUIApp.swift rename to Sources/Adwaita/Model/User Interface/App/GTUIApp.swift diff --git a/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift b/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift new file mode 100644 index 0000000..f5a112d --- /dev/null +++ b/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift @@ -0,0 +1,27 @@ +// +// MenuItem.swift +// Adwaita +// +// Created by david-swift on 22.10.23. +// + +import GTUI + +/// A structure representing the content for a certain menu item type. +public protocol MenuItem: MenuItemGroup { + + /// Add the menu item to a certain menu. + /// - Parameters: + /// - menu: The menu. + /// - app: The application containing the menu. + /// - window: The application window containing the menu. + func addMenuItem(menu: GTUI.Menu, app: GTUIApp, window: GTUIApplicationWindow?) + +} + +extension MenuItem { + + /// The menu item's content is itself. + @MenuBuilder public var content: MenuContent { self } + +} diff --git a/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift b/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift new file mode 100644 index 0000000..0bc47ad --- /dev/null +++ b/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift @@ -0,0 +1,37 @@ +// +// MenuItemGroup.swift +// Adwaita +// +// Created by david-swift on 22.10.23. +// + +import GTUI + +/// A structure conforming to `MenuItemGroup` can be added to the content accepting a menu. +public protocol MenuItemGroup { + + /// The menu's content. + @MenuBuilder var content: MenuContent { get } + +} + +extension MenuItemGroup { + + /// Add the menu items described by the group to a menu. + /// - Parameter menu: The menu. + func addMenuItems(menu: GTUI.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + for element in content { + if let item = element as? MenuItem { + item.addMenuItem(menu: menu, app: app, window: window) + } else { + element.addMenuItems(menu: menu, app: app, window: window) + } + } + } + +} + +/// `MenuContent` is an array of menu item groups. +public typealias MenuContent = [MenuItemGroup] +/// A builder for the `MenuContent` +public typealias MenuBuilder = ArrayBuilder diff --git a/Sources/Adwaita/Model/User Interface/View.swift b/Sources/Adwaita/Model/User Interface/View/View.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/View.swift rename to Sources/Adwaita/Model/User Interface/View/View.swift diff --git a/Sources/Adwaita/Model/User Interface/ViewBuilder.swift b/Sources/Adwaita/Model/User Interface/View/ViewBuilder.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/ViewBuilder.swift rename to Sources/Adwaita/Model/User Interface/View/ViewBuilder.swift diff --git a/Sources/Adwaita/Model/User Interface/ViewStorage.swift b/Sources/Adwaita/Model/User Interface/View/ViewStorage.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/ViewStorage.swift rename to Sources/Adwaita/Model/User Interface/View/ViewStorage.swift diff --git a/Sources/Adwaita/Model/User Interface/Widget.swift b/Sources/Adwaita/Model/User Interface/View/Widget.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/Widget.swift rename to Sources/Adwaita/Model/User Interface/View/Widget.swift diff --git a/Sources/Adwaita/Model/User Interface/GTUIApplicationWindow.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/GTUIApplicationWindow.swift rename to Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift diff --git a/Sources/Adwaita/Model/User Interface/GTUIWindow.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/GTUIWindow.swift rename to Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift diff --git a/Sources/Adwaita/Model/User Interface/WindowScene.swift b/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/WindowScene.swift rename to Sources/Adwaita/Model/User Interface/Window/WindowScene.swift diff --git a/Sources/Adwaita/Model/User Interface/WindowSceneGroup.swift b/Sources/Adwaita/Model/User Interface/Window/WindowSceneGroup.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/WindowSceneGroup.swift rename to Sources/Adwaita/Model/User Interface/Window/WindowSceneGroup.swift diff --git a/Sources/Adwaita/Model/User Interface/WindowStorage.swift b/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift similarity index 100% rename from Sources/Adwaita/Model/User Interface/WindowStorage.swift rename to Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift diff --git a/Sources/Adwaita/View/Menu.swift b/Sources/Adwaita/View/Menu.swift new file mode 100644 index 0000000..6fbb504 --- /dev/null +++ b/Sources/Adwaita/View/Menu.swift @@ -0,0 +1,98 @@ +// +// Menu.swift +// Adwaita +// +// Created by david-swift on 21.10.23. +// + +import GTUI + +/// A menu button widget. +public struct Menu: Widget { + + /// The button's label. + var label: String? + /// The button's icon. + var icon: Icon? + /// The menu's content. + var content: MenuContent + /// The application. + var app: GTUIApp + /// The window. + var window: GTUIApplicationWindow? + + // swiftlint:disable function_default_parameter_at_end + /// Initialize a menu button. + /// - Parameters: + /// - label: The button's label. + /// - icon: The button's icon. + /// - app: The application. + /// - window: The application window. + /// - content: The menu's content. + public init( + _ label: String? = nil, + icon: Icon, + app: GTUIApp, + window: GTUIApplicationWindow?, + @MenuBuilder content: () -> MenuContent + ) { + self.label = label + self.icon = icon + self.app = app + self.window = window + self.content = content() + } + // swiftlint:enable function_default_parameter_at_end + + /// Initialize a menu button. + /// - Parameters: + /// - label: The buttons label. + /// - app: The application. + /// - window: The application window. + /// - content: The menu's content. + public init( + _ label: String, + app: GTUIApp, + window: GTUIApplicationWindow?, + @MenuBuilder content: () -> MenuContent + ) { + self.label = label + self.app = app + self.window = window + self.content = content() + } + + /// Update a button's view storage. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + if let button = storage.view as? GTUI.MenuButton { + let content = button.getContent() + if let label { + if icon == nil { + button.setLabel(label) + } else { + content?.setLabel(label) + } + } + if let icon { + content?.setIcon(icon) + } + } + } + + /// Get a button's view storage. + /// - Returns: The button's view storage. + public func container() -> ViewStorage { + let button: GTUI.MenuButton + if let icon { + button = .init(label, icon: icon) + } else { + button = .init(label ?? .init()) + } + for element in content { + element.addMenuItems(menu: button.getMenu(), app: app, window: window) + } + return .init(button) + } + +} diff --git a/Tests/Demo.swift b/Tests/Demo.swift index 1847d30..b0c9deb 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -20,9 +20,6 @@ struct Demo: App { Window(id: "main") { window in DemoContent(window: window, app: app) } - .appKeyboardShortcut("n".ctrl()) { $0.addWindow("main") } - .closeShortcut() - .quitShortcut() HelperWindows() } @@ -44,7 +41,7 @@ struct Demo: App { struct DemoContent: View { @State private var selection: Page = .welcome - var window: GTUIWindow + var window: GTUIApplicationWindow var app: GTUIApp! var view: Body { @@ -58,7 +55,24 @@ struct Demo: App { .sidebarStyle() } .topToolbar { - HeaderBar.empty() + HeaderBar.end { + Menu(icon: .default(icon: .openMenu), app: app, window: window) { + MenuButton("New Window", window: false) { + app.addWindow("main") + } + .keyboardShortcut("n".ctrl()) + MenuButton("Close Window") { + window.close() + } + .keyboardShortcut("w".ctrl()) + MenuSection { + MenuButton("Quit", window: false) { + app.quit() + } + .keyboardShortcut("q".ctrl()) + } + } + } } .navigationTitle("Demo") } content: { diff --git a/user-manual/Basics/KeyboardShortcuts.md b/user-manual/Basics/KeyboardShortcuts.md index 99c6069..c92bfca 100644 --- a/user-manual/Basics/KeyboardShortcuts.md +++ b/user-manual/Basics/KeyboardShortcuts.md @@ -70,6 +70,29 @@ struct HelloWorld: App { } ``` +## Create Shortcuts from a Menu +The most elegant way for adding keyboard shortcuts is in many cases adding them via menus. +Here is an example using a menu button: +```swift +struct TestView: View { + + var app: GTUIApp + + var view: Body { + Menu(icon: .default(icon: .openMenu), app: app) { + MenuButton("New Window", window: false) { + app.addWindow("main") + } + // Add a keyboard shortcut to the app. + .keyboardShortcut("n".ctrl()) + } + } + +} +``` +Add the keyboard shortcut to a single window by specifying the `window` parameter in the initializer of `Menu`, +and removing `window: false` in the initializer of `MenuButton`. + ## Create Shortcuts from a Button It's possible to easily create a keyboard shortcut from a button. Use `appKeyboardShortcut` instead of `keyboardShortcut` for shortcuts on an application level.