Add support for keyboard shortcuts

This commit is contained in:
david-swift 2023-10-20 07:39:40 +02:00
parent 921f025e39
commit af59675089
18 changed files with 429 additions and 55 deletions

View File

@ -11,6 +11,7 @@
## Structs
- [ApplicationWindow](structs/ApplicationWindow.md)
- [Binding](structs/Binding.md)
- [Button](structs/Button.md)
- [Clamp](structs/Clamp.md)
@ -58,6 +59,7 @@
## Typealiases
- [Body](typealiases/Body.md)
- [GTUIApplicationWindow](typealiases/GTUIApplicationWindow.md)
- [GTUIWindow](typealiases/GTUIWindow.md)
- [Scene](typealiases/Scene.md)
- [SceneBuilder](typealiases/SceneBuilder.md)

View File

@ -6,3 +6,22 @@
### `scene`
The window scene's body is itself.
## Methods
### `appKeyboardShortcut(_:action:)`
Add a keyboard shortcut that is available for the whole app.
- Parameters:
- shortcut: The keyboard shortcut.
- The closure to execute.
### `updateAppShortcuts(app:)`
Update the app shortcuts.
Call this function in types of window scene.
### `quitShortcut()`
Add the shortcut "<Ctrl>q" which terminates the application.
- Returns: The app.

View File

@ -8,7 +8,9 @@
Get the windows described by the group.
- Returns: The windows.
### `update(_:)`
### `update(_:app:)`
Update the windows described by the group.
- Parameter storage: The window's storage.
- Parameters:
- storage: The window's storage.
- app: The application.

View File

@ -13,6 +13,10 @@ The window type's identifier.
The number of instances of the window at the startup.
### `appShortcuts`
The keyboard shortcuts on the application's level.
## Methods
### `createWindow(app:)`
@ -20,7 +24,9 @@ Get the storage for the window.
- Parameter app: The application.
- Returns: The storage.
### `update(_:)`
### `update(_:app:)`
Update a window storage's content.
- Parameter storage: The storage to update.
- Parameters:
- storage: The storage to update.
- app: The application.

View File

@ -0,0 +1,78 @@
**STRUCT**
# `ApplicationWindow`
A structure representing an application window type.
Note that multiple instances of a window can be opened at the same time.
## Properties
### `id`
The window's identifier.
### `content`
The window's content.
### `open`
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.
## Methods
### `init(id:open:content:)`
Create a window type with a certain identifier and user interface.
- Parameters:
- id: The identifier.
- open: The number of instances of the window type when the app is starting.
- content: The window's content.
### `createWindow(app:)`
Get the storage for the window.
- Parameter app: The application.
- Returns: The storage.
### `createGTUIWindow(app:)`
Get the window.
- Parameter app: The application.
- Returns: The window.
### `getViewStorage(window:)`
Get the storage of the content view.
- Parameter window: The window.
- Returns: The storage of the content of the window.
### `update(_:app:)`
Update a window storage's content.
- Parameter storage: The storage to update.
### `keyboardShortcut(_:action:)`
Add a keyboard shortcut.
- Parameters:
- 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

@ -42,3 +42,23 @@ Update a button's view storage.
Get a button's view storage.
- Returns: The button's view storage.
### `keyboardShortcut(_:window:)`
Create a keyboard shortcut for an application window 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.
- window: The application window.
- Returns: The button.
### `keyboardShortcut(_:app:)`
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.
- window: The application.
- Returns: The button.

View File

@ -19,6 +19,10 @@ The window's content.
Whether an instance of the window type should be opened when the app is starting up.
### `appShortcuts`
The keyboard shortcuts on the app level.
## Methods
### `init(id:open:content:)`
@ -46,7 +50,9 @@ Get the storage of the content view.
- Parameter window: The window.
- Returns: The storage of the content of the window.
### `update(_:)`
### `update(_:app:)`
Update a window storage's content.
- Parameter storage: The storage to update.
- Parameters:
- storage: The storage to update.
- app: The application.

View File

@ -0,0 +1,5 @@
**TYPEALIAS**
# `GTUIApplicationWindow`
A GTUI application window.

