Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/widget-template-manual/box.blp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Gtk 4.0;

template $My_Box: Gtk.Box {
halign: center;
valign: center;

Button my-button {
label: _("Hi! Click me and look at the terminal!");
clicked => $my_button_clicked();
}
}
Binary file added examples/widget-template-manual/box.gresource
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/widget-template-manual/box.gresource.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<gresources>
<gresource prefix="/example">
<file>box.ui</file>
</gresource>
</gresources>
19 changes: 19 additions & 0 deletions examples/widget-template-manual/box.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="My_Box" parent="GtkBox">
<property name="halign">3</property>
<property name="valign">3</property>
<child>
<object class="GtkButton" id="my-button">
<property name="label" translatable="yes">Hi! Click me and look at the terminal!</property>
<signal name="clicked" handler="my_button_clicked"/>
</object>
</child>
</template>
</interface>
147 changes: 147 additions & 0 deletions examples/widget-template-manual/window.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package template

@require import "base:runtime"
@require import "core:fmt"
@require import adw "../../adwaita"
@require import "../../glib"
@require import gobj "../../glib/gobject"
@require import "../../glib/gio"
@require import "../../gtk"

// Return the GType of our widget, and register it if it isn't already.
my_box_get_type :: proc "c" () -> (g_type: gobj.Type) {
@static static_g_type: gobj.Type

// If the type is already registered, we just return the type id.
if static_g_type != 0 { return static_g_type }

info := gobj.TypeInfo{
class_size = size_of(My_Box_Class),
class_init = class_init,

instance_size = size_of(My_Box),
instance_init = instance_init,
}
// We the type id so it sticks.
static_g_type = gobj.type_register_static(gtk.box_get_type(), "My_Box", &info, .NONE)
g_type = static_g_type
return

// Constructors
// These could be defined outside of this proc too, but it's nicer to have them contained here.

// This handles everything that's related to the overall widget class.
class_init :: proc "c" (class: glib.pointer, data: glib.pointer) {
gtk.widget_class_set_template_from_resource(cast(^gtk.WidgetClass)class, "/example/box.ui")

// We can set the click action here. See the blueprint `box.blp` for more information.
// Alternatively, we could set it in the `instance_init` proc as well.
widget_class := cast(^gtk.WidgetClass)class
offset := cast(glib.ssize)offset_of(My_Box, button)

gtk.widget_class_bind_template_child_full(
widget_class,
name = "my-button",
internal_child = false, // I have no idea what this does. Both work (shrug).
struct_offset = offset, // This will automatically bind our button into our My_Box struct. How cool!
)

// We set up our button's clicked action defined in the blueprint.
// Refer to the `instance_init` proc if you want to set it without blueprint.
clicked_callback := cast(gobj.Callback)my_button_clicked
gtk.widget_class_bind_template_callback_full(widget_class, "my_button_clicked", clicked_callback)

// Rest of your class setup and virtual methods etc. go here as well.
}

// This handles the individual instances of the widget.
instance_init :: proc "c" (instance: ^gobj.TypeInstance, class_data: glib.pointer) {
gtk.widget_init_template(cast(^gtk.Widget)instance)

/*
// We could also set up our child button here instead, if we wanted to.

button := cast(^gtk.Button)(
gtk.widget_get_template_child(cast(^gtk.Widget)instance, my_button_get_type(), "my-button")
)
my_box := cast(^My_Box)instance
my_box.button = button

// It is, however, important to note that we would need to unset the `my_button_clicked` signal
// in the blueprint. It's either-or. Both cannot be true at once, GTK will yell at us otherwise.

clicked_callback := cast(gobj.Callback)my_button_clicked
gobj.signal_connect(button, "clicked", clicked_callback, nil)
*/

// Rest of your instance overrides and setup go here as well.
}

// This will get called when we click the button.
my_button_clicked :: proc "c"(button: ^gtk.Button, data: glib.pointer) {
parent := gtk.widget_get_parent(cast(^gtk.Widget)button)
my_box := cast(^My_Box)parent

my_box.button_clicked += 1

context = runtime.default_context()
fmt.printfln("Button clicked %d times!", my_box.button_clicked)

// GTK copies our string, so we are responsible for freeing the memory.
button_clicked_cstring := fmt.caprintf("Clicked %d times!", my_box.button_clicked)
defer delete(button_clicked_cstring)

gtk.button_set_label(button, button_clicked_cstring)
}
}

