Skip to content

Commit 16505f1

Browse files
authored
Update from GPT-4.1 copilot, to gtk4, and gtk3/opengl
Copilot Copilot navigation Copilot Chat GTK4 event handling code adaptation You said: The event management code in https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTK4_AdvancedGL.py on The event management code in https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTK4_AdvancedGL.py on line 52 to 61 is wrong. Can you change them into their GTK4 corresponding apis? 2/2 1 reference Copilot said: Here’s what needs to be changed for Here’s what needs to be changed for GTK4 event handling in the code block from line 52 to 61. In GTK4, the event mask system and event signals for widgets like Gtk.GLArea have changed: add_events() is deprecated/removed in GTK4; event masks are set differently or are simply not needed—the widget is expected to be “event controller” driven. Mouse and key events are handled via GtkEventController (for mouse: Gtk.EventControllerMotion and Gtk.GestureClick; for keyboard: Gtk.EventControllerKey). Below is the correct replacement for lines 52-61, using GTK4 idioms. This will move the event handling setup to use GTK4 event controllers and gestures: SkiaGTK4_AdvancedGL.py v1 52| # Setup event controllers for GTK4 53| self.motion_controller = Gtk.EventControllerMotion.new(self) 54| self.motion_controller.connect("motion", self.on_motion_notify) 55| 56| self.click_gesture = Gtk.GestureClick.new(self) 57| self.click_gesture.connect("pressed", self.on_button_press) You’ll also need to update the signatures of the event handler methods to match the new controller APIs: on_motion_notify(self, motion_controller, x, y) (instead of (widget, event)) on_button_press(self, gesture, n_press, x, y) and on_button_release(self, gesture, n_press, x, y) on_key_press(self, key_controller, keyval, keycode, state) Summary of changes: Remove add_events() and all old-style connect("event-type", ...) calls. Use GTK4 event controllers for mouse/keyboard events. Would you like a full code patch, including the updated event handler function signatures and logic? You said: Yes, i want a full code path Yes, i want a full code path Copilot said: ```python name=SkiaGTK4_AdvancedGL.py #!/usr/bin/env python3 # Skia + Gtk4 + SkiaGTK4_AdvancedGL.py v2 #!/usr/bin/env python3 # Skia + Gtk4 + OpenGL (hardware accelerated, advanced GPU features) example import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk, Gdk, GLib Summary of changes: Old event mask and signal-based event handling (lines 52-61) are replaced with GTK4 event controllers (Gtk.EventControllerMotion, Gtk.GestureClick, Gtk.EventControllerKey). The handler methods are updated to match the new controller signatures. The logic inside each handler is adapted as needed to work with the new API (e.g., using gesture.get_current_button()). This patch will make your event handling fully compatible with GTK4. You said: Can you improve https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTKGLExample.py ? It seems Can you improve https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTKGLExample.py ? It seems to be refreshing excessively. 1 reference Copilot said: The excessive refreshing in this code is The excessive refreshing in this code is mainly due to the line in on_realize: Python GLib.timeout_add(16, self.queue_draw) # ~60fps This causes the widget to redraw at ~60fps regardless of whether something actually changed. Improvement suggestion: Only trigger redraws (queue_draw) when something visual actually changes (e.g., star rotation, mouse drag, or new rect). Move the animation timer (GLib.timeout_add) to only run while the star is rotating or a drag is happening. When idle (no animation, no drag), stop the timer to avoid unnecessary draws. Below is an improved version of the file that only refreshes when needed. SkiaGTKGLExample.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk, GObject, GLib Key improvements: The animation timer is not always running; it's started only when the star needs to animate and stopped when not needed. Mouse drag events and static UI do not trigger continuous redraws—only queue a draw as needed. The UI is now much more efficient and should not refresh excessively. Let me know if you want further refinements (e.g., only animating star rotation on user input)! You said: Can you improve https://github.com/HinTak/skia-python-examples/blob/main/SkiaTkExample.py? It seems to Can you improve https://github.com/HinTak/skia-python-examples/blob/main/SkiaTkExample.py? It seems to be using a lot of CPU time and doing excessive refreshing 1 reference Copilot said: Here's how you can improve `SkiaTkExample.py` to Here's how you can improve SkiaTkExample.py to greatly reduce CPU usage and only refresh the UI when needed: Problems in the original code: The animate method uses root.after(16, self.animate) to refresh at ~60fps, regardless of whether anything is changing (e.g. no interaction, window not visible). The draw() method is called on every frame even if nothing has changed. The star is always rotating, causing continuous redraws. Improvements: Only animate the star when the window is focused/visible. Only trigger redraws on user interaction (drag, resize, etc.) or when actively animating. Pause the animation when the window is not focused or minimized. Avoid unnecessary surface recreation. Below is an improved version of your file: SkiaTkExample.py import tkinter as tk from skia import * import math import random HELP_MESSAGE = "Click and drag to create rects. Press esc to quit." Key Improvements: Animation runs only when the window is focused, greatly reducing CPU usage when the app is idle or in the background. Redraws occur only on interaction or during animation. Surface recreation only happens when the window is resized. Avoids unnecessary creation of PhotoImage objects. Let me know if you'd like further refinements or explanations! You said: https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTKExample.py does not use skia at all, https://github.com/HinTak/skia-python-examples/blob/main/SkiaGTKExample.py does not use skia at all, does it? Copilot said: You are correct: the file SkiaGTKExample.py in You are correct: the file SkiaGTKExample.py in the repository HinTak/skia-python-examples does not use Skia at all. Looking at the file's contents, it only uses GTK (via PyGObject), Cairo, and standard Python modules. There is no import or usage of skia or skia-python APIs. All drawing is done via Cairo (the GTK default graphics library), not Skia. If you want to update this example to actually use Skia for rendering, let me know—I'm happy to help you rewrite it to use skia-python for drawing! You said: Yes, please. Yes, please. Copilot said: Here is a version of `SkiaGTKExample.py` that Here is a version of SkiaGTKExample.py that actually uses skia-python for all drawing, replacing all Cairo usage with Skia rendering. This version creates a GTK drawing area, uses Skia to draw into a raster surface, and then converts the Skia surface to a GdkPixbuf for display in GTK. All interactive rectangle and text drawing will be handled by Skia. SkiaGTKExample.py v1 #!/usr/bin/env python3 import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk, GLib from skia import * Key points: All rendering is done in Skia, not Cairo. Drawing events (mouse/key) update Skia state and redraw. The Skia surface is converted to a PNG and rendered in GTK via a Pixbuf. The UI is interactive and all shapes, text, and the star are rendered using skia-python. Let me know if you need a GTK4 version or further features! You said: Yes, give me a GTK4 version please. Yes, give me a GTK4 version please. Copilot said: ```python name=SkiaGTKExample.py #!/usr/bin/env python3 import gi gi.require_version("Gtk", "4.0") from gi.repository import SkiaGTKExample.py v2 #!/usr/bin/env python3 import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk, Gdk from skia import * Notes: Uses Gtk.DrawingArea with set_draw_func (GTK4 idiom). Uses Skia for all rendering; outputs to a PNG, then displays via Gdk.Texture. Mouse and key events are handled via GTK4 event controllers. All interactive drawing is now Skia-backed, not Cairo. Let me know if you want further features or refinements! You said: Actually I want you to go back Actually I want you to go back a bit, set up GTK3 with a opengl context and do skia drawing through the opengl context. Copilot said: ```python name=SkiaGTKExample.py #!/usr/bin/env python3 import gi gi.require_version("Gtk", "3.0") from gi.repository import SkiaGTKExample.py v3 #!/usr/bin/env python3 import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk, GObject from OpenGL.GL import * from skia import * Highlights: Uses Gtk.GLArea for OpenGL context in GTK3. Skia draws directly into the OpenGL framebuffer via GrDirectContext. Interactive drawing with mouse events. Drawing is performed by Skia, not Cairo. All rendering is hardware accelerated (if supported). Let me know if you want further features or refinements!
1 parent 0e3c2d1 commit 16505f1

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