View File

@ -85,23 +85,56 @@ If you want to use _Adwaita_ in a project, but there are widgets missing, open a
### View Modifiers
| Syntax | Description |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| Syntax | Description |
| ---------------------------- | --------------------------------------------------------------------------------------- |
| `inspect(_:)` | Edit the underlying [GTUI][10] widget. |
| `padding(_:_:)` | Add empty space around a view. |
| `hexpand(_:)` | Enable or disable the horizontal expansion of a view. |
| `vexpand(_:)` | Enable or disable the vertical expansion of a view. |
| `halign(_:)` | Set the horizontal alignment of a view. |
| `valign(_:)` | Set the vertical alignment of a view. |
| `frame(minWidth:minHeight:)` | Set the views minimal width or height. |
| `frame(maxSize:)` | Set the views maximal size. |
| `transition(_:)` | Assign a transition with the view that is used if it is a direct child of an EitherView. |
| `onUpdate(_:)` | Run a function every time a view gets updated. |
| `navigationTitle(_:)` | Add a title that is used if the view is a direct child of a NavigationView. |
| `style(_:)` | Add a style class to the view. |
| `onAppear(_:)` | Run when the view is rendered for the first time. |
| `topToolbar(visible:_:)` | Add a native toolbar to the view. Normally, it contains a HeaderBar. |
| `bottomToolbar(visible:_:)` | Add a native bottom toolbar to the view. |
| `padding(_:_:)` | Add empty space around a view. |
| `hexpand(_:)` | Enable or disable the horizontal expansion of a view. |
| `vexpand(_:)` | Enable or disable the vertical expansion of a view. |
| `halign(_:)` | Set the horizontal alignment of a view. |
| `valign(_:)` | Set the vertical alignment of a view. |
| `frame(minWidth:minHeight:)` | Set the views minimal width or height. |
| `frame(maxSize:)` | Set the views maximal size. |
| `transition(_:)` | Assign a transition with the view that is used if it is a direct child of an EitherView.|
| `onUpdate(_:)` | Run a function every time a view gets updated. |
| `navigationTitle(_:)` | Add a title that is used if the view is a direct child of a NavigationView. |
| `style(_:)` | Add a style class to the view. |
| `onAppear(_:)` | Run when the view is rendered for the first time. |
| `topToolbar(visible:_:)` | Add a native toolbar to the view. Normally, it contains a HeaderBar. |
| `bottomToolbar(visible:_:)` | Add a native bottom toolbar to the view. |
### `Button` Modifiers
| Syntax | Description |
| ---------------------------- | --------------------------------------------------------------------------------------- |
| `keyboardShortcut(_:window:)`| Create a keyboard shortcut for the window with the button's action. |
| `keyboardShortcut(_:app:)` | Create a keyboard shortcut for the application with the button's action. |
### `HeaderBar` Modifiers
| Syntax | Description |
| ---------------------------- | --------------------------------------------------------------------------------------- |
| `headerBarTitle(view:)` | Customize the title view in the header bar. |
### `List` Modifiers
| Syntax | Description |
| ---------------------------- | --------------------------------------------------------------------------------------- |
| `sidebarStyle()` | Change the style of the list to match a sidebar. |
### Window Types
| Name | Description | Widget |
| -------------------- | ----------------------------------------------------------------- | ---------------------- |
| Window | A simple application window. | AdwApplicationWindow |
### Window Modifiers
| Syntax | Description |
| ------------------------------- | --------------------------------------------------------------------------------------- |
| `appKeyboardShortcut(_:action:)`| Create a keyboard shortcut available in the whole the application. |
| `quitShortcut()` | Create a keyboard shortcut for quitting the application with "Ctrl + q". |
### `Window` Modifiers
| Syntax | Description |
| ------------------------------- | --------------------------------------------------------------------------------------- |
| `keyboardShortcut(_:action:)` | Create a keyboard shortcut available in one window. |
| `closeShortcut()` | Create a keyboard shortcut for closing the window with "Ctrl + w". |
## Installation
### Dependencies
@ -131,21 +164,22 @@ brew install libadwaita
* [Hello World][13]
* [Creating Views][14]
* [Windows][15]
* [Keyboard Shortcuts][16]
### Advanced
* [Creating Widgets][16]
* [Creating Widgets][17]
## Thanks
### Dependencies
- [SwiftGui][17] licensed under the [GPL-3.0 license][18]
- [SwiftGui][18] licensed under the [GPL-3.0 license][19]
### Other Thanks
- The [contributors][19]
- [SwiftLint][20] for checking whether code style conventions are violated
- The programming language [Swift][21]
- [SourceDocs][22] used for generating the [docs][23]
- The [contributors][20]
- [SwiftLint][21] for checking whether code style conventions are violated
- The programming language [Swift][22]
- [SourceDocs][23] used for generating the [docs][24]
[1]: Tests/
[2]: #goals
@ -160,16 +194,17 @@ brew install libadwaita
[11]: https://brew.sh
[12]: user-manual/GettingStarted.md
[13]: user-manual/Basics/HelloWorld.md
[14]: user-manual/Basics/CreatingViews.md
[15]: user-manual/Basics/Windows.md
[16]: user-manual/Advanced/CreatingWidgets.md
[17]: https://github.com/JCWasmx86/SwiftGui
[18]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING
[19]: Contributors.md
[20]: https://github.com/realm/SwiftLint
[21]: https://github.com/apple/swift
[22]: https://github.com/SourceDocs/SourceDocs
[23]: Documentation/Reference/README.md
[14]: user-manual/Basics/CreatingViews.md
[15]: user-manual/Basics/Windows.md
[16]: user-manual/Basics/KeyboardShortcuts.md
[17]: user-manual/Advanced/CreatingWidgets.md
[18]: https://github.com/JCWasmx86/SwiftGui
[19]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING
[20]: Contributors.md
[21]: https://github.com/realm/SwiftLint
[22]: https://github.com/apple/swift
[23]: https://github.com/SourceDocs/SourceDocs
[24]: Documentation/Reference/README.md
[image-1]: Icons/Screenshot.png
[image-2]: Icons/Demo.png