// Needed by GTK internally.
My_Box_Class :: struct {
parent_class: gtk.BoxClass,
}

// The box instance.
// Note: `parent` field has to be the first.
My_Box :: struct {
parent_instance: gtk.Box,
button_clicked: int,
button: ^gtk.Button, // this field is set by us in the `class_init` proc.
}

main :: proc() {
context = glib.create_context()

// We must initialise GTK, otherwise our parent class, `gtk.Box` is not valid.
gtk.init()

// We load the resource file that was compiled by `glib-compile-resources`
// The `.ui` file was created by blueprint-compiler.
// use the `#load` directive for release builds, to bundle it into the app.
resource := gio.resource_load("box.gresource", nil)
gio.resources_register(resource)

app := adw.application_new("some.example", .NONE)
defer gobj.object_unref(app)

gobj.signal_connect(app, "activate", cast(gobj.Callback)show_window, nil)
status := gio.application_run(cast(^gio.Application)app, 0, nil)

if status != 0 {
fmt.eprintfln("%#v", "oh no!")
}
}

// We set up and show the main window here. This could be split up to multiple procs, obviously.
show_window :: proc "c" (app: ^adw.Application) {
// GTK is very cast heavy, unfortunately.
// I like to prefix generic variables with `_` as a note to myself.
_window := adw.application_window_new(cast(^gtk.Application)(app))
window := cast(^adw.ApplicationWindow)_window

// We create our custom box here, initialised as per its init function.
my_box_g_type := my_box_get_type()
_my_box := gobj.object_new(my_box_g_type, nil)

adw.application_window_set_content(window, cast(^gtk.Widget)_my_box)
gtk.window_present(cast(^gtk.Window)_window)
}
11 changes: 11 additions & 0 deletions examples/widget-template-with-helper/box.blp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Gtk 4.0;

template $My_Box: Gtk.Box {
halign: center;
valign: center;

Button my-button {
label: bind template.button-label;
clicked => $my_button_clicked();
}
}
Binary file added examples/widget-template-with-helper/box.gresource
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/widget-template-with-helper/box.gresource.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<gresources>
<gresource prefix="/example">
<file>box.ui</file>
</gresource>
</gresources>
19 changes: 19 additions & 0 deletions examples/widget-template-with-helper/box.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="My_Box" parent="GtkBox">
<property name="halign">3</property>
<property name="valign">3</property>
<child>
<object class="GtkButton" id="my-button">
<property name="label" bind-source="My_Box" bind-property="button-label" bind-flags="sync-create"/>
<signal name="clicked" handler="my_button_clicked"/>
</object>
</child>
</template>
</interface>
144 changes: 144 additions & 0 deletions examples/widget-template-with-helper/window.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package template

@require import "base:runtime"
@require import "core:fmt"
@require import adw "../../adwaita"
@require import "../../glib"
@require import gobj "../../glib/gobject"
@require import "../../glib/gio"
@require import "../../gtk"

// The box instance.
// Note: `parent` field has to be the first.
My_Box :: struct {
parent_instance: gtk.Box,
button_clicked: int,
button: ^gtk.Button, // This field is populated automatically.
button_label: cstring `gproperty:"1,button-label"`,
}

main :: proc() {
context = glib.create_context()

// We must initialise GTK, otherwise our parent class, `gtk.Box` is not valid.
gtk.init()

// We load the resource file that was compiled by `glib-compile-resources`
// The `.ui` file was created by blueprint-compiler.
// use the `#load` directive for release builds, to bundle it into the app.
resource := gio.resource_load("box.gresource", nil)
gio.resources_register(resource)

app := adw.application_new("some.example", .NONE)
defer gobj.object_unref(app)

gobj.signal_connect(app, "activate", cast(gobj.Callback)show_window, nil)
status := gio.application_run(cast(^gio.Application)app, 0, nil)

if status != 0 {
fmt.eprintfln("%#v", "oh no!")
}
}

