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

View File

@ -6,3 +6,22 @@
### `scene` ### `scene`
The window scene's body is itself. 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. Get the windows described by the group.
- Returns: The windows. - Returns: The windows.
### `update(_:)` ### `update(_:app:)`
Update the windows described by the group. 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. The number of instances of the window at the startup.
### `appShortcuts`
The keyboard shortcuts on the application's level.
## Methods ## Methods
### `createWindow(app:)` ### `createWindow(app:)`
@ -20,7 +24,9 @@ Get the storage for the window.
- Parameter app: The application. - Parameter app: The application.
- Returns: The storage. - Returns: The storage.
### `update(_:)` ### `update(_:app:)`
Update a window storage's content. 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. Get a button's view storage.
- Returns: The 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. 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 ## Methods
### `init(id:open:content:)` ### `init(id:open:content:)`
@ -46,7 +50,9 @@ Get the storage of the content view.
- Parameter window: The window. - Parameter window: The window.
- Returns: The storage of the content of the window. - Returns: The storage of the content of the window.
### `update(_:)` ### `update(_:app:)`
Update a window storage's content. 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 ### View Modifiers
| Syntax | Description | | Syntax | Description |
| ---------------------------- | -------------------------------------------------------------------------------------- | | ---------------------------- | --------------------------------------------------------------------------------------- |
| `inspect(_:)` | Edit the underlying [GTUI][10] widget. | | `inspect(_:)` | Edit the underlying [GTUI][10] widget. |
| `padding(_:_:)` | Add empty space around a view. | | `padding(_:_:)` | Add empty space around a view. |
| `hexpand(_:)` | Enable or disable the horizontal expansion of a view. | | `hexpand(_:)` | Enable or disable the horizontal expansion of a view. |
| `vexpand(_:)` | Enable or disable the vertical expansion of a view. | | `vexpand(_:)` | Enable or disable the vertical expansion of a view. |
| `halign(_:)` | Set the horizontal alignment of a view. | | `halign(_:)` | Set the horizontal alignment of a view. |
| `valign(_:)` | Set the vertical alignment of a view. | | `valign(_:)` | Set the vertical alignment of a view. |
| `frame(minWidth:minHeight:)` | Set the views minimal width or height. | | `frame(minWidth:minHeight:)` | Set the views minimal width or height. |
| `frame(maxSize:)` | Set the views maximal size. | | `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. | | `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. | | `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. | | `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. | | `style(_:)` | Add a style class to the view. |
| `onAppear(_:)` | Run when the view is rendered for the first time. | | `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. | | `topToolbar(visible:_:)` | Add a native toolbar to the view. Normally, it contains a HeaderBar. |
| `bottomToolbar(visible:_:)` | Add a native bottom toolbar to the view. | | `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 ## Installation
### Dependencies ### Dependencies
@ -131,21 +164,22 @@ brew install libadwaita
* [Hello World][13] * [Hello World][13]
* [Creating Views][14] * [Creating Views][14]
* [Windows][15] * [Windows][15]
* [Keyboard Shortcuts][16]
### Advanced ### Advanced
* [Creating Widgets][16] * [Creating Widgets][17]
## Thanks ## Thanks
### Dependencies ### Dependencies
- [SwiftGui][17] licensed under the [GPL-3.0 license][18] - [SwiftGui][18] licensed under the [GPL-3.0 license][19]
### Other Thanks ### Other Thanks
- The [contributors][19] - The [contributors][20]
- [SwiftLint][20] for checking whether code style conventions are violated - [SwiftLint][21] for checking whether code style conventions are violated
- The programming language [Swift][21] - The programming language [Swift][22]
- [SourceDocs][22] used for generating the [docs][23] - [SourceDocs][23] used for generating the [docs][24]
[1]: Tests/ [1]: Tests/
[2]: #goals [2]: #goals
@ -160,16 +194,17 @@ brew install libadwaita
[11]: https://brew.sh [11]: https://brew.sh
[12]: user-manual/GettingStarted.md [12]: user-manual/GettingStarted.md
[13]: user-manual/Basics/HelloWorld.md [13]: user-manual/Basics/HelloWorld.md
[14]: user-manual/Basics/CreatingViews.md [14]: user-manual/Basics/CreatingViews.md
[15]: user-manual/Basics/Windows.md [15]: user-manual/Basics/Windows.md
[16]: user-manual/Advanced/CreatingWidgets.md [16]: user-manual/Basics/KeyboardShortcuts.md
[17]: https://github.com/JCWasmx86/SwiftGui [17]: user-manual/Advanced/CreatingWidgets.md
[18]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING [18]: https://github.com/JCWasmx86/SwiftGui
[19]: Contributors.md [19]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING
[20]: https://github.com/realm/SwiftLint [20]: Contributors.md
[21]: https://github.com/apple/swift [21]: https://github.com/realm/SwiftLint
[22]: https://github.com/SourceDocs/SourceDocs [22]: https://github.com/apple/swift
[23]: Documentation/Reference/README.md [23]: https://github.com/SourceDocs/SourceDocs
[24]: Documentation/Reference/README.md
[image-1]: Icons/Screenshot.png [image-1]: Icons/Screenshot.png
[image-2]: Icons/Demo.png [image-2]: Icons/Demo.png

