Add Idle for updating the UI asynchronously
This commit is contained in:
parent
7ff56613e5
commit
932e4aeb8c
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,4 +11,5 @@ DerivedData/
|
||||
.Ulysses-Group.plist
|
||||
/.docc-build
|
||||
/io.github.AparokshaUI.Generation.json
|
||||
/.vscode
|
||||
/io.github.AparokshaUI.swiftlint.json
|
||||
/.vscode
|
||||
|
110
Sources/Adwaita/Model/Data Flow/Idle.swift
Normal file
110
Sources/Adwaita/Model/Data Flow/Idle.swift
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// Idle.swift
|
||||
// Adwaita
|
||||
//
|
||||
// Created by david-swift on 02.05.24.
|
||||
//
|
||||
|
||||
import CAdw
|
||||
|
||||
/// Add a task to GLib's idle.
|
||||
public struct Idle {
|
||||
|
||||
/// The idle handler.
|
||||
static let handler = IdleHandler()
|
||||
|
||||
/// Run a function whenever there are no higher priority events pending to the default main loop.
|
||||
/// - Parameters:
|
||||
/// - priority: The task's priority, default priority by default.
|
||||
/// - closure: The closure to run.
|
||||
@discardableResult
|
||||
public init(
|
||||
priority: Priority = .defaultIdle,
|
||||
closure: @escaping () -> Void
|
||||
) {
|
||||
Self.handler.add({ closure(); return false }, priority: .init(priority.rawValue))
|
||||
}
|
||||
|
||||
/// Repeat a function with a certain delay.
|
||||
/// - Parameters:
|
||||
/// - delay: The delay between the repetitions.
|
||||
/// - priority: The task's priority, default priority by default.
|
||||
/// - closure: The closure to run. Return if you want to exit the loop.
|
||||
@discardableResult
|
||||
public init(
|
||||
delay: Duration,
|
||||
priority: Priority = .defaultIdle,
|
||||
closure: @escaping () -> Bool
|
||||
) {
|
||||
Self.handler.add(closure, priority: .init(priority.rawValue), delay: delay)
|
||||
}
|
||||
|
||||
/// The priority of an idle task.
|
||||
public enum Priority: Int {
|
||||
|
||||
/// A very low priority background task.
|
||||
case low = 300
|
||||
/// A high priority event source.
|
||||
case high = -100
|
||||
/// A default priority event source.
|
||||
case `default` = 0
|
||||
/// A high priority idle function.
|
||||
case highIdle = 100
|
||||
/// A default priority idle function.
|
||||
case defaultIdle = 200
|
||||
|
||||
}
|
||||
|
||||
/// An idle handler.
|
||||
class IdleHandler {
|
||||
|
||||
/// Add a function to be called whenever there are no higher priority events pending to the default main loop.
|
||||
/// - Parameter closure: The function.
|
||||
func add(_ closure: @escaping () -> Bool, priority: Int32, delay: Duration? = nil) {
|
||||
let context = UnsafeMutableRawPointer(Unmanaged.passRetained(ClosureContainer(closure: closure)).toOpaque())
|
||||
let secondsToMilliseconds: Int64 = 1_000
|
||||
let attosecondsToMilliseconds: Int64 = 1_000_000_000_000_000
|
||||
if let delay {
|
||||
let milliseconds = delay.components.seconds * secondsToMilliseconds
|
||||
+ (delay.components.attoseconds / attosecondsToMilliseconds)
|
||||
// swiftlint:disable prefer_self_in_static_references
|
||||
g_timeout_add_full(priority, .init(milliseconds), { IdleHandler.run(pointer: $0) }, context, nil)
|
||||
// swiftlint:enable prefer_self_in_static_references
|
||||
} else {
|
||||
// swiftlint:disable prefer_self_in_static_references
|
||||
g_idle_add_full(priority, { IdleHandler.run(pointer: $0) }, context, nil)
|
||||
// swiftlint:enable prefer_self_in_static_references
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the function.
|
||||
/// - Parameter pointer: The closure wrapper's pointer.
|
||||
static func run(pointer: gpointer?) -> Int32 {
|
||||
if let pointer {
|
||||
let container = Unmanaged<ClosureContainer>.fromOpaque(pointer).takeUnretainedValue()
|
||||
let result = container.closure()
|
||||
if !result {
|
||||
Unmanaged<ClosureContainer>.fromOpaque(pointer).release()
|
||||
}
|
||||
return result.cBool
|
||||
}
|
||||
return G_SOURCE_REMOVE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// A reference type holding a closure.
|
||||
class ClosureContainer {
|
||||
|
||||
/// The closure.
|
||||
var closure: () -> Bool
|
||||
|
||||
/// Initialize an object.
|
||||
/// - Parameter closure: The closure.
|
||||
init(closure: @escaping () -> Bool) {
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -110,7 +110,7 @@ public struct Window: WindowScene {
|
||||
updateAppShortcuts(app: app)
|
||||
}
|
||||
for signal in signals where signal.update {
|
||||
Task {
|
||||
Idle {
|
||||
app.showWindow(signal.id.uuidString)
|
||||
}
|
||||
}
|
||||
|
50
Tests/IdleDemo.swift
Normal file
50
Tests/IdleDemo.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// IdleDemo.swift
|
||||
// Adwaita
|
||||
//
|
||||
// Created by david-swift on 05.05.24.
|
||||
//
|
||||
|
||||
// swiftlint:disable missing_docs
|
||||
|
||||
import Adwaita
|
||||
|
||||
struct IdleDemo: View {
|
||||
|
||||
@State private var progress = 0.0
|
||||
@State private var activeProcess = false
|
||||
let max = 500.0
|
||||
let delayFactor = 5.0
|
||||
let maxWidth = 300
|
||||
|
||||
var view: Body {
|
||||
ProgressBar(value: progress, total: max)
|
||||
.vexpand()
|
||||
.valign(.center)
|
||||
.frame(maxWidth: maxWidth)
|
||||
Button("Play") {
|
||||
Task {
|
||||
Idle {
|
||||
activeProcess = true
|
||||
progress = 0
|
||||
}
|
||||
Idle(delay: .seconds(delayFactor / max)) {
|
||||
progress += 1
|
||||
let done = progress == max
|
||||
if done {
|
||||
activeProcess = false
|
||||
}
|
||||
return !done
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.style("pill")
|
||||
.hexpand()
|
||||
.halign(.center)
|
||||
.insensitive(activeProcess)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// swiftlint:enable missing_docs
|
@ -29,6 +29,7 @@ enum Page: String, Identifiable, CaseIterable, Codable {
|
||||
case flowBox
|
||||
case navigationView
|
||||
case picture
|
||||
case idle
|
||||
|
||||
var id: Self {
|
||||
self
|
||||
@ -94,6 +95,8 @@ enum Page: String, Identifiable, CaseIterable, Codable {
|
||||
return "A page-based navigation container"
|
||||
case .picture:
|
||||
return "Display an image"
|
||||
case .idle:
|
||||
return "Update UI from an asynchronous context"
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +138,8 @@ enum Page: String, Identifiable, CaseIterable, Codable {
|
||||
NavigationViewDemo(app: app)
|
||||
case .picture:
|
||||
PictureDemo(url: pictureURL, app: app, window: window)
|
||||
case .idle:
|
||||
IdleDemo()
|
||||
}
|
||||
}
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
|
Loading…
Reference in New Issue
Block a user