Skip to content

Commit 82aa7db

Browse files
committed
Implement onClick modifier (AppKit, Gtk4, Gtk3)
1 parent aaa935c commit 82aa7db

File tree

20 files changed

+1044
-10
lines changed

20 files changed

+1044
-10
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ public final class AppKitBackend: AppBackend {
397397
// though it's not editable. It prevents the text from resetting to default
398398
// styles when clicked (yeah that happens...)
399399
field.allowsEditingTextAttributes = true
400+
field.isSelectable = false
400401
return field
401402
}
402403

@@ -1006,6 +1007,42 @@ public final class AppKitBackend: AppBackend {
10061007
handleResponse(response)
10071008
}
10081009
}
1010+
1011+
public func createClickTarget(wrapping child: Widget) -> Widget {
1012+
let container = NSView()
1013+
1014+
container.addSubview(child)
1015+
child.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
1016+
child.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
1017+
child.translatesAutoresizingMaskIntoConstraints = false
1018+
1019+
let clickTarget = NSCustomClickTarget()
1020+
container.addSubview(clickTarget)
1021+
clickTarget.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
1022+
clickTarget.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
1023+
clickTarget.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
1024+
clickTarget.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
1025+
clickTarget.translatesAutoresizingMaskIntoConstraints = false
1026+
1027+
return container
1028+
1029+
}
1030+
1031+
public func updateClickTarget(
1032+
_ container: Widget,
1033+
clickHandler handleClick: @escaping () -> Void
1034+
) {
1035+
let clickTarget = container.subviews[1] as! NSCustomClickTarget
1036+
clickTarget.leftClickHandler = handleClick
1037+
}
1038+
}
1039+
1040+
final class NSCustomClickTarget: NSView {
1041+
var leftClickHandler: (() -> Void)?
1042+
1043+
override func mouseDown(with event: NSEvent) {
1044+
leftClickHandler?()
1045+
}
10091046
}
10101047