View File

@ -8,14 +8,16 @@
* [Hello World][3] * [Hello World][3]
* [Creating Views][4] * [Creating Views][4]
* [Windows][5] * [Windows][5]
* [Keyboard Shortcuts][6]
## Advanced ## Advanced
* [Creating Widgets][6] * [Creating Widgets][7]
[1]: README.md [1]: README.md
[2]: user-manual/GettingStarted.md [2]: user-manual/GettingStarted.md
[3]: user-manual/Basics/HelloWorld.md [3]: user-manual/Basics/HelloWorld.md
[4]: user-manual/Basics/CreatingViews.md [4]: user-manual/Basics/CreatingViews.md
[5]: user-manual/Basics/Windows.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 { if window.destroy {
removeIndices.insert(index, at: 0) removeIndices.insert(index, at: 0)
} else if let scene = appInstance.scene.windows().first(where: { $0.id == window.id }) { } 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 { 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 } var id: String { get }
/// The number of instances of the window at the startup. /// The number of instances of the window at the startup.
var `open`: Int { get } 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. /// Get the storage for the window.
/// - Parameter app: The application. /// - Parameter app: The application.
/// - Returns: The storage. /// - Returns: The storage.
func createWindow(app: GTUIApp) -> WindowStorage func createWindow(app: GTUIApp) -> WindowStorage
/// Update a window storage's content. /// Update a window storage's content.
/// - Parameter storage: The storage to update. /// - Parameters:
func update(_ storage: WindowStorage) /// - 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. /// The window scene's body is itself.
@SceneBuilder public var scene: Scene { self } @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. /// Update the windows described by the group.
/// - Parameter storage: The window's storage. /// - Parameters:
func update(_ storage: [WindowStorage]) { /// - storage: The window's storage.
/// - app: The application.
func update(_ storage: [WindowStorage], app: GTUIApp) {
for (index, window) in windows().enumerated() { for (index, window) in windows().enumerated() {
if let storage = storage[safe: index] { 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 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. /// Note that multiple instances of a window can be opened at the same time.
public struct Window: WindowScene { public struct Window: WindowScene {
@ -15,16 +15,20 @@ public struct Window: WindowScene {
/// The window's identifier. /// The window's identifier.
public var id: String public var id: String
/// The window's content. /// 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. /// Whether an instance of the window type should be opened when the app is starting up.
public var `open`: Int 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. /// Create a window type with a certain identifier and user interface.
/// - Parameters: /// - Parameters:
/// - id: The identifier. /// - id: The identifier.
/// - open: The number of instances of the window type when the app is starting. /// - open: The number of instances of the window type when the app is starting.
/// - content: The window's content. /// - 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.content = content
self.id = id self.id = id
self.open = open self.open = open
@ -47,8 +51,9 @@ public struct Window: WindowScene {
/// Get the window. /// Get the window.
/// - Parameter app: The application. /// - Parameter app: The application.
/// - Returns: The window. /// - Returns: The window.
func createGTUIWindow(app: GTUIApp) -> GTUIWindow { func createGTUIWindow(app: GTUIApp) -> GTUIApplicationWindow {
let window = GTUIWindow(app: app) let window = GTUIApplicationWindow(app: app)
updateAppShortcuts(app: app)
window.show() window.show()
return window return window
} }
@ -56,16 +61,48 @@ public struct Window: WindowScene {
/// Get the storage of the content view. /// Get the storage of the content view.
/// - Parameter window: The window. /// - Parameter window: The window.
/// - Returns: The storage of the content of the window. /// - Returns: The storage of the content of the window.
func getViewStorage(window: GTUIWindow) -> ViewStorage { func getViewStorage(window: GTUIApplicationWindow) -> ViewStorage {
let storage = content(window).widget().container() let content = content(window)
let storage = content.widget().container()
window.setChild(storage.view) window.setChild(storage.view)
updateShortcuts(window: window)
return storage return storage
} }
/// Update a window storage's content. /// Update a window storage's content.
/// - Parameter storage: The storage to update. /// - Parameter storage: The storage to update.
public func update(_ storage: WindowStorage) { public func update(_ storage: WindowStorage, app: GTUIApp) {
content(storage.window).widget().updateStorage(storage.view) 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 Window(id: "main") { window in
DemoContent(window: window, app: app) DemoContent(window: window, app: app)
} }
.appKeyboardShortcut("n".ctrl()) { $0.addWindow("main") }
.closeShortcut()
.quitShortcut()
HelperWindows() HelperWindows()
} }
@ -29,9 +32,11 @@ struct Demo: App {
Window(id: "content", open: 0) { window in Window(id: "content", open: 0) { window in
WindowsDemo.WindowContent(window: window) WindowsDemo.WindowContent(window: window)
} }
.closeShortcut()
Window(id: "toolbar-demo", open: 0) { window in Window(id: "toolbar-demo", open: 0) { window in
ToolbarDemo.WindowContent(window: window) 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)
}
}
```