Libove Blog

Personal Blog about anything - mostly programming, cooking and random thoughts

Inheritance Pattern in Go

Go isn't an object oriented language and doesn't have inheritance. Sometimes I still want to model something in a similar way as I would in an object oriented languages. My blog software has different types of "entries", e.g. articles, images, recipes ... This is modeled by an interface Entry which has to be implemented by each type of post.

// truncated for simplicity
type Entry interface {
	ID() string
	Content() EntryContent
	PublishedAt() *time.Time
	Title() string
	SetID(id string)
	SetPublishedAt(publishedAt *time.Time)
}

One problem with this is, that I have to implement these functions for each type of entry. Many of these implementation will be identical leading to a high degree of code duplication. To avoid this I've created an EntryBase, which implements sensible defaults.

type EntryBase struct {
	id          string
	publishedAt *time.Time
}

func (e *EntryBase) ID() string {
	return e.id
}

func (e *EntryBase) PublishedAt() *time.Time {
	return e.publishedAt
}

func (e *EntryBase) SetID(id string) {
	e.id = id
}

func (e *EntryBase) SetPublishedAt(publishedAt *time.Time) {
	e.publishedAt = publishedAt
}

With this "base class" the specific implementations only have to implement functions which differ between entry types.

type Article struct {
	EntryBase
	meta ArticleMetaData
}

type ArticleMetaData struct {
	Title   string
	Content string
}


func (e *Article) Title() string {
	return e.meta.Title
}

func (e *Article) Content() model.EntryContent {
    // render content from markdown	
}

Pilz Kichererbsen Risotto

Servings: 3 Portionen , Prep Time:

Ingredients

  • 250g Risotto Reis
  • 1 Glas Kichererbsen
  • 400g Champignons
  • 2 kleine Zwiebel
  • 2 Knoblauchzehen
  • 25g getrocknete Pilze
  • 300ml Gemüsebrühe
  • ½ Dose Kokosmilch
  • 1 EL Sojasoße
  • 1 EL Balsamico Essig
  • Thymian und Pfeffer

Instructions

  • Getrocknete Pilze in 300 ml Wasser einweichen (~30 min). Aus Wasser nehmen und grob hacken. Wasser aufbewahren!
  • Pilze in Würfel schneiden und in Olivenöl goldbraun anbraten. Anschließen aus der Pfanne nehmen und beiseite Stellen.
  • Zwiebel und Knoblauch fein gehackt bei mittlerer Hitze leicht braun anbraten.
  • Reis und getrocknete Pilze hinzufügen und 1-2 Minuten mit anbraten. Mit Pilzewasser ablöschen.
  • Pilze und Kichererbsen hinzufügen.
  • Mischung aus Gemüsebrühe, Sojasoße, Essig und Thymian portionsweise hinzugeben.
  • Solange kochen und Flüssigkeit hinzufügen bis Reis fast gar ist.
  • Kokosmilch und Pfeffer hinzufügen. Für etwa 5 Minuten kochen bis gewünschte Konsistenz erreicht ist.

Soap Bubble

Soap Bubble

Sheep

Sheep

Bhaal Spawn

Bhaal Spawn

Rooster

Rooster

Goose

Goose

Oldtimer

Oldtimer

Little Christmas Tree

Little Christmas Tree

Rust GTK: Creating a Menu Bar Programmatically with gtk-rs

This guide is written for gtk-rs and GTK 4.6

To create a menu bar in GTK4 we need a MenuModel, from which the bar is created. The MenuModel can be derived from a Menu object. Menu objects can have sub menues and and MenuItems as children, buidling a tree structure which represents the entire menu of the application.

Let's start at the end of our menu definition, by defining the root of the menu tree. The root should only have sub menues as children.

let menu = Menu::new();
menu.insert_submenu(0, Some("File"), &file_menu);
menu.insert_submenu(1, Some("Edit"), &edit_menu);
let menu_model: MenuModel = menu.into();

This is our root menu and derived model. The first parameter determines the ordering of the menu entries. The second parameter defines the name displayed in the menu bar and the last parameter is a reference to another Menu object. In this example menu has two children "File" and "Edit" which are further menus we have to define.

The resulting menu bar will look similar to this:

The menus are Menu objects. To add selectable entries, MenuItems are inserted. The first parameter defines the ordering within the menu. I tend to leave gaps in my ordering to allow insertions later, without changing all order numbers. The MenuItem takes two (optional) strings as parameters. The first string is the string shown in the menu. and the latter references an action. This action is executed if the menu item is selected.

let file_menu = Menu::new();
file_menu.insert_item(0, &MenuItem::new(Some("New Dungeon"), Some("file.new")));
file_menu.insert_item(5, &MenuItem::new(Some("Open ..."), Some("file.open")));
file_menu.insert_item(10, &MenuItem::new(Some("Save ..."), Some("file.save")));

The resulting menu looks similar to this:

(Note that the shown shortcuts are automatically added when an accelarator is defined for the action).

To define the effect of selecting a menu item, actions have to be added to the window. I insert actions into action groups to organize them by their main menu. This code defines two actions in the "file" action group; "file.new" and "file.open". The name of the action group is defined when the group is added to the window.

let file_actions = SimpleActionGroup::new();

let action_file_new = ActionEntry::builder("new")
    .activate(
        move |_group: &SimpleActionGroup, _, _| {
            // Define behavior here
        },
    )
    .build();

let action_file_open = ActionEntry::builder("open")
    .activate(
        move |_group: &SimpleActionGroup, _, _| {
            // Define behavior here
        },
    )
    .build();

file_actions.add_action_entries([
    action_file_new,
    action_file_open,
]);

window.insert_action_group("file", Some(&file_actions));

For further organization of menu entries, sub menues can be used. This uses the same method as used on the root menu.

edit_menu.insert_submenu(20, Some("Change Mode"), &mode_menu);

This results in a sub menu similar to this

After defining the menu and deriving the MenuModel a PopoverMenuBar can be created and added to the application window. This can be achieved by wrapping the content of the window in another box and adding the menu bar to this box.

let menubar = PopoverMenuBar::from_model(Some(&menu_model));

let window_box = gtk::Box::builder()
    .orientation(gtk::Orientation::Vertical)
    .build();
window_box.append(&menubar);
window_box.append(&main_box);

// Create a window
let window = ApplicationWindow::builder()
    .application(app)
    .child(&window_box)
    ...
    .build():

The full code used in these examples can be found in the DungeonPlanner Source Code