gtk4 4.2.2 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/gtk4.gemspec +1 -1
- data/lib/gtk4/loader.rb +2 -0
- data/lib/gtk4/text-buffer.rb +11 -0
- data/sample/demos/gtk-demo/README.md +3 -0
- data/sample/demos/gtk-demo/data/scalable/apps/org.gtk.Demo4.svg +1 -0
- data/sample/demos/gtk-demo/data/symbolic/apps/org.gtk.Demo4-symbolic.svg +114 -0
- data/sample/demos/gtk-demo/demo.gresource +0 -0
- data/sample/demos/gtk-demo/demo.gresource.xml +474 -0
- data/sample/demos/gtk-demo/demo.rb +82 -0
- data/sample/demos/gtk-demo/glarea-gl.fs.glsl +9 -0
- data/sample/demos/gtk-demo/glarea-gl.vs.glsl +8 -0
- data/sample/demos/gtk-demo/glarea-gles.fs.glsl +7 -0
- data/sample/demos/gtk-demo/glarea-gles.vs.glsl +7 -0
- data/sample/demos/gtk-demo/glarea.rb +323 -0
- data/sample/demos/gtk-demo/main-listitem.ui +25 -0
- data/sample/demos/gtk-demo/main.rb +264 -0
- data/sample/demos/gtk-demo/main.ui +184 -0
- metadata +21 -7
| @@ -0,0 +1,323 @@ | |
| 1 | 
            +
            # Copyright (C) 2024  Ruby-GNOME Project Team
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # This library is free software; you can redistribute it and/or
         | 
| 4 | 
            +
            # modify it under the terms of the GNU Lesser General Public
         | 
| 5 | 
            +
            # License as published by the Free Software Foundation; either
         | 
| 6 | 
            +
            # version 2.1 of the License, or (at your option) any later version.
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # This library is distributed in the hope that it will be useful,
         | 
| 9 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 10 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         | 
| 11 | 
            +
            # Lesser General Public License for more details.
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # You should have received a copy of the GNU Lesser General Public
         | 
| 14 | 
            +
            # License along with this library; if not, write to the Free Software
         | 
| 15 | 
            +
            # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            # Original:
         | 
| 18 | 
            +
            #   https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/glarea.c
         | 
| 19 | 
            +
            #
         | 
| 20 | 
            +
            # See the following for license information:
         | 
| 21 | 
            +
            #   * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/AUTHORS
         | 
| 22 | 
            +
            #   * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/COPYING
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            require "opengl"
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            class GtkGLAreaDemo < GtkDemo
         | 
| 27 | 
            +
              X_AXIS = 0
         | 
| 28 | 
            +
              Y_AXIS = 1
         | 
| 29 | 
            +
              Z_AXIS = 2
         | 
| 30 | 
            +
              N_AXISES = 3
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def initialize
         | 
| 33 | 
            +
                super(__FILE__,
         | 
| 34 | 
            +
                      "OpenGL/OpenGL Area",
         | 
| 35 | 
            +
                      "Gtk::GLArea is a widget that allows custom drawing using OpenGL calls.",
         | 
| 36 | 
            +
                      [])
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def run(parent)
         | 
| 40 | 
            +
                window = Gtk::Window.new
         | 
| 41 | 
            +
                window.display = parent.display
         | 
| 42 | 
            +
                window.title = "OpenGL Area"
         | 
| 43 | 
            +
                window.set_default_size(400, 600)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                box = Gtk::Box.new(:vertical)
         | 
| 46 | 
            +
                box.margin_start = 12
         | 
| 47 | 
            +
                box.margin_end = 12
         | 
| 48 | 
            +
                box.margin_top = 12
         | 
| 49 | 
            +
                box.margin_bottom = 12
         | 
| 50 | 
            +
                box.spacing = 6
         | 
| 51 | 
            +
                window.child = box
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                gl_area = Gtk::GLArea.new
         | 
| 54 | 
            +
                gl_area.hexpand = true
         | 
| 55 | 
            +
                gl_area.vexpand = true
         | 
| 56 | 
            +
                gl_area.set_size_request(100, 200)
         | 
| 57 | 
            +
                box.append(gl_area)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                # We need to initialize and free GL resources, so we use the
         | 
| 60 | 
            +
                # realize and unrealize signals on the widget
         | 
| 61 | 
            +
                gl_area.signal_connect("realize") do
         | 
| 62 | 
            +
                  realize(gl_area)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
                gl_area.signal_connect("unrealize") do
         | 
| 65 | 
            +
                  unrealize(gl_area)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # The main "draw" call for GtkGLArea
         | 