// We set up and show the main window here. This could be split up to multiple procs, obviously.
show_window :: proc "c" (app: ^adw.Application) {
// GTK is very cast heavy, unfortunately.
// I like to prefix generic variables with `_` as a note to myself.
_window := adw.application_window_new(cast(^gtk.Application)(app))
window := cast(^adw.ApplicationWindow)_window

// Context is needed for reflection used by the setup.
context = runtime.default_context()

// If we only care about registering the child, and don't want to bind it to our struct,
// we can leave `field_name` empty.
template_children := []gtk.Template_Child{
{
id = "my-button",
field_name = "button",
},
}
// See gtk.Template_Data for more information about what this is.
template_data := gtk.Template_Data{
resource_path = "/example/box.ui",
children = template_children,
type = My_Box,
}
gtk.register_type_with_template(
My_Box,
gtk.BoxClass,
gtk.box_get_type(),
&template_data,

// We want to set a `clicked` signal to our button, so we must supply our own init proc.
class_init_proc = my_box_class_init,
instance_init_proc = my_box_instance_init,
)

// We create our custom box here, initialised as per its init function.
my_box_g_type := gtk.custom_type_get_type(My_Box)
_my_box := gobj.object_new(my_box_g_type, nil)

adw.application_window_set_content(window, cast(^gtk.Widget)_my_box)
gtk.window_present(cast(^gtk.Window)_window)
}

// Our custom class init proc, since we want to set the `clicked` action on our button.
my_box_class_init :: proc "c" (class: glib.pointer, data: glib.pointer) {
// We register the getter and setter procs for our box's custom gproperties.
object_class := cast(^gobj.ObjectClass)class
object_class.get_property = my_box_property_get
object_class.set_property = my_box_property_set

// We call the default init proc, so that we don't have to do the child binding and template setup ourselves.
// This takes care of common use cases like registering children and annotated gproperties.
gtk.class_init_default_template(class, data)

// We register our proc as a callback to the signal defined in the blueprint.
// Alternatively, we could register the signal in the `instance_init` proc too.
widget_class := cast(^gtk.WidgetClass)class
clicked_callback := cast(gobj.Callback)my_button_clicked
gtk.widget_class_bind_template_callback_full(widget_class, "my_button_clicked", clicked_callback)
}

// This is called by GTK whenever it wants to know the value of any of the registered properties.
my_box_property_get :: proc "c"(object: ^gobj.Object, property_id: glib.uint_, value: ^gobj.Value, pspec: ^gobj.ParamSpec) {
my_box := cast(^My_Box)object
switch property_id {
case 1: // We set the id of our property to 1.
gobj.value_set_string(value, my_box.button_label)
}
}

my_box_instance_init :: proc "c" (instance: ^gobj.TypeInstance, class_data: glib.pointer) {
// We call the default template to do the setup.
gtk.instance_init_default_template(instance, class_data)

// We set a default value for our label, since the binding sets none by itself.
my_box := cast(^My_Box)instance
gtk.button_set_label(my_box.button, "Click me!")
}

// We can leave this empty for now because we don't plan on setting it through GTK.
my_box_property_set :: proc "c"(object: ^gobj.Object, property_id: glib.uint_, value: ^gobj.Value, pspec: ^gobj.ParamSpec) {}

// This will get called when we click the button.
my_button_clicked :: proc "c"(button: ^gtk.Button, data: glib.pointer) {
parent := gtk.widget_get_parent(cast(^gtk.Widget)button)
my_box := cast(^My_Box)parent

my_box.button_clicked += 1

context = runtime.default_context()
fmt.printfln("Button clicked %d times!", my_box.button_clicked)

button_clicked_cstring := fmt.caprintf("Clicked %d times!", my_box.button_clicked)

// We set the property of our box, and tell GTK that it changed, which will automatically update everything that depends on it.
delete(my_box.button_label)
my_box.button_label = button_clicked_cstring
gobj.object_notify(cast(^gobj.Object)my_box, "button-label")

// In this case, we could have set the button label directly too, via:
// gtk.button_set_label(button, button_clicked_cstring)
}
Loading