View File

@ -8,14 +8,16 @@
* [Hello World][3]
* [Creating Views][4]
* [Windows][5]
* [Keyboard Shortcuts][6]
## Advanced
* [Creating Widgets][6]
* [Creating Widgets][7]
[1]: README.md
[2]: user-manual/GettingStarted.md
[3]: user-manual/Basics/HelloWorld.md
[4]: user-manual/Basics/CreatingViews.md
[5]: user-manual/Basics/Windows.md
[6]: user-manual/Advanced/CreatingWidgets.md
[6]: user-manual/Basics/KeyboardShortcuts.md
[7]: user-manual/Advanced/CreatingWidgets.md

View File

@ -51,7 +51,7 @@ extension App {
if window.destroy {
removeIndices.insert(index, at: 0)
} else if let scene = appInstance.scene.windows().first(where: { $0.id == window.id }) {
scene.update(window)
scene.update(window, app: appInstance.app)
}
}
for index in removeIndices {

View File

@ -0,0 +1,11 @@
//
// GTUIApplicationWindow.swift
// Adwaita
//
// Created by david-swift on 19.10.23.
//
import GTUI
/// A GTUI application window.
public typealias GTUIApplicationWindow = GTUI.ApplicationWindow

View File

@ -14,13 +14,17 @@ public protocol WindowScene: WindowSceneGroup {
var id: String { get }
/// The number of instances of the window at the startup.
var `open`: Int { get }
/// The keyboard shortcuts on the application's level.
var appShortcuts: [String: (GTUIApp) -> Void] { get set }
/// Get the storage for the window.
/// - Parameter app: The application.
/// - Returns: The storage.
func createWindow(app: GTUIApp) -> WindowStorage
/// Update a window storage's content.
/// - Parameter storage: The storage to update.
func update(_ storage: WindowStorage)
/// - Parameters:
/// - storage: The storage to update.
/// - app: The application.
func update(_ storage: WindowStorage, app: GTUIApp)
}
@ -29,4 +33,29 @@ extension WindowScene {
/// The window scene's body is itself.
@SceneBuilder public var scene: Scene { self }
/// Add a keyboard shortcut that is available for the whole app.
/// - Parameters:
/// - shortcut: The keyboard shortcut.
/// - The closure to execute.
public func appKeyboardShortcut(_ shortcut: String, action: @escaping (GTUIApp) -> Void) -> Self {
var newSelf = self
newSelf.appShortcuts[shortcut] = action
return newSelf
}
/// Update the app shortcuts.
///
/// Call this function in types of window scene.
public func updateAppShortcuts(app: GTUIApp) {
for shortcut in appShortcuts {
app.addKeyboardShortcut(shortcut.key, id: shortcut.key) { shortcut.value(app) }
}
}
/// Add the shortcut "<Ctrl>q" which terminates the application.
/// - Returns: The app.
public func quitShortcut() -> Self {
appKeyboardShortcut("q".ctrl()) { $0.quit() }
}
}

View File

@ -30,11 +30,13 @@ extension WindowSceneGroup {
}
/// Update the windows described by the group.
/// - Parameter storage: The window's storage.
func update(_ storage: [WindowStorage]) {
/// - Parameters:
/// - storage: The window's storage.
/// - app: The application.
func update(_ storage: [WindowStorage], app: GTUIApp) {
for (index, window) in windows().enumerated() {
if let storage = storage[safe: index] {
window.update(storage)
window.update(storage, app: app)
}
}
}

View File

@ -67,4 +67,28 @@ public struct Button: Widget {
}
}
/// Create a keyboard shortcut for an application window 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.
/// - window: The application window.
/// - Returns: The button.
public func keyboardShortcut(_ shortcut: String, window: GTUIApplicationWindow) -> Self {
window.addKeyboardShortcut(shortcut, id: shortcut, handler: handler)
return self
}
/// 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.
/// - window: The application.
/// - Returns: The button.
public func keyboardShortcut(_ shortcut: String, app: GTUIApp) -> Self {
app.addKeyboardShortcut(shortcut, id: shortcut, handler: handler)
return self
}
}