| 69 | 
            +
                gl_area.signal_connect("render") do
         | 
| 70 | 
            +
                  render(gl_area)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                controls = Gtk::Box.new(:vertical)
         | 
| 74 | 
            +
                controls.hexpand = true
         | 
| 75 | 
            +
                box.append(controls)
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                N_AXISES.times do |i|
         | 
| 78 | 
            +
                  controls.append(create_axis_slider(gl_area, i))
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                button = Gtk::Button.new(label: "Quit")
         | 
| 82 | 
            +
                button.hexpand = true
         | 
| 83 | 
            +
                box.append(button)
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                button.signal_connect("clicked") do
         | 
| 86 | 
            +
                  window.destroy
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                window.present
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                window
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              private
         | 
| 95 | 
            +
              # We need to set up our state when we realize the GtkGLArea widget
         | 
| 96 | 
            +
              def realize(gl_area)
         | 
| 97 | 
            +
                @rotation_angles = [0.0] * N_AXISES
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                GL.load_lib
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                gl_area.make_current
         | 
| 102 | 
            +
                api_name = gl_area.api.nick
         | 
| 103 | 
            +
                vertex_path = "/glarea/glarea-#{api_name}.vs.glsl"
         | 
| 104 | 
            +
                fragment_path = "/glarea/glarea-#{api_name}.fs.glsl"
         | 
| 105 | 
            +
                init_buffers
         | 
| 106 | 
            +
                init_shaders(vertex_path, fragment_path)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              # The object we are drawing
         | 
| 110 | 
            +
              VERTEX_DATA = [
         | 
| 111 | 
            +
                 0.0,  0.5,   0.0, 1.0,
         | 
| 112 | 
            +
                 0.5, -0.366, 0.0, 1.0,
         | 
| 113 | 
            +
                -0.5, -0.366, 0.0, 1.0,
         | 
| 114 | 
            +
              ]
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              def init_buffers
         | 
| 117 | 
            +
                # We only use one VAO, so we always keep it bound
         | 
| 118 | 
            +
                vao_buffer = " " * Fiddle::SIZEOF_UINT32_T
         | 
| 119 | 
            +
                GL.GenVertexArrays(1, vao_buffer)
         | 
| 120 | 
            +
                @vao = vao_buffer.unpack1("L")
         | 
| 121 | 
            +
                GL.BindVertexArray(@vao)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                # This is the buffer that holds the vertices
         | 
| 124 | 
            +
                position_buffer = " " * Fiddle::SIZEOF_UINT32_T
         | 
| 125 | 
            +
                GL.GenBuffers(1, position_buffer)
         | 
| 126 | 
            +
                GL.GenBuffers(1, position_buffer)
         | 
| 127 | 
            +
                @position = position_buffer.unpack1("L")
         | 
| 128 | 
            +
                GL.BindBuffer(GL::ARRAY_BUFFER, @position)
         | 
| 129 | 
            +
                GL.BufferData(GL::ARRAY_BUFFER,
         | 
| 130 | 
            +
                              Fiddle::SIZEOF_FLOAT * VERTEX_DATA.size,
         | 
| 131 | 
            +
                              VERTEX_DATA.pack("f*"),
         | 
| 132 | 
            +
                              GL::STATIC_DRAW)
         | 
| 133 | 
            +
                GL.BindBuffer(GL::ARRAY_BUFFER, 0)
         | 
| 134 | 
            +
              end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
              # Initialize the shaders and link them into a program
         | 
| 137 | 
            +
              def init_shaders(vertex_path, fragment_path)
         | 
| 138 | 
            +
                @program = nil
         | 
| 139 | 
            +
                vertex = 0
         | 
| 140 | 
            +
                fragment = 0
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                begin
         | 
| 143 | 
            +
                  source = Gio::Resources.lookup_data(vertex_path)
         | 
| 144 | 
            +
                  vertex = create_shader(GL::VERTEX_SHADER, source.to_s)
         | 
| 145 | 
            +
                  return if vertex.zero?
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  source = Gio::Resources.lookup_data(fragment_path)
         | 
| 148 | 
            +
                  fragment = create_shader(GL::FRAGMENT_SHADER, source.to_s)
         | 
| 149 | 
            +
                  return if fragment.zero?
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  @program = GL.CreateProgram
         | 
| 152 | 
            +
                  return if @program.zero?
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  GL.AttachShader(@program, vertex)
         | 
| 155 | 
            +
                  GL.AttachShader(@program, fragment)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  GL.LinkProgram(@program)
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  program_ok_buffer = " " * Fiddle::SIZEOF_UINT32_T
         | 
