Skip to content

Commit 6710908

Browse files
committed
GtkBackend,Gtk3Backend: Implement Path/Shape support (fixes #143)
1 parent c9502ce commit 6710908

File tree

10 files changed

+787
-38
lines changed

10 files changed

+787
-38
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import CGtk
2+
3+
/// Allows drawing with cairo.
4+
///
5+
/// <picture><source srcset="drawingarea-dark.png" media="(prefers-color-scheme: dark)"><img alt="An example GtkDrawingArea" src="drawingarea.png"></picture>
6+
///
7+
/// It’s essentially a blank widget; you can draw on it. After
8+
/// creating a drawing area, the application may want to connect to:
9+
///
10+
/// - The [signal@Gtk.Widget::realize] signal to take any necessary actions
11+
/// when the widget is instantiated on a particular display.
12+
/// (Create GDK resources in response to this signal.)
13+
///
14+
/// - The [signal@Gtk.DrawingArea::resize] signal to take any necessary
15+
/// actions when the widget changes size.
16+
///
17+
/// - Call [method@Gtk.DrawingArea.set_draw_func] to handle redrawing the
18+
/// contents of the widget.
19+
///
20+
/// The following code portion demonstrates using a drawing
21+
/// area to display a circle in the normal widget foreground
22+
/// color.
23+
///
24+
/// ## Simple GtkDrawingArea usage
25+
///
26+
/// ```c
27+
/// static void
28+
/// draw_function (GtkDrawingArea *area,
29+
/// cairo_t *cr,
30+
/// int width,
31+
/// int height,
32+
/// gpointer data)
33+
/// {
34+
/// GdkRGBA color;
35+
///
36+
/// cairo_arc (cr,
37+
/// width / 2.0, height / 2.0,
38+
/// MIN (width, height) / 2.0,
39+
/// 0, 2 * G_PI);
40+
///
41+
/// gtk_widget_get_color (GTK_WIDGET (area),
42+
/// &color);
43+
/// gdk_cairo_set_source_rgba (cr, &color);
44+
///
45+
/// cairo_fill (cr);
46+
/// }
47+
///
48+
/// int
49+
/// main (int argc, char **argv)
50+
/// {
51+
/// gtk_init ();
52+
///
53+
/// GtkWidget *area = gtk_drawing_area_new ();
54+
/// gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
55+
/// gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
56+
/// gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area),
57+
/// draw_function,
58+
/// NULL, NULL);
59+
/// return 0;
60+
/// }
61+
/// ```
62+
///
63+
/// The draw function is normally called when a drawing area first comes
64+
/// onscreen, or when it’s covered by another window and then uncovered.
65+
/// You can also force a redraw by adding to the “damage region” of the
66+
/// drawing area’s window using [method@Gtk.Widget.queue_draw].
67+
/// This will cause the drawing area to call the draw function again.
68+
///
69+
/// The available routines for drawing are documented in the
70+
/// [Cairo documentation](https://www.cairographics.org/manual/); GDK
71+
/// offers additional API to integrate with Cairo, like [func@Gdk.cairo_set_source_rgba]
72+
/// or [func@Gdk.cairo_set_source_pixbuf].
73+
///
74+
/// To receive mouse events on a drawing area, you will need to use
75+
/// event controllers. To receive keyboard events, you will need to set
76+
/// the “can-focus” property on the drawing area, and you should probably
77+
/// draw some user-visible indication that the drawing area is focused.
78+
///
79+
/// If you need more complex control over your widget, you should consider
80+
/// creating your own `GtkWidget` subclass.
81+
open class DrawingArea: Widget {
82+
/// Creates a new drawing area.
83+
public convenience init() {
84+
self.init(
85+
gtk_drawing_area_new()
86+
)
87+
}
88+
89+
override func didMoveToParent() {
90+
super.didMoveToParent()
91+
92+
let handler0:
93+
@convention(c) (UnsafeMutableRawPointer, Int, Int, UnsafeMutableRawPointer) -> Void =
94+
{ _, value1, value2, data in
95+
SignalBox2<Int, Int>.run(data, value1, value2)
96+
}
97+
98+
addSignal(name: "resize", handler: gCallback(handler0)) {
99+
[weak self] (param0: Int, param1: Int) in
100+
guard let self = self else { return }
101+
self.resize?(self, param0, param1)
102+
}
103+
104+
let handler1:
105+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
106+
{ _, value1, data in
107+
SignalBox1<OpaquePointer>.run(data, value1)
108+
}
109+
110+
addSignal(name: "notify::content-height", handler: gCallback(handler1)) {
111+
[weak self] (param0: OpaquePointer) in
112+
guard let self = self else { return }
113+
self.notifyContentHeight?(self, param0)
114+
}
115+
116+
let handler2:
117+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
118+
{ _, value1, data in
119+
SignalBox1<OpaquePointer>.run(data, value1)
120+
}
121+
122+
addSignal(name: "notify::content-width", handler: gCallback(handler2)) {
123+
[weak self] (param0: OpaquePointer) in
124+
guard let self = self else { return }
125+
self.notifyContentWidth?(self, param0)
126+
}
127+
}
128+
129+
/// The content height.
130+
@GObjectProperty(named: "content-height") public var contentHeight: Int
131+
132+
/// The content width.
133+
@GObjectProperty(named: "content-width") public var contentWidth: Int
134+
135+
/// Emitted once when the widget is realized, and then each time the widget
136+
/// is changed while realized.
137+
///
138+
/// This is useful in order to keep state up to date with the widget size,
139+
/// like for instance a backing surface.
140+
public var resize: ((DrawingArea, Int, Int) -> Void)?
141+
142+
public var notifyContentHeight: ((DrawingArea, OpaquePointer) -> Void)?
143+
144+
public var notifyContentWidth: ((DrawingArea, OpaquePointer) -> Void)?
145+
}

Sources/Gtk/Gsk/PathBuilder.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// import CGtk
2+
3+
// open class PathBuilder: GObject {
4+
// public init() {
5+
// super.init(gsk_path_builder_new())
6+
// }
7+
8+
// public func move(to point: SIMD2<Double>) {
9+
// gsk_path_builder_move_to(opaquePointer, Float(point.x), Float(point.y))
10+
// }
11+
12+
// public func line(to point: SIMD2<Double>) {
13+
// gsk_path_builder_line_to(opaquePointer, Float(point.x), Float(point.y))
14+
// }
15+
16+
// public func curve(to point: SIMD2<Double>, controlPoint: SIMD2<Double>) {
17+
// gsk_path_builder_quad_to(
18+
// opaquePointer,
19+
// Float(controlPoint.x),
20+
// Float(controlPoint.y),
21+
// Float(point.x),
22+
// Float(point.y)
23+
// )
24+
// }
25+
26+
// public func curve(
27+
// to point: SIMD2<Double>,
28+
// controlPoint1: SIMD2<Double>,
29+
// controlPoint2: SIMD2<Double>
30+
// ) {
31+
// gsk_path_builder_cubic_to(
32+
// opaquePointer,
33+
// Float(controlPoint1.x),
34+
// Float(controlPoint1.y),
35+
// Float(controlPoint2.x),
36+
// Float(controlPoint2.y),
37+
// Float(point.x),
38+
// Float(point.y)
39+
// )
40+
// }
41+
42+
// public func appendRect(origin: SIMD2<Double>, size: SIMD2<Double>) {
43+
// let rect = graphene_rect_alloc()
44+
// graphene_rect_init(
45+
// rect,
46+
// Float(origin.x),
47+
// Float(origin.y),
48+
// Float(size.x),
49+
// Float(size.y)
50+
// )
51+
// gsk_path_builder_add_rect(opaquePointer, rect)
52+
// graphene_rect_free(rect)
53+
// }
54+
55+
// public func appendCircle(center: SIMD2<Double>, radius: Double) {
56+
// let centerPoint = graphene_point_alloc()
57+
// graphene_point_init(centerPoint, Float(center.x), Float(center.y))
58+
// gsk_path_builder_add_circle(opaquePointer, centerPoint, Float(radius))
59+
// graphene_point_free(centerPoint)
60+
// }
61+
62+
// public func appendArc(
63+
// withCenter center: SIMD2<Double>,
64+
// radius: Double,
65+
// startAngle: Double,
66+
// endAngle: Double,
67+
// clockwise: Bool
68+
// ) {
69+
// gsk_path_builder_line_to(opaquePointer, Float(center.x + cos(startAngle)), Float(center.y + sin(endAngle)))
70+
// gsk_path_builder_move_to(opaquePointer, Float(center.x + cos(endAngle)), Float(center.y + sin(endAngle)))
71+
// }
72+
73+
// public consuming func finalize() -> OpaquePointer {
74+
// gsk_path_builder_free_to_path(opaquePointer)
75+
// }
76+
// }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import CGtk
2+
3+
extension DrawingArea {
4+
public func setDrawFunc(
5+
_ drawFunc: @escaping (
6+
_ cairo: OpaquePointer,
7+
_ width: Int,
8+
_ height: Int
9+
) -> Void
10+
) {
11+
let box = SignalBox3<OpaquePointer, Int, Int> { cairo, width, height in
12+
drawFunc(cairo, width, height)
13+
}
14+
15+
gtk_drawing_area_set_draw_func(
16+
castedPointer(),
17+
{ _, cairo, width, height, data in
18+
let box = Unmanaged<SignalBox3<OpaquePointer, Int, Int>>
19+
.fromOpaque(data!)
20+
.takeUnretainedValue()
21+
box.callback(cairo!, Int(width), Int(height))
22+
},
23+
Unmanaged.passRetained(box).toOpaque(),
24+
{ data in
25+
Unmanaged<SignalBox3<OpaquePointer, Int, Int>>
26+
.fromOpaque(data!)
27+
.release()
28+
}
29+
)
30+
}
31+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import CGtk3
2+
3+
/// The #GtkDrawingArea widget is used for creating custom user interface
4+
/// elements. It’s essentially a blank widget; you can draw on it. After
5+
/// creating a drawing area, the application may want to connect to:
6+
///
7+
/// - Mouse and button press signals to respond to input from
8+
/// the user. (Use gtk_widget_add_events() to enable events
9+
/// you wish to receive.)
10+
///
11+
/// - The #GtkWidget::realize signal to take any necessary actions
12+
/// when the widget is instantiated on a particular display.
13+
/// (Create GDK resources in response to this signal.)
14+
///
15+
/// - The #GtkWidget::size-allocate signal to take any necessary
16+
/// actions when the widget changes size.
17+
///
18+
/// - The #GtkWidget::draw signal to handle redrawing the
19+
/// contents of the widget.
20+
///
21+
/// The following code portion demonstrates using a drawing
22+
/// area to display a circle in the normal widget foreground
23+
/// color.
24+
///
25+
/// Note that GDK automatically clears the exposed area before sending
26+
/// the expose event, and that drawing is implicitly clipped to the exposed
27+
/// area. If you want to have a theme-provided background, you need
28+
/// to call gtk_render_background() in your ::draw method.
29+
///
30+
/// ## Simple GtkDrawingArea usage
31+
///
32+
/// |[<!-- language="C" -->
33+
/// gboolean
34+
/// draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data)
35+
/// {
36+
/// guint width, height;
37+
/// GdkRGBA color;
38+
/// GtkStyleContext *context;
39+
///
40+
/// context = gtk_widget_get_style_context (widget);
41+
///
42+
/// width = gtk_widget_get_allocated_width (widget);
43+
/// height = gtk_widget_get_allocated_height (widget);
44+
///
45+
/// gtk_render_background (context, cr, 0, 0, width, height);
46+
///
47+
/// cairo_arc (cr,
48+
/// width / 2.0, height / 2.0,
49+
/// MIN (width, height) / 2.0,
50+
/// 0, 2 * G_PI);
51+
///
52+
/// gtk_style_context_get_color (context,
53+
/// gtk_style_context_get_state (context),
54+
/// &color);
55+
/// gdk_cairo_set_source_rgba (cr, &color);
56+
///
57+
/// cairo_fill (cr);
58+
///
59+
/// return FALSE;
60+
/// }
61+
/// [...]
62+
/// GtkWidget *drawing_area = gtk_drawing_area_new ();
63+
/// gtk_widget_set_size_request (drawing_area, 100, 100);
64+
/// g_signal_connect (G_OBJECT (drawing_area), "draw",
65+
/// G_CALLBACK (draw_callback), NULL);
66+
/// ]|
67+
///
68+
/// Draw signals are normally delivered when a drawing area first comes
69+
/// onscreen, or when it’s covered by another window and then uncovered.
70+
/// You can also force an expose event by adding to the “damage region”
71+
/// of the drawing area’s window; gtk_widget_queue_draw_area() and
72+
/// gdk_window_invalidate_rect() are equally good ways to do this.
73+
/// You’ll then get a draw signal for the invalid region.
74+
///
75+
/// The available routines for drawing are documented on the
76+
/// [GDK Drawing Primitives][gdk3-Cairo-Interaction] page
77+
/// and the cairo documentation.
78+
///
79+
/// To receive mouse events on a drawing area, you will need to enable
80+
/// them with gtk_widget_add_events(). To receive keyboard events, you
81+
/// will need to set the “can-focus” property on the drawing area, and you
82+
/// should probably draw some user-visible indication that the drawing
83+
/// area is focused. Use gtk_widget_has_focus() in your expose event
84+
/// handler to decide whether to draw the focus indicator. See
85+
/// gtk_render_focus() for one way to draw focus.
86+
open class DrawingArea: Widget {
87+
/// Creates a new drawing area.
88+
public convenience init() {
89+
self.init(
90+
gtk_drawing_area_new()
91+
)
92+
}
93+
94+
}

0 commit comments

Comments
 (0)