Add support for menus

This commit is contained in:
david-swift 2023-10-22 16:39:46 +02:00
parent af59675089
commit ac0c775985
33 changed files with 593 additions and 10 deletions

View File

@ -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)

View File

@ -0,0 +1,8 @@
**EXTENSION**
# `MenuItem`
## Properties
### `content`
The menu item's content is itself.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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 "<Ctrl>w" which closes the window.
- Returns: The window.

View File

@ -0,0 +1,5 @@
**TYPEALIAS**
# `MenuBuilder`
A builder for the `MenuContent`

View File

@ -0,0 +1,5 @@
**TYPEALIAS**
# `MenuContent`
`MenuContent` is an array of menu item groups.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -78,6 +78,7 @@ If you want to use _Adwaita_ in a project, but there are widgets missing, open a
| VStack | A widget which arranges child widgets into a single column. | GtkBox |
| HStack | A widget which arranges child widgets into a single row. | GtkBox |
| List | A widget which arranges child widgets vertically into rows. | GtkListBox |
| Menu | A widget showing a button that toggles the appearance of a menu. | GtkMenuButton |
| NavigationSplitView | A widget presenting sidebar and content side by side. | AdwNavigationSplitView |
| ScrollView | A container that makes its child scrollable. | GtkScrolledWindow |
| StatusPage | A page with an icon, title, and optionally description and widget.| AdwStatusPage |
@ -134,7 +135,19 @@ If you want to use _Adwaita_ in a project, but there are widgets missing, open a
| Syntax | Description |
| ------------------------------- | --------------------------------------------------------------------------------------- |
| `keyboardShortcut(_:action:)` | Create a keyboard shortcut available in one window. |
| `closeShortcut()` | Create a keyboard shortcut for closing the window with "Ctrl + w". |
| `closeShortcut()` | Create a keyboard shortcut for closing the window with "Ctrl + w".
### Menu Widgets
| Name | Description | Widget |
| -------------------- | ----------------------------------------------------------------- | ---------------------- |
| MenuButton | A button in a menu. | GMenuItem |
| MenuSection | A collection of menu widgets grouped with lines. | GMenuItem |
| Submenu | A collection of menu widgets grouped by navigation. | GMenuItem |
### `MenuButton` Modifiers
| Syntax | Description |
| ------------------------------- | --------------------------------------------------------------------------------------- |
| `keyboardShortcut(_:)` | Assign a keyboard shortcut to the button's action. |
## Installation
### Dependencies

View File

@ -0,0 +1,58 @@
//
// MenuButton.swift
// Adwaita
//
// Created by david-swift on 22.10.23.
//
import GTUI
/// A button widget for menus.
public struct MenuButton: MenuItem {
/// The button's label.
var label: String
/// The button's action handler.
var handler: () -> 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
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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 }
}

View File

@ -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<MenuItemGroup>

View File

@ -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)
}
}

View File

@ -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: {

View File

@ -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.