| 160 | 
            +
                  GL.GetProgramiv(@program, GL::LINK_STATUS, program_ok_buffer)
         | 
| 161 | 
            +
                  program_ok = (program_ok_buffer.unpack1("L") == GL::TRUE)
         | 
| 162 | 
            +
                  unless program_ok
         | 
| 163 | 
            +
                    log_length_buffer = " " * Fiddle::SIZEOF_UINT32_T
         | 
| 164 | 
            +
                    GL.GetProgramiv(@program, GL::INFO_LOG_LENGTH, log_length_buffer)
         | 
| 165 | 
            +
                    log_length = log_length_buffer.unpack1("L")
         | 
| 166 | 
            +
                    log = " " * log_length
         | 
| 167 | 
            +
                    GL.GetProgramInfoLog(@program, log_length, nil, log)
         | 
| 168 | 
            +
                    $stderr.puts("Linking failure:")
         | 
| 169 | 
            +
                    $stderr.puts(log)
         | 
| 170 | 
            +
                    GL.DeleteProgram(@program)
         | 
| 171 | 
            +
                    @program = nil
         | 
| 172 | 
            +
                    return
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  # Get the location of the "mvp" uniform
         | 
| 176 | 
            +
                  @mvp_location = GL.GetUniformLocation(@program, "mvp")
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  GL.DetachShader(@program, vertex)
         | 
| 179 | 
            +
                  GL.DetachShader(@program, fragment)
         | 
| 180 | 
            +
                ensure
         | 
| 181 | 
            +
                  GL.DeleteShader(vertex) if vertex.zero?
         | 
| 182 | 
            +
                  GL.DeleteShader(fragment) if fragment.zero?
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
              end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
              # Create and compile a shader
         | 
| 187 | 
            +
              def create_shader(type, source)
         | 
| 188 | 
            +
                shader = GL.CreateShader(type)
         | 
| 189 | 
            +
                GL.ShaderSource(shader, 1, [source].pack("p"), nil)
         | 
| 190 | 
            +
                GL.CompileShader(shader)
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                shader_ok_buffer = " " * Fiddle::SIZEOF_UINT32_T
         | 
| 193 | 
            +
                GL.GetShaderiv(shader, GL::COMPILE_STATUS, shader_ok_buffer)
         | 
| 194 | 
            +
                shader_ok = (shader_ok_buffer.unpack1("L") == GL::TRUE)
         | 
| 195 | 
            +
                unless shader_ok
         | 
| 196 | 
            +
                  log_length_buffer = " " * Fiddle::SIZEOF_UINT32_t
         | 
| 197 | 
            +
                  GL.GetShaderiv(shader, GL::INFO_LOG_LENGTH, log_length_buffer)
         | 
| 198 | 
            +
                  log_length = log_length_buffer.unpack1("L")
         | 
| 199 | 
            +
                  log = " " * log_length
         | 
| 200 | 
            +
                  GL.GetShaderInfoLog(shader, log_length, nil, buffer)
         | 
| 201 | 
            +
                  type_name = (type == GL::VERTEX_SHADER ? "vertex" : "fragment")
         | 
| 202 | 
            +
                  $stderr.puts("Compile failure in #{type_name} shader:")
         | 
| 203 | 
            +
                  $stderr.puts(log)
         | 
| 204 | 
            +
                  GL.DeleteShader(shader)
         | 
| 205 | 
            +
                  return 0
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                shader
         | 
| 209 | 
            +
              end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
              # We should tear down the state when unrealizing
         | 
| 212 | 
            +
              def unrealize(gl_area)
         | 
| 213 | 
            +
                gl_area.make_current
         | 
| 214 | 
            +
                GL.DeleteBuffers(1, [[@position].pack("L")].pack("p"))
         | 
| 215 | 
            +
                GL.DeleteProgram(@program)
         | 
| 216 | 
            +
              end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
              def compute_mvp(phi, theta, psi)
         | 
| 219 | 
            +
                x = phi * (Math::PI / 180.0)
         | 
| 220 | 
            +
                y = theta * (Math::PI / 180.0)
         | 
| 221 | 
            +
                z = psi * (Math::PI / 180.0)
         | 
| 222 | 
            +
                c1 = Math.cos(x)
         | 
| 223 | 
            +
                s1 = Math.sin(x)
         | 
| 224 | 
            +
                c2 = Math.cos(y)
         | 
| 225 | 
            +
                s2 = Math.sin(y)
         | 
