can-lehmann / owlkettle

A declarative user interface framework based on GTK 4
https://can-lehmann.github.io/owlkettle/README
MIT License
367 stars 14 forks source link

How do I move focus from one widget to another? #48

Closed silenc3r closed 1 year ago

silenc3r commented 1 year ago

Hi! In the example TODO app after pressing the + button I want to move focus back to the entry widget. How can I do that? I guess I have to use gtk_widget_grab_focus, but how do I get reference to entry widget?

can-lehmann commented 1 year ago

There currently is no simple built-in method for getting a reference to a GTK Widget anywhere in the widget tree. You can however use hooks to get around this limitation. One idea for solving your specific use case might be building something similar to React's refs. Here is a basic implementation of a Ref widget that extracts the underlying GTK widget of its child.

# License: MIT
# Copyright (c) 2023 Can Joshua Lehmann

import owlkettle/[gtk, widgetutils]

type Cell = ref object
  widget*: GtkWidget

proc focus*(cell: Cell) =
  gtk_widget_grab_focus(cell.widget)

renderable Ref:
  cell: Cell
  child: Widget

  hooks:
    beforeBuild:
      state.internalWidget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)

  hooks child:
    (build, update):
      proc addChild(box, child: GtkWidget) {.cdecl.} =
        gtk_widget_set_hexpand(child, 1)
        gtk_box_append(box, child)

      state.updateChild(state.child, widget.valChild, addChild, gtk_box_remove)

      if not state.cell.isNil:
        if state.child.isNil:
          state.cell.widget = nil
        else:
          state.cell.widget = state.child.unwrapInternalWidget()

  adder add:
    if widget.hasChild:
      raise newException(ValueError, "Unable to add multiple children to a Ref.")
    widget.hasChild = true
    widget.valChild = child

You can then use it like this in the TODO app:

Box(orient = OrientX, spacing = 6) {.expand: false.}:
  let entry = Cell()
  Ref(cell = entry):
    Entry:
      text = app.newItem
      proc changed(newItem: string) =
        app.newItem = newItem
  Button {.expand: false.}:
    icon = "list-add-symbolic"
    style = [ButtonSuggested]
    proc clicked() =
      app.todos.add(TodoItem(text: app.newItem))
      app.newItem = ""
      entry.focus()