Skip to content

Commit a25cca5

Browse files
committed
Implement progress bars (used by ProgressView when using any of the new determinate progress initialisers)
Also supports indeterminate progress bars on some systems.
1 parent b8fb86a commit a25cca5

File tree

9 files changed

+561
-13
lines changed

9 files changed

+561
-13
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,34 @@ public final class AppKitBackend: AppBackend {
738738
return spinner
739739
}
740740

741+
public func createProgressBar() -> Widget {
742+
let progressBar = NSProgressIndicator()
743+
progressBar.isIndeterminate = false
744+
progressBar.style = .bar
745+
progressBar.minValue = 0
746+
progressBar.maxValue = 1
747+
return progressBar
748+
}
749+
750+
public func updateProgressBar(
751+
_ widget: Widget,
752+
progressFraction: Double?,
753+
environment: Environment
754+
) {
755+
let progressBar = widget as! NSProgressIndicator
756+
progressBar.doubleValue = progressFraction ?? 0
757+
progressBar.appearance = environment.colorScheme.nsAppearance
758+
759+
if progressFraction == nil && !progressBar.isIndeterminate {
760+
// Start the indeterminate animation
761+
progressBar.isIndeterminate = true
762+
progressBar.startAnimation(nil)
763+
} else if progressFraction != nil && progressBar.isIndeterminate {
764+
// Stop the indeterminate animation
765+
progressBar.isIndeterminate = false
766+
progressBar.stopAnimation(nil)
767+
}
768+
}
741769
}
742770