| 226 | 
            +
                c3 = Math.cos(z)
         | 
| 227 | 
            +
                s3 = Math.sin(z)
         | 
| 228 | 
            +
                c3c2 = c3 * c2
         | 
| 229 | 
            +
                s3c1 = s3 * c1
         | 
| 230 | 
            +
                c3s2s1 = c3 * s2 * s1
         | 
| 231 | 
            +
                s3s1 = s3 * s1
         | 
| 232 | 
            +
                c3s2c1 = c3 * s2 * c1
         | 
| 233 | 
            +
                s3c2 = s3 * c2
         | 
| 234 | 
            +
                c3c1 = c3 * c1
         | 
| 235 | 
            +
                s3s2s1 = s3 * s2 * s1
         | 
| 236 | 
            +
                c3s1 = c3 * s1
         | 
| 237 | 
            +
                s3s2c1 = s3 * s2 * c1
         | 
| 238 | 
            +
                c2s1 = c2 * s1
         | 
| 239 | 
            +
                c2c1 = c2 * c1
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                # apply all three rotations using the three matrices:
         | 
| 242 | 
            +
                #
         | 
| 243 | 
            +
                # ⎡  c3 s3 0 ⎤ ⎡ c2  0 -s2 ⎤ ⎡ 1   0  0 ⎤
         | 
| 244 | 
            +
                # ⎢ -s3 c3 0 ⎥ ⎢  0  1   0 ⎥ ⎢ 0  c1 s1 ⎥
         | 
| 245 | 
            +
                # ⎣   0  0 1 ⎦ ⎣ s2  0  c2 ⎦ ⎣ 0 -s1 c1 ⎦
         | 
| 246 | 
            +
                mvp = [0.0] * 16
         | 
| 247 | 
            +
                mvp[0] = c3c2;  mvp[4] = s3c1 + c3s2s1; mvp[8] = s3s1 - c3s2c1; mvp[12] = 0.0;
         | 
| 248 | 
            +
                mvp[1] = -s3c2; mvp[5] = c3c1 - s3s2s1; mvp[9] = c3s1 + s3s2c1; mvp[13] = 0.0;
         | 
| 249 | 
            +
                mvp[2] = s2;    mvp[6] = -c2s1;         mvp[10] = c2c1;         mvp[14] = 0.0;
         | 
| 250 | 
            +
                mvp[3] = 0.0;   mvp[7] = 0.0;           mvp[11] = 0.0;          mvp[15] = 1.0;
         | 
| 251 | 
            +
                mvp
         | 
| 252 | 
            +
             end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
              def draw_triangle
         | 
| 255 | 
            +
                # Compute the model view projection matrix using the
         | 
| 256 | 
            +
                # rotation angles specified through the GtkRange widgets
         | 
| 257 | 
            +
                mvp = compute_mvp(@rotation_angles[X_AXIS],
         | 
| 258 | 
            +
                                  @rotation_angles[Y_AXIS],
         | 
| 259 | 
            +
                                  @rotation_angles[Z_AXIS])
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                # Use our shaders
         | 
| 262 | 
            +
                GL.UseProgram(@program)
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                # Update the "mvp" matrix we use in the shader
         | 
| 265 | 
            +
                GL.UniformMatrix4fv(@mvp_location, 1, GL::FALSE, mvp.pack("f*"))
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                # Use the vertices in our buffer
         | 
| 268 | 
            +
                GL.BindBuffer(GL::ARRAY_BUFFER, @position)
         | 
| 269 | 
            +
                GL.EnableVertexAttribArray(0)
         | 
| 270 | 
            +
                GL.VertexAttribPointer(0, 4, GL::FLOAT, GL::FALSE, 0, 0)
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                # Draw the three vertices as a triangle
         | 
| 273 | 
            +
                GL.DrawArrays(GL::TRIANGLES, 0, 3)
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                # We finished using the buffers and program
         | 
| 276 | 
            +
                GL.DisableVertexAttribArray(0)
         | 
| 277 | 
            +
                GL.BindBuffer(GL::ARRAY_BUFFER, 0)
         | 
| 278 | 
            +
                GL.UseProgram(0)
         | 
| 279 | 
            +
              end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
              def render(gl_area)
         | 
| 282 | 
            +
                # Clear the viewport
         | 
| 283 | 
            +
                GL.ClearColor(0.5, 0.5, 0.5, 1.0)
         | 
| 284 | 
            +
                GL.Clear(GL::COLOR_BUFFER_BIT)
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                # Draw our object
         | 
