Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Commit

Permalink
Add Idle for updating the UI asynchronously
Browse files Browse the repository at this point in the history
  • Loading branch information
david-swift committed May 5, 2024
1 parent 7ff5661 commit 932e4ae
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ DerivedData/
.Ulysses-Group.plist
/.docc-build
/io.github.AparokshaUI.Generation.json
/.vscode
/io.github.AparokshaUI.swiftlint.json
/.vscode
110 changes: 110 additions & 0 deletions Sources/Adwaita/Model/Data Flow/Idle.swift
Original file line number Diff line number Diff line change
@@ -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
}

}

}
2 changes: 1 addition & 1 deletion Sources/Adwaita/Window/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
50 changes: 50 additions & 0 deletions Tests/IdleDemo.swift
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions Tests/Page.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum Page: String, Identifiable, CaseIterable, Codable {
case flowBox
case navigationView
case picture
case idle

var id: Self {
self
Expand Down Expand Up @@ -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"
}
}

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 932e4ae

Please sign in to comment.