View File

@ -7,7 +7,7 @@
import GTUI
/// 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.
public struct Window: WindowScene {
@ -15,16 +15,20 @@ public struct Window: WindowScene {
/// The window's identifier.
public var id: String
/// The window's content.
var content: (GTUIWindow) -> Body
var content: (GTUIApplicationWindow) -> Body
/// Whether an instance of the window type should be opened when the app is starting up.
public var `open`: Int
/// The keyboard shortcuts.
var shortcuts: [String: (GTUIApplicationWindow) -> Void] = [:]
/// The keyboard shortcuts on the app level.
public var appShortcuts: [String: (GTUIApp) -> Void] = [:]
/// Create a window type with a certain identifier and user interface.
/// - Parameters:
/// - id: The identifier.
/// - open: The number of instances of the window type when the app is starting.
/// - content: The window's content.
public init(id: String, `open`: Int = 1, @ViewBuilder content: @escaping (GTUIWindow) -> Body) {
public init(id: String, `open`: Int = 1, @ViewBuilder content: @escaping (GTUIApplicationWindow) -> Body) {
self.content = content
self.id = id
self.open = open
@ -47,8 +51,9 @@ public struct Window: WindowScene {
/// Get the window.
/// - Parameter app: The application.
/// - Returns: The window.
func createGTUIWindow(app: GTUIApp) -> GTUIWindow {
let window = GTUIWindow(app: app)
func createGTUIWindow(app: GTUIApp) -> GTUIApplicationWindow {
let window = GTUIApplicationWindow(app: app)
updateAppShortcuts(app: app)
window.show()
return window
}
@ -56,16 +61,48 @@ public struct Window: WindowScene {
/// Get the storage of the content view.
/// - Parameter window: The window.
/// - Returns: The storage of the content of the window.
func getViewStorage(window: GTUIWindow) -> ViewStorage {
let storage = content(window).widget().container()
func getViewStorage(window: GTUIApplicationWindow) -> ViewStorage {
let content = content(window)
let storage = content.widget().container()
window.setChild(storage.view)
updateShortcuts(window: window)
return storage
}
/// Update a window storage's content.
/// - Parameter storage: The storage to update.
public func update(_ storage: WindowStorage) {
content(storage.window).widget().updateStorage(storage.view)
public func update(_ storage: WindowStorage, app: GTUIApp) {
if let window = storage.window as? GTUIApplicationWindow {
let content = content(window)
content.widget().updateStorage(storage.view)
updateShortcuts(window: window)
updateAppShortcuts(app: app)
}
}
/// Add a keyboard shortcut.
/// - Parameters:
/// - shortcut: The keyboard shortcut.
/// - action: The closure to execute when the keyboard shortcut is pressed.
/// - Returns: The window.
public func keyboardShortcut(_ shortcut: String, action: @escaping (GTUIApplicationWindow) -> Void) -> Self {
var newSelf = self
newSelf.shortcuts[shortcut] = action
return newSelf
}
/// Update the keyboard shortcuts.
/// - Parameter window: The application window.
func updateShortcuts(window: GTUIApplicationWindow) {
for shortcut in shortcuts {
window.addKeyboardShortcut(shortcut.key, id: shortcut.key) { shortcut.value(window) }
}
}
/// Add the shortcut "<Ctrl>w" which closes the window.
/// - Returns: The window.
public func closeShortcut() -> Self {
keyboardShortcut("w".ctrl()) { $0.close() }
}
}

View File

@ -20,6 +20,9 @@ struct Demo: App {
Window(id: "main") { window in
DemoContent(window: window, app: app)
}
.appKeyboardShortcut("n".ctrl()) { $0.addWindow("main") }
.closeShortcut()
.quitShortcut()
HelperWindows()
}
@ -29,9 +32,11 @@ struct Demo: App {
Window(id: "content", open: 0) { window in
WindowsDemo.WindowContent(window: window)
}
.closeShortcut()
Window(id: "toolbar-demo", open: 0) { window in
ToolbarDemo.WindowContent(window: window)
}
.closeShortcut()
}
}

View File

@ -0,0 +1,91 @@
# Keyboard Shortcuts
Keyboard shortcuts can be attached to individual windows or whole applications.
## About Keyboard Shortcuts
Keyboard shortcuts are represented as a `String`.
You can add a single character by adding itself to the string, e.g. `"n"`.
The F keys are written as `"F1"`, `"F2"`, etc.
For character keys, write the lowercase name instead of the symbol, such as `"minus"` instead of `"-"`.
Add modifiers to the shortcut using the following string modifiers:
- `.shift()`
- `.ctrl()`
- `.alt()`
- `.meta()`
- `.super()`
- `.hyper()`
As an example, the following syntax represents the `Ctrl + N` shortcut: `"n".ctrl()`.
## Add Shortcuts to a Window
Add a keyboard shortcut to an invividual window. It is only available in that window.
```swift
import Adwaita
@main
struct HelloWorld: App {
let id = "io.github.david-swift.HelloWorld"
var app: GTUIApp!
var scene: Scene {
Window(id: "content") { _ in
HeaderBar.empty()
Text("Hello, world!")
.padding()
}
// Add the shortcut "Ctrl + W" for closing the window
.keyboardShortcut("w".ctrl()) { window in
window.close()
}
}
}
```
## Add Shortcuts to an App
Add a keyboard to an app so that the shortcut is available in every top-level window.
```swift
import Adwaita
@main
struct HelloWorld: App {
let id = "io.github.david-swift.HelloWorld"
var app: GTUIApp!
var scene: Scene {
Window(id: "content") { _ in
HeaderBar.empty()
Text("Hello, world!")
.padding()
}
// Add the shortcut "Ctrl + Q" for terminating the app
.appKeyboardShortcut("q".ctrl()) { app in
app.quit()
}
}
}
```
## 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.
Note that the shortcut gets activated after presenting the view for the first time.
```swift
struct HelloView: View {
var window: GTUIWindow
var view: Body {
Button("New Item") {
print("New Item")
}
// Add a keyboard shortcut to the window "window".
.keyboardShortcut("n".ctrl().shift(), window: window)
}
}
```