| 287 | 
            +
                draw_triangle
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                # Flush the contents of the pipeline
         | 
| 290 | 
            +
                GL.Flush
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                true
         | 
| 293 | 
            +
              end
         | 
| 294 | 
            +
             | 
| 295 | 
            +
              def create_axis_slider(gl_area, axis)
         | 
| 296 | 
            +
                box = Gtk::Box.new(:horizontal)
         | 
| 297 | 
            +
                case axis
         | 
| 298 | 
            +
                when X_AXIS
         | 
| 299 | 
            +
                  text = "X axis"
         | 
| 300 | 
            +
                when Y_AXIS
         | 
| 301 | 
            +
                  text = "Y axis"
         | 
| 302 | 
            +
                when Z_AXIS
         | 
| 303 | 
            +
                  text = "Z axis"
         | 
| 304 | 
            +
                end
         | 
| 305 | 
            +
                label = Gtk::Label.new(text)
         | 
| 306 | 
            +
                box.append(label)
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                adjustment = Gtk::Adjustment.new(0.0, 0.0, 360.0, 1.0, 12.0, 0.0)
         | 
| 309 | 
            +
                adjustment.signal_connect("value-changed") do
         | 
| 310 | 
            +
                  @rotation_angles[axis] = adjustment.value
         | 
| 311 | 
            +
                  # Update the contents of the GL drawing area
         | 
| 312 | 
            +
                  gl_area.queue_draw
         | 
| 313 | 
            +
                end
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                slider = Gtk::Scale.new(:horizontal, adjustment)
         | 
| 316 | 
            +
                box.append(slider)
         | 
| 317 | 
            +
                slider.hexpand = true
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                box
         | 
| 320 | 
            +
              end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
              GtkDemo.register(new)
         | 
| 323 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            <?xml version="1.0" encoding="UTF-8"?>
         | 
| 2 | 
            +
            <interface>
         | 
| 3 | 
            +
              <template class="GtkListItem">
         | 
| 4 | 
            +
                <property name="focusable">0</property>
         | 
| 5 | 
            +
                <property name="child">
         | 
| 6 | 
            +
                  <object class="GtkTreeExpander" id="expander">
         | 
| 7 | 
            +
                    <binding name="list-row">
         | 
| 8 | 
            +
                      <lookup name="item">GtkListItem</lookup>
         | 
| 9 | 
            +
                    </binding>
         | 
| 10 | 
            +
                    <property name="child">
         | 
| 11 | 
            +
                      <object class="GtkInscription">
         | 
| 12 | 
            +
                        <property name="hexpand">1</property>
         | 
| 13 | 
            +
                        <property name="nat-chars">25</property>
         | 
| 14 | 
            +
                        <property name="text-overflow">ellipsize-end</property>
         | 
| 15 | 
            +
                        <binding name="text">
         | 
| 16 | 
            +
                          <lookup name="title" type="GtkDemo">
         | 
| 17 | 
            +
                            <lookup name="item">expander</lookup>
         | 
| 18 | 
            +
                          </lookup>
         | 
| 19 | 
            +
                        </binding>
         | 
| 20 | 
            +
                      </object>
         | 
| 21 | 
            +
                    </property>
         | 
| 22 | 
            +
                  </object>
         | 
| 23 | 
            +
                </property>
         | 
| 24 | 
            +
              </template>
         | 
| 25 | 
            +
            </interface>
         | 
| @@ -0,0 +1,264 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # Copyright (C) 2024  Ruby-GNOME Project Team
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # This library is free software; you can redistribute it and/or
         | 
| 6 | 
            +
            # modify it under the terms of the GNU Lesser General Public
         | 
| 7 | 
            +
            # License as published by the Free Software Foundation; either
         | 
| 8 | 
            +
            # version 2.1 of the License, or (at your option) any later version.
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # This library is distributed in the hope that it will be useful,
         | 
| 11 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 12 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         | 
| 13 | 
            +
            # Lesser General Public License for more details.
         | 
| 14 | 
            +
            #
         | 
| 15 | 
            +
            # You should have received a copy of the GNU Lesser General Public
         | 
| 16 | 
            +
            # License along with this library; if not, write to the Free Software
         | 
| 17 | 
            +
            # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            # Original:
         | 
| 20 | 
            +
            #   https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
            # See the following for license information:
         | 
| 23 | 
            +
            #   * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/AUTHORS
         | 
| 24 | 
            +
            #   * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/COPYING
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            require "gtk4"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            gresource = File.join(__dir__, "demo.gresource")
         | 
| 29 | 
            +
            gresource_xml = "#{gresource}.xml"
         | 
