1 | #include "gtkshaderbin.h" |
2 | |
3 | typedef struct { |
4 | GskGLShader *shader; |
5 | GtkStateFlags state; |
6 | GtkStateFlags state_mask; |
7 | float ; |
8 | gboolean compiled; |
9 | gboolean compiled_ok; |
10 | } ShaderInfo; |
11 | |
12 | struct _GtkShaderBin |
13 | { |
14 | GtkWidget parent_instance; |
15 | GtkWidget *child; |
16 | ShaderInfo *active_shader; |
17 | GPtrArray *shaders; |
18 | guint tick_id; |
19 | float time; |
20 | float mouse_x, mouse_y; |
21 | gint64 first_frame_time; |
22 | }; |
23 | |
24 | struct _GtkShaderBinClass |
25 | { |
26 | GtkWidgetClass parent_class; |
27 | }; |
28 | |
29 | G_DEFINE_TYPE (GtkShaderBin, gtk_shader_bin, GTK_TYPE_WIDGET) |
30 | |
31 | static void |
32 | shader_info_free (ShaderInfo *info) |
33 | { |
34 | g_object_unref (object: info->shader); |
35 | g_free (mem: info); |
36 | } |
37 | |
38 | static void |
39 | gtk_shader_bin_finalize (GObject *object) |
40 | { |
41 | GtkShaderBin *self = GTK_SHADER_BIN (ptr: object); |
42 | |
43 | g_ptr_array_free (array: self->shaders, TRUE); |
44 | |
45 | G_OBJECT_CLASS (gtk_shader_bin_parent_class)->finalize (object); |
46 | } |
47 | |
48 | static void |
49 | gtk_shader_bin_dispose (GObject *object) |
50 | { |
51 | GtkShaderBin *self = GTK_SHADER_BIN (ptr: object); |
52 | |
53 | g_clear_pointer (&self->child, gtk_widget_unparent); |
54 | |
55 | G_OBJECT_CLASS (gtk_shader_bin_parent_class)->dispose (object); |
56 | } |
57 | |
58 | static gboolean |
59 | gtk_shader_bin_tick (GtkWidget *widget, |
60 | GdkFrameClock *frame_clock, |
61 | gpointer unused) |
62 | { |
63 | GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget); |
64 | gint64 frame_time; |
65 | |
66 | frame_time = gdk_frame_clock_get_frame_time (frame_clock); |
67 | if (self->first_frame_time == 0) |
68 | self->first_frame_time = frame_time; |
69 | self->time = (frame_time - self->first_frame_time) / (float)G_USEC_PER_SEC; |
70 | |
71 | gtk_widget_queue_draw (widget); |
72 | |
73 | return G_SOURCE_CONTINUE; |
74 | } |
75 | |
76 | static void |
77 | motion_cb (GtkEventControllerMotion *controller, |
78 | double x, |
79 | double y, |
80 | GtkShaderBin *self) |
81 | { |
82 | self->mouse_x = x; |
83 | self->mouse_y = y; |
84 | } |
85 | |
86 | static void |
87 | gtk_shader_bin_init (GtkShaderBin *self) |
88 | { |
89 | GtkEventController *controller; |
90 | self->shaders = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)shader_info_free); |
91 | |
92 | controller = gtk_event_controller_motion_new (); |
93 | g_signal_connect (controller, "motion" , G_CALLBACK (motion_cb), self); |
94 | gtk_widget_add_controller (GTK_WIDGET (self), controller); |
95 | } |
96 | |
97 | void |
98 | gtk_shader_bin_update_active_shader (GtkShaderBin *self) |
99 | { |
100 | GtkStateFlags new_state = gtk_widget_get_state_flags (GTK_WIDGET (self)); |
101 | ShaderInfo *new_shader = NULL; |
102 | |
103 | for (int i = 0; i < self->shaders->len; i++) |
104 | { |
105 | ShaderInfo *info = g_ptr_array_index (self->shaders, i); |
106 | |
107 | if ((info->state_mask & new_state) == info->state) |
108 | { |
109 | new_shader = info; |
110 | break; |
111 | } |
112 | } |
113 | |
114 | if (self->active_shader == new_shader) |
115 | return; |
116 | |
117 | self->active_shader = new_shader; |
118 | self->first_frame_time = 0; |
119 | |
120 | if (self->active_shader) |
121 | { |
122 | if (self->tick_id == 0) |
123 | self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), |
124 | callback: gtk_shader_bin_tick, |
125 | NULL, NULL); |
126 | } |
127 | else |
128 | { |
129 | if (self->tick_id != 0) |
130 | { |
131 | gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: self->tick_id); |
132 | self->tick_id = 0; |
133 | } |
134 | } |
135 | |
136 | gtk_widget_queue_draw (GTK_WIDGET (self)); |
137 | } |
138 | |
139 | static void |
140 | gtk_shader_bin_state_flags_changed (GtkWidget *widget, |
141 | GtkStateFlags previous_state_flags) |
142 | { |
143 | GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget); |
144 | |
145 | gtk_shader_bin_update_active_shader (self); |
146 | } |
147 | |
148 | void |
149 | gtk_shader_bin_add_shader (GtkShaderBin *self, |
150 | GskGLShader *shader, |
151 | GtkStateFlags state, |
152 | GtkStateFlags state_mask, |
153 | float ) |
154 | { |
155 | ShaderInfo *info = g_new0 (ShaderInfo, 1); |
156 | info->shader = g_object_ref (shader); |
157 | info->state = state; |
158 | info->state_mask = state_mask; |
159 | info->extra_border = extra_border; |
160 | |
161 | g_ptr_array_add (array: self->shaders, data: info); |
162 | |
163 | gtk_shader_bin_update_active_shader (self); |
164 | } |
165 | |
166 | void |
167 | gtk_shader_bin_set_child (GtkShaderBin *self, |
168 | GtkWidget *child) |
169 | { |
170 | |
171 | if (self->child == child) |
172 | return; |
173 | |
174 | g_clear_pointer (&self->child, gtk_widget_unparent); |
175 | |
176 | if (child) |
177 | { |
178 | self->child = child; |
179 | gtk_widget_set_parent (widget: child, GTK_WIDGET (self)); |
180 | } |
181 | } |
182 | |
183 | GtkWidget * |
184 | gtk_shader_bin_get_child (GtkShaderBin *self) |
185 | { |
186 | return self->child; |
187 | } |
188 | |
189 | static void |
190 | gtk_shader_bin_snapshot (GtkWidget *widget, |
191 | GtkSnapshot *snapshot) |
192 | { |
193 | GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget); |
194 | int width, height; |
195 | |
196 | width = gtk_widget_get_width (widget); |
197 | height = gtk_widget_get_height (widget); |
198 | |
199 | if (self->active_shader) |
200 | { |
201 | if (!self->active_shader->compiled) |
202 | { |
203 | GtkNative *native = gtk_widget_get_native (widget); |
204 | GskRenderer *renderer = gtk_native_get_renderer (self: native); |
205 | GError *error = NULL; |
206 | |
207 | self->active_shader->compiled = TRUE; |
208 | self->active_shader->compiled_ok = |
209 | gsk_gl_shader_compile (shader: self->active_shader->shader, |
210 | renderer, error: &error); |
211 | if (!self->active_shader->compiled_ok) |
212 | { |
213 | g_warning ("GtkShaderBin failed to compile shader: %s" , error->message); |
214 | g_error_free (error); |
215 | } |
216 | } |
217 | |
218 | if (self->active_shader->compiled_ok) |
219 | { |
220 | float border = self->active_shader->extra_border; |
221 | graphene_vec2_t mouse; |
222 | graphene_vec2_init (v: &mouse, x: self->mouse_x + border, y: self->mouse_y + border); |
223 | gtk_snapshot_push_gl_shader (snapshot, shader: self->active_shader->shader, |
224 | bounds: &GRAPHENE_RECT_INIT(-border, -border, width+2*border, height+2*border), |
225 | take_args: gsk_gl_shader_format_args (shader: self->active_shader->shader, |
226 | "u_time" , self->time, |
227 | "u_mouse" , &mouse, |
228 | NULL)); |
229 | gtk_widget_snapshot_child (widget, child: self->child, snapshot); |
230 | gtk_snapshot_gl_shader_pop_texture (snapshot); |
231 | gtk_snapshot_pop (snapshot); |
232 | |
233 | return; |
234 | } |
235 | } |
236 | |
237 | /* Non-shader fallback */ |
238 | gtk_widget_snapshot_child (widget, child: self->child, snapshot); |
239 | } |
240 | |
241 | static void |
242 | gtk_shader_bin_class_init (GtkShaderBinClass *class) |
243 | { |
244 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
245 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
246 | |
247 | object_class->finalize = gtk_shader_bin_finalize; |
248 | object_class->dispose = gtk_shader_bin_dispose; |
249 | |
250 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
251 | |
252 | widget_class->snapshot = gtk_shader_bin_snapshot; |
253 | widget_class->state_flags_changed = gtk_shader_bin_state_flags_changed; |
254 | } |
255 | |
256 | GtkWidget * |
257 | gtk_shader_bin_new (void) |
258 | { |
259 | GtkShaderBin *self; |
260 | |
261 | self = g_object_new (GTK_TYPE_SHADER_BIN, NULL); |
262 | |
263 | return GTK_WIDGET (self); |
264 | } |
265 | |