SkiaGTKExample_Version2.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
import gi
3+
gi.require_version("Gtk", "4.0")
4+
from gi.repository import Gtk, Gdk
5+
6+
from skia import *
7+
import random
8+
import math
9+
10+
HELP_MESSAGE = "Click and drag to create rects. Press esc to quit."
11+
12+
class ApplicationState:
13+
def __init__(self, width, height):
14+
self.window_width = width
15+
self.window_height = height
16+
self.fRects = []
17+
self.drag_rect = None
18+
19+
def create_star():
20+
kNumPoints = 5
21+
path = Path()
22+
angle = 2 * math.pi / kNumPoints
23+
points = []
24+
for i in range(kNumPoints):
25+
theta = i * angle - math.pi / 2
26+
x = math.cos(theta) * 50
27+
y = math.sin(theta) * 50
28+
points.append((x, y))
29+
path.moveTo(*points[0])
30+
for i in range(kNumPoints):
31+
idx = (2 * i) % kNumPoints
32+
path.lineTo(*points[idx])
33+
path.setFillType(PathFillType.kEvenOdd)
34+
path.close()
35+
return path
36+
37+
class SkiaGTKExample(Gtk.ApplicationWindow):
38+
def __init__(self, app):
39+
super().__init__(application=app, title="Skia + GTK4 Example")
40+
self.set_default_size(800, 600)
41+
self.state = ApplicationState(800, 600)
42+
self.drawing = False
43+
44+
self.darea = Gtk.DrawingArea()
45+
self.darea.set_content_width(self.state.window_width)
46+
self.darea.set_content_height(self.state.window_height)
47+
self.darea.set_draw_func(self.on_draw)
48+
self.set_child(self.darea)
49+
50+
# GTK4 Event Controllers
51+
self.click_gesture = Gtk.GestureClick.new()
52+
self.click_gesture.connect("pressed", self.on_button_press)
53+
self.click_gesture.connect("released", self.on_button_release)
54+
self.darea.add_controller(self.click_gesture)
55+
56+
self.motion_controller = Gtk.EventControllerMotion.new()
57+
self.motion_controller.connect("motion", self.on_motion_notify)
58+
self.darea.add_controller(self.motion_controller)
59+
60+
self.key_controller = Gtk.EventControllerKey.new()
61+
self.key_controller.connect("key-pressed", self.on_key_press)
62+
self.add_controller(self.key_controller)
63+
64+
def on_draw(self, area, cr, width, height):
65+
if (self.state.window_width != width) or (self.state.window_height != height):
66+
self.state.window_width = width
67+
self.state.window_height = height
68+
69+
# Skia surface to draw into
70+
surface = Surface(width, height)
71+
canvas = surface.getCanvas()
72+
canvas.clear(ColorWHITE)
73+
74+
# Draw help text
75+
paint = Paint(AntiAlias=True)
76+
paint.setColor(ColorBLACK)
77+
font = Font()
78+
canvas.drawString(HELP_MESSAGE, 10, font.getSize() + 10, font, paint)
79+
80+
# Draw rectangles
81+
random.seed(0)
82+
for rect in self.state.fRects:
83+
color = random.randint(0, 0xFFFFFFFF) | 0x44808080
84+
paint.setColor(color)
85+
canvas.drawRect(rect, paint)
86+
# Draw drag rect if present
87+
if self.state.drag_rect:
88+
paint.setColor(0x44808080)
89+
canvas.drawRect(self.state.drag_rect, paint)
90+
91+
# Draw star in the center
92+
canvas.save()
93+
cx, cy = width // 2, height // 2
94+
canvas.translate(cx, cy)
95+
canvas.drawPath(create_star(), paint)
96+
canvas.restore()
97+
98+
# Export Skia surface as PNG bytes and load into GdkPixbuf for GTK4
99+
img = surface.makeImageSnapshot()
100+
png_bytes = img.encodeToData()
101+
loader = Gdk.Texture.new_from_bytes(png_bytes)
102+
loader.draw(cr, 0, 0, width, height)
103+
return False
104+
105+
def on_button_press(self, gesture, n_press, x, y):
106+
if gesture.get_current_button() == Gdk.BUTTON_PRIMARY:
107+
self.drawing = True
108+
self.state.drag_rect = Rect.MakeLTRB(x, y, x, y)
109+
self.state.fRects.append(self.state.drag_rect)
110+
self.darea.queue_draw()
111+
112+
def on_motion_notify(self, motion_controller, x, y):
113+
if self.drawing and self.state.drag_rect:
114+
rect = self.state.drag_rect
115+
rect.fRight = x
116+
rect.fBottom = y
117+
self.darea.queue_draw()
118+
119+
def on_button_release(self, gesture, n_press, x, y):
120+
if gesture.get_current_button() == Gdk.BUTTON_PRIMARY:
121+
self.drawing = False
122+
self.state.drag_rect = None
123+
self.darea.queue_draw()
124+
125+
def on_key_press(self, key_controller, keyval, keycode, state):
126+
if keyval == Gdk.KEY_Escape:
127+
self.close()
128+
return True
129+
return False
130+
131+
class SkiaGTKApp(Gtk.Application):
132+
def __init__(self):
133+
super().__init__(application_id="org.example.SkiaGTKExample")
134+
135+
def do_activate(self):
136+
win = SkiaGTKExample(self)
137+
win.present()
138+
139+
def main():
140+
import sys
141+
app = SkiaGTKApp()
142+
app.run(sys.argv)
143+
144+
if __name__ == '__main__':
145+
main()