| 30 | 
            +
            need_generate = false
         | 
| 31 | 
            +
            if File.exist?(gresource)
         | 
| 32 | 
            +
              need_generate = File.mtime(gresource_xml) > File.mtime(gresource)
         | 
| 33 | 
            +
            else
         | 
| 34 | 
            +
              need_generate = true
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
            if need_generate
         | 
| 37 | 
            +
              unless system("glib-compile-resources",
         | 
| 38 | 
            +
                            "--target", gresource,
         | 
| 39 | 
            +
                            "--sourcedir", __dir__,
         | 
| 40 | 
            +
                            gresource_xml)
         | 
| 41 | 
            +
                $stderr.puts("Failed to compile resource")
         | 
| 42 | 
            +
                exit(false)
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
            resource = Gio::Resource.load(gresource)
         | 
| 46 | 
            +
            Gio::Resources.register(resource)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
             | 
| 49 | 
            +
            require_relative "demo"
         | 
| 50 | 
            +
            Dir.glob("#{__dir__}/*.rb") do |rb|
         | 
| 51 | 
            +
              base_name = File.basename(rb, ".rb")
         | 
| 52 | 
            +
              next if base_name == "main"
         | 
| 53 | 
            +
              require_relative base_name
         | 
| 54 | 
            +
            end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
             | 
| 57 | 
            +
            def run_demo(demo, window)
         | 
| 58 | 
            +
              demo_window = demo.run(window)
         | 
| 59 | 
            +
              demo_window.transient_for = window
         | 
| 60 | 
            +
              demo_window.modal = true
         | 
| 61 | 
            +
              demo_window
         | 
| 62 | 
            +
            end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            def create_demo_model(window)
         | 
| 65 | 
            +
              store = Gio::ListStore.new(GtkDemo)
         | 
| 66 | 
            +
              GtkDemo.all.each do |demo|
         | 
| 67 | 
            +
                store.append(demo)
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
              store
         | 
| 70 | 
            +
            end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            def load_file(demo, notebook, info_view)
         | 
| 73 | 
            +
              notebook.n_pages.downto(1) do |i|
         | 
| 74 | 
            +
                notebook.remove_page(i)
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              info_buffer = Gtk::TextBuffer.new
         | 
| 78 | 
            +
              info_buffer.create_tag("title",
         | 
| 79 | 
            +
                                     size: 18 * Pango::SCALE,
         | 
| 80 | 
            +
                                     pixels_below_lines: 10)
         | 
| 81 | 
            +
              info_buffer.begin_irreversible_action do
         | 
| 82 | 
            +
                info_buffer_iter = info_buffer.get_iter_at(offset: 0)
         | 
| 83 | 
            +
                info_buffer.insert(info_buffer_iter, demo.title, tags: ["title"])
         | 
| 84 | 
            +
                info_buffer.insert(info_buffer_iter, "\n")
         | 
| 85 | 
            +
                info_buffer.insert(info_buffer_iter, demo.description)
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
              info_view.buffer = info_buffer
         | 
| 88 | 
            +
            end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            def select_item(selection, window, notebook, info_view)
         | 
| 91 | 
            +
              row = selection.selected_item
         | 
| 92 | 
            +
              notebook.sensitive = !!row
         | 
| 93 | 
            +
              if row
         | 
| 94 | 
            +
                demo = row.item
         | 
| 95 | 
            +
                load_file(demo, notebook, info_view)
         | 
| 96 | 
            +
                run_action = window.lookup_action("run")
         | 
| 97 | 
            +
                run_action.enabled = true
         | 
| 98 | 
            +
                window.title = demo.title
         | 
| 99 | 
            +
              else
         | 
| 100 | 
            +
                window.title = "No match"
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
             | 
| 105 | 
            +
            app = Gtk::Application.new("com.github.ruby-gnome.gtk4.Demo",
         | 
| 106 | 
            +
                                       [:non_unique, :handles_command_line])
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            about_action = Gio::SimpleAction.new("about")
         | 
| 109 | 
            +
            about_action.signal_connect("activate") do
         | 
| 110 | 
            +
              dialog = Gtk::AboutDialog.new
         | 
| 111 | 
            +
              dialog.modal = true
         | 
| 112 | 
            +
              dialog.transient_for = app.active_window
         | 
| 113 | 
            +
              dialog.destroy_with_parent = true
         | 
| 114 | 
            +
              dialog.program_name = "Ruby/GTK4 Demo"
         | 
| 115 | 
            +
              dialog.version = GLib::BINDING_VERSION.join(".")
         | 