743771
class NSCustomTableView: NSTableView {
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import CGtk
2+
3+
/// `GtkProgressBar` is typically used to display the progress of a long
4+
/// running operation.
5+
///
6+
/// It provides a visual clue that processing is underway. `GtkProgressBar`
7+
/// can be used in two different modes: percentage mode and activity mode.
8+
///
9+
/// ![An example GtkProgressBar](progressbar.png)
10+
///
11+
/// When an application can determine how much work needs to take place
12+
/// (e.g. read a fixed number of bytes from a file) and can monitor its
13+
/// progress, it can use the `GtkProgressBar` in percentage mode and the
14+
/// user sees a growing bar indicating the percentage of the work that
15+
/// has been completed. In this mode, the application is required to call
16+
/// [method@Gtk.ProgressBar.set_fraction] periodically to update the progress bar.
17+
///
18+
/// When an application has no accurate way of knowing the amount of work
19+
/// to do, it can use the `GtkProgressBar` in activity mode, which shows
20+
/// activity by a block moving back and forth within the progress area. In
21+
/// this mode, the application is required to call [method@Gtk.ProgressBar.pulse]
22+
/// periodically to update the progress bar.
23+
///
24+
/// There is quite a bit of flexibility provided to control the appearance
25+
/// of the `GtkProgressBar`. Functions are provided to control the orientation
26+
/// of the bar, optional text can be displayed along with the bar, and the
27+
/// step size used in activity mode can be set.
28+
///
29+
/// # CSS nodes
30+
///
31+
/// ```
32+
/// progressbar[.osd]
33+
/// ├── [text]
34+
/// ╰── trough[.empty][.full]
35+
/// ╰── progress[.pulse]
36+
/// ```
37+
///
38+
/// `GtkProgressBar` has a main CSS node with name progressbar and subnodes with
39+
/// names text and trough, of which the latter has a subnode named progress. The
40+
/// text subnode is only present if text is shown. The progress subnode has the
41+
/// style class .pulse when in activity mode. It gets the style classes .left,
42+
/// .right, .top or .bottom added when the progress 'touches' the corresponding
43+
/// end of the GtkProgressBar. The .osd class on the progressbar node is for use
44+
/// in overlays like the one Epiphany has for page loading progress.
45+
///
46+
/// # Accessibility
47+
///
48+
/// `GtkProgressBar` uses the %GTK_ACCESSIBLE_ROLE_PROGRESS_BAR role.
49+
public class ProgressBar: Widget, Orientable {
50+
/// Creates a new `GtkProgressBar`.
51+
override public init() {
52+
super.init()
53+
widgetPointer = gtk_progress_bar_new()
54+
}
55+
56+
override func didMoveToParent() {
57+
removeSignals()
58+
59+
super.didMoveToParent()
60+
61+
let handler0:
62+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
63+
{ _, value1, data in
64+
SignalBox1<OpaquePointer>.run(data, value1)
65+
}
66+
67+
addSignal(name: "notify::ellipsize", handler: gCallback(handler0)) {
68+
[weak self] (_: OpaquePointer) in
69+
guard let self = self else { return }
70+
self.notifyEllipsize?(self)
71+
}
72+
73+
let handler1:
74+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
75+
{ _, value1, data in
76+
SignalBox1<OpaquePointer>.run(data, value1)
77+
}
78+
79+
addSignal(name: "notify::fraction", handler: gCallback(handler1)) {
80+
[weak self] (_: OpaquePointer) in
81+
guard let self = self else { return }
82+
self.notifyFraction?(self)
83+
}
84+
85+
let handler2:
86+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
87+
{ _, value1, data in
88+
SignalBox1<OpaquePointer>.run(data, value1)
89+
}
90+
91+
addSignal(name: "notify::inverted", handler: gCallback(handler2)) {
92+
[weak self] (_: OpaquePointer) in
93+
guard let self = self else { return }
94+
self.notifyInverted?(self)
95+
}
96+
97+
let handler3:
98+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
99+
{ _, value1, data in
100+
SignalBox1<OpaquePointer>.run(data, value1)
101+
}
102+
103+
addSignal(name: "notify::pulse-step", handler: gCallback(handler3)) {
104+
[weak self] (_: OpaquePointer) in
105+
guard let self = self else { return }
106+
self.notifyPulseStep?(self)
107+
}
108+
109+
let handler4:
110+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
111+
{ _, value1, data in
112+
SignalBox1<OpaquePointer>.run(data, value1)
113+
}
114+
115+
addSignal(name: "notify::show-text", handler: gCallback(handler4)) {
116+
[weak self] (_: OpaquePointer) in
117+
guard let self = self else { return }
118+
self.notifyShowText?(self)
119+
}
120+
121+
let handler5:
122+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
123+
{ _, value1, data in
124+
SignalBox1<OpaquePointer>.run(data, value1)
125+
}
126+
127+
addSignal(name: "notify::text", handler: gCallback(handler5)) {
128+
[weak self] (_: OpaquePointer) in
129+
guard let self = self else { return }
130+
self.notifyText?(self)
131+
}
132+
133+
let handler6:
134+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
135+
{ _, value1, data in
136+
SignalBox1<OpaquePointer>.run(data, value1)
137+
}
138+
139+
addSignal(name: "notify::orientation", handler: gCallback(handler6)) {
140+
[weak self] (_: OpaquePointer) in
141+
guard let self = self else { return }
142+
self.notifyOrientation?(self)
143+
}
144+
}
145+
146+
/// The fraction of total work that has been completed.
147+
@GObjectProperty(named: "fraction") public var fraction: Double
148+
149+
/// Invert the direction in which the progress bar grows.
150+
@GObjectProperty(named: "inverted") public var inverted: Bool
151+
152+
/// The fraction of total progress to move the bounding block when pulsed.
153+
@GObjectProperty(named: "pulse-step") public var pulseStep: Double
154+
155+
/// Sets whether the progress bar will show a text in addition
156+
/// to the bar itself.
157+
///
158+
/// The shown text is either the value of the [property@Gtk.ProgressBar:text]
159+
/// property or, if that is %NULL, the [property@Gtk.ProgressBar:fraction]
160+
/// value, as a percentage.
161+
///
162+
/// To make a progress bar that is styled and sized suitably for showing text
163+
/// (even if the actual text is blank), set [property@Gtk.ProgressBar:show-text]
164+
/// to %TRUE and [property@Gtk.ProgressBar:text] to the empty string (not %NULL).
165+
@GObjectProperty(named: "show-text") public var showText: Bool
166+
167+
/// Text to be displayed in the progress bar.
168+
@GObjectProperty(named: "text") public var text: String?
169+
170+
/// The orientation of the orientable.
171+
@GObjectProperty(named: "orientation") public var orientation: Orientation
172+
173+
public var notifyEllipsize: ((ProgressBar) -> Void)?
174+
175+
public var notifyFraction: ((ProgressBar) -> Void)?
176+
177+
public var notifyInverted: ((ProgressBar) -> Void)?
178+
179+
public var notifyPulseStep: ((ProgressBar) -> Void)?
180+
181+
public var notifyShowText: ((ProgressBar) -> Void)?
182+
183+
public var notifyText: ((ProgressBar) -> Void)?
184+
185+
public var notifyOrientation: ((ProgressBar) -> Void)?
186+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import CGtk3
2+
3+
/// The #GtkProgressBar is typically used to display the progress of a long
4+
/// running operation. It provides a visual clue that processing is underway.
5+
/// The GtkProgressBar can be used in two different modes: percentage mode
6+
/// and activity mode.
7+
///
8+
/// When an application can determine how much work needs to take place
9+
/// (e.g. read a fixed number of bytes from a file) and can monitor its
10+
/// progress, it can use the GtkProgressBar in percentage mode and the
11+
/// user sees a growing bar indicating the percentage of the work that
12+
/// has been completed. In this mode, the application is required to call
13+
/// gtk_progress_bar_set_fraction() periodically to update the progress bar.
14+
///
15+
/// When an application has no accurate way of knowing the amount of work
16+
/// to do, it can use the #GtkProgressBar in activity mode, which shows
17+
/// activity by a block moving back and forth within the progress area. In
18+
/// this mode, the application is required to call gtk_progress_bar_pulse()
19+
/// periodically to update the progress bar.
20+
///
21+
/// There is quite a bit of flexibility provided to control the appearance
22+
/// of the #GtkProgressBar. Functions are provided to control the orientation
23+
/// of the bar, optional text can be displayed along with the bar, and the
24+
/// step size used in activity mode can be set.
25+
///
26+
/// # CSS nodes
27+
///
28+
/// |[<!-- language="plain" -->
29+
/// progressbar[.osd]
30+
/// ├── [text]
31+
/// ╰── trough[.empty][.full]
32+
/// ╰── progress[.pulse]
33+
/// ]|
34+
///
35+
/// GtkProgressBar has a main CSS node with name progressbar and subnodes with
36+
/// names text and trough, of which the latter has a subnode named progress. The
37+
/// text subnode is only present if text is shown. The progress subnode has the
38+
/// style class .pulse when in activity mode. It gets the style classes .left,
39+
/// .right, .top or .bottom added when the progress 'touches' the corresponding
40+
/// end of the GtkProgressBar. The .osd class on the progressbar node is for use
41+
/// in overlays like the one Epiphany has for page loading progress.
42+
public class ProgressBar: Widget, Orientable {
43+
/// Creates a new #GtkProgressBar.
44+
override public init() {
45+
super.init()
46+
widgetPointer = gtk_progress_bar_new()
47+
}
48+
49+
override func didMoveToParent() {
50+
removeSignals()
51+
52+
super.didMoveToParent()
53+
54+
let handler0:
55+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
56+
{ _, value1, data in
57+
SignalBox1<OpaquePointer>.run(data, value1)
58+
}
59+
60+
addSignal(name: "notify::ellipsize", handler: gCallback(handler0)) {
61+
[weak self] (_: OpaquePointer) in
62+
guard let self = self else { return }
63+
self.notifyEllipsize?(self)
64+
}
65+
66+
let handler1:
67+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
68+
{ _, value1, data in
69+
SignalBox1<OpaquePointer>.run(data, value1)
70+
}
71+
72+
addSignal(name: "notify::fraction", handler: gCallback(handler1)) {
73+
[weak self] (_: OpaquePointer) in
74+
guard let self = self else { return }
75+
self.notifyFraction?(self)
76+
}
77+
78+
let handler2:
79+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
80+
{ _, value1, data in
81+
SignalBox1<OpaquePointer>.run(data, value1)
82+
}
83+
84+
addSignal(name: "notify::inverted", handler: gCallback(handler2)) {
85+
[weak self] (_: OpaquePointer) in
86+
guard let self = self else { return }
87+
self.notifyInverted?(self)
88+
}
89+
90+
let handler3:
91+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
92+
{ _, value1, data in
93+
SignalBox1<OpaquePointer>.run(data, value1)
94+
}
95+
96+
addSignal(name: "notify::pulse-step", handler: gCallback(handler3)) {
97+
[weak self] (_: OpaquePointer) in
98+
guard let self = self else { return }
99+
self.notifyPulseStep?(self)
100+
}
101+
102+
let handler4:
103+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
104+
{ _, value1, data in
105+
SignalBox1<OpaquePointer>.run(data, value1)
106+
}
107+
108+
addSignal(name: "notify::show-text", handler: gCallback(handler4)) {
109+
[weak self] (_: OpaquePointer) in
110+
guard let self = self else { return }
111+
self.notifyShowText?(self)
112+
}
113+
114+
let handler5:
115+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
116+
{ _, value1, data in
117+
SignalBox1<OpaquePointer>.run(data, value1)
118+
}
119+
120+
addSignal(name: "notify::text", handler: gCallback(handler5)) {
121+
[weak self] (_: OpaquePointer) in
122+
guard let self = self else { return }
123+
self.notifyText?(self)
124+
}
125+
126+
let handler6:
127+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
128+
{ _, value1, data in
129+
SignalBox1<OpaquePointer>.run(data, value1)
130+
}
131+
132+
addSignal(name: "notify::orientation", handler: gCallback(handler6)) {
133+
[weak self] (_: OpaquePointer) in
134+
guard let self = self else { return }
135+
self.notifyOrientation?(self)
136+
}
137+
}
138+
139+
@GObjectProperty(named: "fraction") public var fraction: Double
140+
141+
@GObjectProperty(named: "inverted") public var inverted: Bool
142+
143+
@GObjectProperty(named: "pulse-step") public var pulseStep: Double
144+
145+
@GObjectProperty(named: "text") public var text: String?
146+
147+
public var notifyEllipsize: ((ProgressBar) -> Void)?
148+
149+
public var notifyFraction: ((ProgressBar) -> Void)?
150+
151+
public var notifyInverted: ((ProgressBar) -> Void)?
152+
153+
public var notifyPulseStep: ((ProgressBar) -> Void)?
154+
155+
public var notifyShowText: ((ProgressBar) -> Void)?
156+
157+
public var notifyText: ((ProgressBar) -> Void)?
158+
159+
public var notifyOrientation: ((ProgressBar) -> Void)?
160+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import CGtk3
2+
3+
extension Spinner {
4+
public func start() {
5+
gtk_spinner_start(castedPointer())
6+
}
7+
8+
public func stop() {
9+
gtk_spinner_stop(castedPointer())
10+
}
11+
}

0 commit comments

Comments
 (0)