SkiaGTKExample_Version3.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env python3
2+
import gi
3+
gi.require_version("Gtk", "3.0")
4+
from gi.repository import Gtk, Gdk, GObject
5+
from OpenGL.GL import *
6+
from skia import *
7+
import random
8+
import math
9+
10+
HELP_MESSAGE = "Click and drag to create rects. Press esc to quit."
11+
12+
class ApplicationState:
13+
def __init__(self, width, height):
14+
self.window_width = width
15+
self.window_height = height
16+
self.fRects = []
17+
self.drag_rect = None
18+
19+
def create_star():
20+
kNumPoints = 5
21+
path = Path()
22+
angle = 2 * math.pi / kNumPoints
23+
points = []
24+
for i in range(kNumPoints):
25+
theta = i * angle - math.pi / 2
26+
x = math.cos(theta) * 50
27+
y = math.sin(theta) * 50
28+
points.append((x, y))
29+
path.moveTo(*points[0])
30+
for i in range(kNumPoints):
31+
idx = (2 * i) % kNumPoints
32+
path.lineTo(*points[idx])
33+
path.setFillType(PathFillType.kEvenOdd)
34+
path.close()
35+
return path
36+
37+
class SkiaGLArea(Gtk.GLArea):
38+
def __init__(self):
39+
super().__init__()
40+
self.set_has_depth_buffer(False)
41+
self.set_has_stencil_buffer(True)
42+
self.set_required_version(3, 2)
43+
self.connect("realize", self.on_realize)
44+
self.connect("render", self.on_render)
45+
self.connect("resize", self.on_resize)
46+
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
47+
Gdk.EventMask.BUTTON_RELEASE_MASK |
48+
Gdk.EventMask.POINTER_MOTION_MASK)
49+
self.set_can_focus(True)
50+
self.grab_focus()
51+
self.state = ApplicationState(800, 600)
52+
self.drawing = False
53+
self.grContext = None
54+
self.surface = None
55+
56+
def on_realize(self, area):
57+
self.make_current()
58+
glClearColor(1,1,1,1)
59+
glClearStencil(0)
60+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
61+
self.grContext = GrDirectContext.MakeGL()
62+
63+
def on_resize(self, area, width, height):
64+
self.state.window_width = width
65+
self.state.window_height = height
66+
self.surface = None # force recreation
67+
68+
def ensure_surface(self, width, height):
69+
if not self.grContext:
70+
self.grContext = GrDirectContext.MakeGL()
71+
if not self.surface or self.surface.width() != width or self.surface.height() != height:
72+
fb = glGetIntegerv(GL_FRAMEBUFFER_BINDING)
73+
info = GrGLFramebufferInfo(fb, GL_RGBA8)
74+
kStencilBits = 8
75+
kMsaaSampleCount = 0
76+
target = GrBackendRenderTarget(width, height, kMsaaSampleCount, kStencilBits, info)
77+
self.surface = Surface.MakeFromBackendRenderTarget(
78+
self.grContext, target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_ColorType, None, None
79+
)
80+
return self.surface
81+
82+
def on_render(self, area, glctx):
83+
width = self.get_allocated_width()
84+
height = self.get_allocated_height()
85+
glViewport(0, 0, width, height)
86+
glClearColor(1, 1, 1, 1)
87+
glClearStencil(0)
88+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
89+
surface = self.ensure_surface(width, height)
90+
canvas = surface.getCanvas()
91+
canvas.clear(ColorWHITE)
92+
93+
# Draw help text
94+
paint = Paint(AntiAlias=True)
95+
paint.setColor(ColorBLACK)
96+
font = Font()
97+
canvas.drawString(HELP_MESSAGE, 10, font.getSize() + 10, font, paint)
98+
99+
# Draw rectangles
100+
random.seed(0)
101+
for rect in self.state.fRects:
102+
color = random.randint(0, 0xFFFFFFFF) | 0x44808080
103+
paint.setColor(color)
104+
canvas.drawRect(rect, paint)
105+
# Draw drag rect if present
106+
if self.state.drag_rect:
107+
paint.setColor(0x44808080)
108+
canvas.drawRect(self.state.drag_rect, paint)
109+
110+
# Draw star in the center
111+
canvas.save()
112+
cx, cy = width // 2, height // 2
113+
canvas.translate(cx, cy)
114+
canvas.drawPath(create_star(), paint)
115+
canvas.restore()
116+
117+
canvas.flush()
118+
self.grContext.flush()
119+
return True
120+
121+
def do_button_press_event(self, event):
122+
if event.button == 1:
123+
self.drawing = True
124+
self.state.drag_rect = Rect.MakeLTRB(event.x, event.y, event.x, event.y)
125+
self.state.fRects.append(self.state.drag_rect)
126+
self.queue_draw()
127+
128+
def do_motion_notify_event(self, event):
129+
if self.drawing and self.state.drag_rect:
130+
rect = self.state.drag_rect
131+
rect.fRight = event.x
132+
rect.fBottom = event.y
133+
self.queue_draw()
134+
135+
def do_button_release_event(self, event):
136+
if event.button == 1 and self.drawing:
137+
self.drawing = False
138+
self.state.drag_rect = None
139+
self.queue_draw()
140+
141+
class SkiaGTKExample(Gtk.Window):
142+
def __init__(self):
143+
super().__init__(title="Skia + GTK3 + OpenGL Example")
144+
self.set_default_size(800, 600)
145+
self.area = SkiaGLArea()
146+
self.add(self.area)
147+
self.connect("key-press-event", self.on_key_press)
148+
self.connect("destroy", Gtk.main_quit)
149+
150+
def on_key_press(self, widget, event):
151+
if event.keyval == Gdk.KEY_Escape:
152+
Gtk.main_quit()
153+
154+
def main():
155+
win = SkiaGTKExample()
156+
win.show_all()
157+
Gtk.main()
158+
159+
if __name__ == '__main__':
160+
main()

0 commit comments

Comments
 (0)