| 116 | 
            +
              dialog.copyright = "(C) 2024-#{Time.now.year} Ruby-GNOME Project Team"
         | 
| 117 | 
            +
              dialog.license_type = Gtk::License::LGPL_2_1
         | 
| 118 | 
            +
              dialog.website = "https://ruby-gnome.github.io/"
         | 
| 119 | 
            +
              dialog.comments = "Program to demonstrate Ruby/GTK4 widgets"
         | 
| 120 | 
            +
              dialog.authors = ["Ruby-GNOME Project Team"]
         | 
| 121 | 
            +
              dialog.logo_icon_name = "org.gtk.Demo4"
         | 
| 122 | 
            +
              dialog.title = "About Ruby/GTK4 Demo"
         | 
| 123 | 
            +
              dialog.system_information = <<-INFO.chomp
         | 
| 124 | 
            +
            OS\t#{GLib.get_os_info(:name)} #{GLib.get_os_info(:version)}
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            System Libraries
         | 
| 127 | 
            +
            \tGLib\t#{GLib::Version::STRING}
         | 
| 128 | 
            +
            \tPango\t#{Pango::Version::STRING}
         | 
| 129 | 
            +
            \tGTK\t#{Gtk::Version::STRING}
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            A link can appear here: <https://ruby-gnome.github.io/>
         | 
| 132 | 
            +
              INFO
         | 
| 133 | 
            +
              dialog.present
         | 
| 134 | 
            +
            end
         | 
| 135 | 
            +
            app.add_action(about_action)
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            quit_action = Gio::SimpleAction.new("quit")
         | 
| 138 | 
            +
            quit_action.signal_connect("activate") do
         | 
| 139 | 
            +
              app.windows.each do |window|
         | 
| 140 | 
            +
                window.destroy
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         | 
| 143 | 
            +
            app.add_action(quit_action)
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            inspector_action = Gio::SimpleAction.new("inspector")
         | 
| 146 | 
            +
            inspector_action.signal_connect("activate") do
         | 
| 147 | 
            +
              Gtk::Window.interactive_debugging = true
         | 
| 148 | 
            +
            end
         | 
| 149 | 
            +
            app.add_action(inspector_action)
         | 
| 150 | 
            +
            app.set_accels_for_action("app.about", ["F1"])
         | 
| 151 | 
            +
            app.set_accels_for_action("app.quit", ["<Control>q"])
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            app.add_main_option("version",
         | 
| 154 | 
            +
                                0,
         | 
| 155 | 
            +
                                GLib::OptionFlags::NONE,
         | 
| 156 | 
            +
                                GLib::OptionArg::NONE,
         | 
| 157 | 
            +
                                "Show program version")
         | 
| 158 | 
            +
            app.add_main_option("run",
         | 
| 159 | 
            +
                                0,
         | 
| 160 | 
            +
                                GLib::OptionFlags::NONE,
         | 
| 161 | 
            +
                                GLib::OptionArg::STRING,
         | 
| 162 | 
            +
                                "Run an example",
         | 
| 163 | 
            +
                                "EXAMPLE")
         | 
| 164 | 
            +
            app.add_main_option("list",
         | 
| 165 | 
            +
                                0,
         | 
| 166 | 
            +
                                GLib::OptionFlags::NONE,
         | 
| 167 | 
            +
                                GLib::OptionArg::NONE,
         | 
| 168 | 
            +
                                "List examples")
         | 
| 169 | 
            +
            app.add_main_option("auto-quit",
         | 
| 170 | 
            +
                                0,
         | 
| 171 | 
            +
                                GLib::OptionFlags::NONE,
         | 
| 172 | 
            +
                                GLib::OptionArg::NONE,
         | 
| 173 | 
            +
                                "Quit after a delay")
         | 
| 174 | 
            +
            run = nil
         | 
| 175 | 
            +
            list = false
         | 
| 176 | 
            +
            auto_quit = false
         | 
| 177 | 
            +
            app.signal_connect("command-line") do |_app, command_line|
         | 
| 178 | 
            +
              run = command_line.options_dict.lookup("run", "s")
         | 
| 179 | 
            +
              list = command_line.options_dict.lookup("list", "b")
         | 
| 180 | 
            +
              auto_quit = command_line.options_dict.lookup("auto-quit", "b")
         | 
| 181 | 
            +
              app.activate
         | 
| 182 | 
            +
              0
         | 
| 183 | 
            +
            end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
            app.signal_connect("handle-local-options") do |_app, options|
         | 
| 186 | 
            +
              if options.lookup("version", "b")
         | 