10111048
final class NSCustomMenuItem: NSMenuItem {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import CGtk
2+
3+
/// `GtkEventController` is the base class for event controllers.
4+
///
5+
/// These are ancillary objects associated to widgets, which react
6+
/// to `GdkEvents`, and possibly trigger actions as a consequence.
7+
///
8+
/// Event controllers are added to a widget with
9+
/// [method@Gtk.Widget.add_controller]. It is rarely necessary to
10+
/// explicitly remove a controller with [method@Gtk.Widget.remove_controller].
11+
///
12+
/// See the chapter on [input handling](input-handling.html) for
13+
/// an overview of the basic concepts, such as the capture and bubble
14+
/// phases of event propagation.
15+
public class EventController: GObject {
16+
17+
public override func registerSignals() {
18+
super.registerSignals()
19+
20+
let handler0:
21+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
22+
{ _, value1, data in
23+
SignalBox1<OpaquePointer>.run(data, value1)
24+
}
25+
26+
addSignal(name: "notify::name", handler: gCallback(handler0)) {
27+
[weak self] (param0: OpaquePointer) in
28+
guard let self = self else { return }
29+
self.notifyName?(self, param0)
30+
}
31+
32+
let handler1:
33+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
34+
{ _, value1, data in
35+
SignalBox1<OpaquePointer>.run(data, value1)
36+
}
37+
38+
addSignal(name: "notify::propagation-limit", handler: gCallback(handler1)) {
39+
[weak self] (param0: OpaquePointer) in
40+
guard let self = self else { return }
41+
self.notifyPropagationLimit?(self, param0)
42+
}
43+
44+
let handler2:
45+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
46+
{ _, value1, data in
47+
SignalBox1<OpaquePointer>.run(data, value1)
48+
}
49+
50+
addSignal(name: "notify::propagation-phase", handler: gCallback(handler2)) {
51+
[weak self] (param0: OpaquePointer) in
52+
guard let self = self else { return }
53+
self.notifyPropagationPhase?(self, param0)
54+
}
55+
56+
let handler3:
57+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
58+
{ _, value1, data in
59+
SignalBox1<OpaquePointer>.run(data, value1)
60+
}
61+
62+
addSignal(name: "notify::widget", handler: gCallback(handler3)) {
63+
[weak self] (param0: OpaquePointer) in
64+
guard let self = self else { return }
65+
self.notifyWidget?(self, param0)
66+
}
67+
}
68+
69+
/// The name for this controller, typically used for debugging purposes.
70+
@GObjectProperty(named: "name") public var name: String?
71+
72+
/// The limit for which events this controller will handle.
73+
@GObjectProperty(named: "propagation-limit") public var propagationLimit: PropagationLimit
74+
75+
/// The propagation phase at which this controller will handle events.
76+
@GObjectProperty(named: "propagation-phase") public var propagationPhase: PropagationPhase
77+
78+
public var notifyName: ((EventController, OpaquePointer) -> Void)?
79+
80+
public var notifyPropagationLimit: ((EventController, OpaquePointer) -> Void)?
81+
82+
public var notifyPropagationPhase: ((EventController, OpaquePointer) -> Void)?
83+
84+
public var notifyWidget: ((EventController, OpaquePointer) -> Void)?
85+
}

Sources/Gtk/Generated/Gesture.swift

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import CGtk
2+
3+
/// `GtkGesture` is the base class for gesture recognition.
4+
///
5+
/// Although `GtkGesture` is quite generalized to serve as a base for
6+
/// multi-touch gestures, it is suitable to implement single-touch and
7+
/// pointer-based gestures (using the special %NULL `GdkEventSequence`
8+
/// value for these).
9+
///
10+
/// The number of touches that a `GtkGesture` need to be recognized is
11+
/// controlled by the [property@Gtk.Gesture:n-points] property, if a
12+
/// gesture is keeping track of less or more than that number of sequences,
13+
/// it won't check whether the gesture is recognized.
14+
///
15+
/// As soon as the gesture has the expected number of touches, it will check
16+
/// regularly if it is recognized, the criteria to consider a gesture as
17+
/// "recognized" is left to `GtkGesture` subclasses.
18+
///
19+
/// A recognized gesture will then emit the following signals:
20+
///
21+
/// - [signal@Gtk.Gesture::begin] when the gesture is recognized.
22+
/// - [signal@Gtk.Gesture::update], whenever an input event is processed.
23+
/// - [signal@Gtk.Gesture::end] when the gesture is no longer recognized.
24+
///
25+
/// ## Event propagation
26+
///
27+
/// In order to receive events, a gesture needs to set a propagation phase
28+
/// through [method@Gtk.EventController.set_propagation_phase].
29+
///
30+
/// In the capture phase, events are propagated from the toplevel down
31+
/// to the target widget, and gestures that are attached to containers
32+
/// above the widget get a chance to interact with the event before it
33+
/// reaches the target.
34+
///
35+
/// In the bubble phase, events are propagated up from the target widget
36+
/// to the toplevel, and gestures that are attached to containers above
37+
/// the widget get a chance to interact with events that have not been
38+
/// handled yet.
39+
///
40+
/// ## States of a sequence
41+
///
42+
/// Whenever input interaction happens, a single event may trigger a cascade
43+
/// of `GtkGesture`s, both across the parents of the widget receiving the
44+
/// event and in parallel within an individual widget. It is a responsibility
45+
/// of the widgets using those gestures to set the state of touch sequences
46+
/// accordingly in order to enable cooperation of gestures around the
47+
/// `GdkEventSequence`s triggering those.
48+
///
49+
/// Within a widget, gestures can be grouped through [method@Gtk.Gesture.group].
50+
/// Grouped gestures synchronize the state of sequences, so calling
51+
/// [method@Gtk.Gesture.set_state] on one will effectively propagate
52+
/// the state throughout the group.
53+
///
54+
/// By default, all sequences start out in the %GTK_EVENT_SEQUENCE_NONE state,
55+
/// sequences in this state trigger the gesture event handler, but event
56+
/// propagation will continue unstopped by gestures.
57+
///
58+
/// If a sequence enters into the %GTK_EVENT_SEQUENCE_DENIED state, the gesture
59+
/// group will effectively ignore the sequence, letting events go unstopped
60+
/// through the gesture, but the "slot" will still remain occupied while
61+
/// the touch is active.
62+
///
63+
/// If a sequence enters in the %GTK_EVENT_SEQUENCE_CLAIMED state, the gesture
64+
/// group will grab all interaction on the sequence, by:
65+
///
66+
/// - Setting the same sequence to %GTK_EVENT_SEQUENCE_DENIED on every other
67+
/// gesture group within the widget, and every gesture on parent widgets
68+
/// in the propagation chain.
69+
/// - Emitting [signal@Gtk.Gesture::cancel] on every gesture in widgets
70+
/// underneath in the propagation chain.
71+
/// - Stopping event propagation after the gesture group handles the event.
72+
///
73+
/// Note: if a sequence is set early to %GTK_EVENT_SEQUENCE_CLAIMED on
74+
/// %GDK_TOUCH_BEGIN/%GDK_BUTTON_PRESS (so those events are captured before
75+
/// reaching the event widget, this implies %GTK_PHASE_CAPTURE), one similar
76+
/// event will be emulated if the sequence changes to %GTK_EVENT_SEQUENCE_DENIED.
77+
/// This way event coherence is preserved before event propagation is unstopped
78+
/// again.
79+
///
80+
/// Sequence states can't be changed freely.
81+
/// See [method@Gtk.Gesture.set_state] to know about the possible
82+
/// lifetimes of a `GdkEventSequence`.
83+
///
84+
/// ## Touchpad gestures
85+
///
86+
/// On the platforms that support it, `GtkGesture` will handle transparently
87+
/// touchpad gesture events. The only precautions users of `GtkGesture` should
88+
/// do to enable this support are:
89+
///
90+
/// - If the gesture has %GTK_PHASE_NONE, ensuring events of type
91+
/// %GDK_TOUCHPAD_SWIPE and %GDK_TOUCHPAD_PINCH are handled by the `GtkGesture`
92+
public class Gesture: EventController {
93+
94+
public override func registerSignals() {
95+
super.registerSignals()
96+
97+
let handler0:
98+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
99+
{ _, value1, data in
100+
SignalBox1<OpaquePointer>.run(data, value1)
101+
}
102+
103+
addSignal(name: "begin", handler: gCallback(handler0)) {
104+
[weak self] (param0: OpaquePointer) in
105+
guard let self = self else { return }
106+
self.begin?(self, param0)
107+
}
108+
109+
let handler1:
110+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
111+
{ _, value1, data in
112+
SignalBox1<OpaquePointer>.run(data, value1)
113+
}
114+
115+
addSignal(name: "cancel", handler: gCallback(handler1)) {
116+
[weak self] (param0: OpaquePointer) in
117+
guard let self = self else { return }
118+
self.cancel?(self, param0)
119+
}
120+
121+
let handler2:
122+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
123+
{ _, value1, data in
124+
SignalBox1<OpaquePointer>.run(data, value1)
125+
}
126+
127+
addSignal(name: "end", handler: gCallback(handler2)) {
128+
[weak self] (param0: OpaquePointer) in
129+
guard let self = self else { return }
130+
self.end?(self, param0)
131+
}
132+
133+
let handler3:
134+
@convention(c) (
135+
UnsafeMutableRawPointer, OpaquePointer, GtkEventSequenceState,
136+
UnsafeMutableRawPointer
137+
) -> Void =
138+
{ _, value1, value2, data in
139+
SignalBox2<OpaquePointer, GtkEventSequenceState>.run(data, value1, value2)
140+
}
141+
142+
addSignal(name: "sequence-state-changed", handler: gCallback(handler3)) {
143+
[weak self] (param0: OpaquePointer, param1: GtkEventSequenceState) in
144+
guard let self = self else { return }
145+
self.sequenceStateChanged?(self, param0, param1)
146+
}
147+
148+
let handler4:
149+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
150+
{ _, value1, data in
151+
SignalBox1<OpaquePointer>.run(data, value1)
152+
}
153+
154+
addSignal(name: "update", handler: gCallback(handler4)) {
155+
[weak self] (param0: OpaquePointer) in
156+
guard let self = self else { return }
157+
self.update?(self, param0)
158+
}
159+
160+
let handler5:
161+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
162+
{ _, value1, data in
163+
SignalBox1<OpaquePointer>.run(data, value1)
164+
}
165+
166+
addSignal(name: "notify::n-points", handler: gCallback(handler5)) {
167+
[weak self] (param0: OpaquePointer) in
168+
guard let self = self else { return }
169+
self.notifyNPoints?(self, param0)
170+
}
171+
}
172+
173+
/// Emitted when the gesture is recognized.
174+
///
175+
/// This means the number of touch sequences matches
176+
/// [property@Gtk.Gesture:n-points].
177+
///
178+
/// Note: These conditions may also happen when an extra touch
179+
/// (eg. a third touch on a 2-touches gesture) is lifted, in that
180+
/// situation @sequence won't pertain to the current set of active
181+
/// touches, so don't rely on this being true.
182+
public var begin: ((Gesture, OpaquePointer) -> Void)?
183+
184+
/// Emitted whenever a sequence is cancelled.
185+
///
186+
/// This usually happens on active touches when
187+
/// [method@Gtk.EventController.reset] is called on @gesture
188+
/// (manually, due to grabs...), or the individual @sequence
189+
/// was claimed by parent widgets' controllers (see
190+
/// [method@Gtk.Gesture.set_sequence_state]).
191+
///
192+
/// @gesture must forget everything about @sequence as in
193+
/// response to this signal.
194+
public var cancel: ((Gesture, OpaquePointer) -> Void)?
195+
196+
/// Emitted when @gesture either stopped recognizing the event
197+
/// sequences as something to be handled, or the number of touch
198+
/// sequences became higher or lower than [property@Gtk.Gesture:n-points].
199+
///
200+
/// Note: @sequence might not pertain to the group of sequences that
201+
/// were previously triggering recognition on @gesture (ie. a just
202+
/// pressed touch sequence that exceeds [property@Gtk.Gesture:n-points]).
203+
/// This situation may be detected by checking through
204+
/// [method@Gtk.Gesture.handles_sequence].
205+
public var end: ((Gesture, OpaquePointer) -> Void)?
206+
207+
/// Emitted whenever a sequence state changes.
208+
///
209+
/// See [method@Gtk.Gesture.set_sequence_state] to know
210+
/// more about the expectable sequence lifetimes.
211+
public var sequenceStateChanged: ((Gesture, OpaquePointer, GtkEventSequenceState) -> Void)?
212+
213+
/// Emitted whenever an event is handled while the gesture is recognized.
214+
///
215+
/// @sequence is guaranteed to pertain to the set of active touches.
216+
public var update: ((Gesture, OpaquePointer) -> Void)?
217+
218+
public var notifyNPoints: ((Gesture, OpaquePointer) -> Void)?
219+
}

0 commit comments

Comments
 (0)