| 187 | 
            +
                puts("ruby-gtk4-demo: #{GLib::BINDING_VERSION.join(".")}")
         | 
| 188 | 
            +
                0
         | 
| 189 | 
            +
              else
         | 
| 190 | 
            +
                -1
         | 
| 191 | 
            +
              end
         | 
| 192 | 
            +
            end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
            app.signal_connect("activate") do |_app|
         | 
| 195 | 
            +
              builder = Gtk::Builder.new(resource: "/ui/main.ui")
         | 
| 196 | 
            +
              window = builder["window"]
         | 
| 197 | 
            +
              app.add_window(window)
         | 
| 198 | 
            +
             | 
| 199 | 
            +
              run_action = Gio::SimpleAction.new("run")
         | 
| 200 | 
            +
              run_action.signal_connect("activate") do
         | 
| 201 | 
            +
                row = selection.selected_item
         | 
| 202 | 
            +
                demo = row.item
         | 
| 203 | 
            +
                run_demo(demo, window)
         | 
| 204 | 
            +
              end
         | 
| 205 | 
            +
              window.add_action(run_action)
         | 
| 206 | 
            +
             | 
| 207 | 
            +
              notebook = builder["notebook"]
         | 
| 208 | 
            +
             | 
| 209 | 
            +
              info_view = builder["info-textview"]
         | 
| 210 | 
            +
              source_view = builder["source-textview"]
         | 
| 211 | 
            +
              list_view = builder["listview"]
         | 
| 212 | 
            +
              list_view.signal_connect("activate") do |_, position|
         | 
| 213 | 
            +
                model = list_view.model
         | 
| 214 | 
            +
                row = model.get_item(position)
         | 
| 215 | 
            +
                run_demo(row.item, window)
         | 
| 216 | 
            +
              end
         | 
| 217 | 
            +
              search_bar = builder["searchbar"]
         | 
| 218 | 
            +
              search_bar.signal_connect("notify::search-mode-enabled") do
         | 
| 219 | 
            +
                unless search_bar.search_mode?
         | 
| 220 | 
            +
                  bar.child.text = ""
         | 
| 221 | 
            +
                end
         | 
| 222 | 
            +
              end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
              list_model = create_demo_model(window)
         | 
| 225 | 
            +
              tree_model = Gtk::TreeListModel.new(list_model, false, true) do |item|
         | 
| 226 | 
            +
                item.children_model
         | 
| 227 | 
            +
              end
         | 
| 228 | 
            +
              filter_model = Gtk::FilterListModel.new(tree_model, nil)
         | 
| 229 | 
            +
              filter = Gtk::CustomFilter.new do
         | 
| 230 | 
            +
                # TODO
         | 
| 231 | 
            +
                true
         | 
| 232 | 
            +
              end
         | 
| 233 | 
            +
              filter_model.filter = filter
         | 
| 234 | 
            +
             | 
| 235 | 
            +
              search_entry = builder["search-entry"]
         | 
| 236 | 
            +
             | 
| 237 | 
            +
              selection = Gtk::SingleSelection.new(filter_model)
         | 
| 238 | 
            +
              selection.signal_connect("notify::selected-item") do
         | 
| 239 | 
            +
                select_item(selection, window, notebook, info_view)
         | 
| 240 | 
            +
              end
         | 
| 241 | 
            +
              list_view.model = selection
         | 
| 242 | 
            +
              select_item(selection, window, notebook, info_view)
         | 
| 243 | 
            +
             | 
| 244 | 
            +
              target_demo = nil
         | 
| 245 | 
            +
              if run
         | 
| 246 | 
            +
                GtkDemo.each do |demo|
         | 
| 247 | 
            +
                  if demo.name == run
         | 
| 248 | 
            +
                    target_demo = demo
         | 
| 249 | 
            +
                    break
         | 
| 250 | 
            +
                  end
         | 
| 251 | 
            +
                end
         | 
| 252 | 
            +
              end
         | 
| 253 | 
            +
              if target_demo
         | 
| 254 | 
            +
                demo_window = run_demo(target_demo, window)
         | 
| 255 | 
            +
                # TODO: This doesn't work
         | 
| 256 | 
            +
                demo_window.signal_connect("destroy") do
         | 
| 257 | 
            +
                  app.quit
         | 
| 258 | 
            +
                end
         | 
| 259 | 
            +
              else
         | 
| 260 | 
            +
                window.present
         | 
| 261 | 
            +
              end
         | 
| 262 | 
            +
            end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            app.run([$0] + ARGV)
         |