AllenDang / giu

Cross platform rapid GUI framework for golang based on Dear ImGui.
MIT License
2.16k stars 128 forks source link

[bug] SplitLayout causes the text input to lose focus on adding/removing rows to a table widget #717

Open WinPooh32 opened 8 months ago

WinPooh32 commented 8 months ago

What happend?

I'm trying to filter table rows by a user-typed text. The text input widget loses focus when table rows are changed. It happens only when the table is a sub-widget of the SplitLayout. (v0.7.0)

At the master version it loses focus only when table is removed from the layout (table without rows causes panic on any version).

Code example

main.go ```golang package main import ( "fmt" "strings" g "github.com/AllenDang/giu" ) var ( names []string filter string ) func buildTable() g.Widget { rows := make([]*g.TableRowWidget, 0, len(names)) for i, name := range names { if filter != "" && !strings.Contains(name, filter) { continue } row := g.TableRow( g.Label(fmt.Sprintf("%d", i)), g.Label(name), ) rows = append(rows, row) } if len(rows) == 0 { return nil } return g.Table().FastMode(false).Rows(rows...) } var sashPos float32 = 100 func loop() { g.SingleWindow().Layout( g.SplitLayout( g.DirectionHorizontal, &sashPos, g.Label("Note: FastTable only works if all rows have same height"), g.Layout{ g.InputText(&filter).Hint("Filter..."), buildTable(), }, ), ) } func main() { names = make([]string, 10000) for i := range names { names[i] = fmt.Sprintf("Huge list name demo 范例 %d", i) } wnd := g.NewMasterWindow("Huge list demo", 800, 600, 0) wnd.Run(loop) } ```

To Reproduce

  1. Run my demo
  2. Type qwerty to the text input form.

Version

(latest)

OS

Ubuntu 20.04.6

gucio321 commented 8 months ago

@WinPooh32 try to set ID of your input text manually

WinPooh32 commented 8 months ago

@WinPooh32 try to set ID of your input text manually

g.InputText(&filter).Hint("Filter...").Label("##custom_id")

Changes nothing.

AllenDang commented 8 months ago

@WinPooh32 use ID(...) instead of Label(...)

WinPooh32 commented 8 months ago

@WinPooh32 use ID(...) instead of Label(...)

There is no ID method at the InputText. I set ID() for table and it helps. But it panics if there are no rows at the table.

My code:

    if len(rows) == 0 {
        return g.Table().Rows(g.TableRow(g.Dummy(0, 0))).ID("table1234")
    }

    return g.Table()..Rows(rows...).ID("table1234")
WinPooh32 commented 8 months ago

But when rows are filled by buttons, focus is lost on every table change. IDs do not help:

main.go ```golang package main import ( "fmt" "strings" g "github.com/AllenDang/giu" "github.com/AllenDang/imgui-go" ) var ( names []string filter string ) func buildTable() g.Widget { rows := make([]*g.TableRowWidget, 0, len(names)) for i, name := range names { if filter != "" && !strings.Contains(name, filter) { continue } row := g.TableRow( g.Button(fmt.Sprintf("%d %s", i, name)), ) rows = append(rows, row) } if len(rows) == 0 { return g.Table().Rows(g.TableRow(g.Dummy(0, 0))).ID("table1234") } return g.Table().Rows(rows...).ID("table1234") } var sashPos float32 = 100 func loop() { g.SingleWindow().Layout( g.SplitLayout( g.DirectionHorizontal, &sashPos, g.Label("Note: FastTable only works if all rows have same height"), g.Layout{ g.InputText(&filter).Hint("Filter..."), buildTable(), }, ).ID("123456789"), ) } func main() { names = make([]string, 10000) for i := range names { names[i] = fmt.Sprintf("Huge list name demo 范例 %d", i) } wnd := g.NewMasterWindow("Huge list demo", 800, 600, 0) imgui.StyleColorsLight() wnd.Run(loop) } ```
AllenDang commented 8 months ago

because your ID is the same for all rows and buttons, make them unique by add index into ID.

WinPooh32 commented 8 months ago

because your ID is the same for all rows and buttons, make them unique by add index into ID.

I have set ID only for buttons (rows have not method ID(...)), but nothing changed:

g.Button("").ID(fmt.Sprintf("%d", i)),

full example:

main.go ```golang package main import ( "fmt" "strings" g "github.com/AllenDang/giu" "github.com/AllenDang/imgui-go" ) var ( names []string filter string ) func buildTable() g.Widget { rows := make([]*g.TableRowWidget, 0, len(names)) for i, name := range names { if filter != "" && !strings.Contains(name, filter) { continue } row := g.TableRow( g.Button("").ID(fmt.Sprintf("%d", i)), ) rows = append(rows, row) } if len(rows) == 0 { return g.Table().Rows(g.TableRow(g.Button("").ID(fmt.Sprintf("%d", -1)))).ID("table1") } return g.Table().Rows(rows...).ID("table1") } var sashPos float32 = 100 func loop() { g.SingleWindow().Layout( g.SplitLayout( g.DirectionHorizontal, &sashPos, g.Label("Note: FastTable only works if all rows have same height"), g.Layout{ g.InputText(&filter).Hint("Filter..."), buildTable(), }, ).ID("123456789"), ) } func main() { names = make([]string, 10000) for i := range names { names[i] = fmt.Sprintf("Huge list name demo 范例 %d", i) } wnd := g.NewMasterWindow("Huge list demo", 800, 600, 0) imgui.StyleColorsLight() wnd.Run(loop) } ```
gucio321 commented 8 months ago

well strange... SO its not problem of ID

gucio321 commented 8 months ago

my research:

gucio321 commented 8 months ago

A minimalistic reproduction code:

package main

import (
    g "github.com/AllenDang/giu"
)

var (
    filter  string
    sashPos float32 = 100
)

func buildTable() g.Widget {
    if filter != "" {
        return g.Layout{}
    }

    return g.Button("btn")
}

func loop() {
    g.SingleWindow().Layout(
        g.SplitLayout(
            g.DirectionHorizontal,
            &sashPos,
            g.Layout{},
            g.Layout{
                g.InputText(&filter).Hint("Filter..."),
                // If I put it here, it works?
                // giu.Custom(func() {
                //  buildTable().Build()
                // })
                buildTable(),
            },
        ),
    )
}

func main() {
    wnd := g.NewMasterWindow("Huge list demo", 800, 600, 0)

    wnd.Run(loop)
}

also @WinPooh32 if you put your table builder in giu.Custom it starts to work (no idea why...)

gucio321 commented 6 months ago

haha I've just lookad at this code and got the answer - I'll write it here after a qucik play with dlv

gucio321 commented 6 months ago

ok, nevermind, I have no idea why it doesn't wokr...

Orygin commented 6 months ago

Hello, I am also experiencing this issue, but in a slight variation. TabBars loose track of the active tab when inside a splitlayout when the layout is modified. A variation of your reproducing code as such triggers the issue:

func loop() {
    g.SingleWindow().Layout(
        g.SplitLayout(
            g.DirectionHorizontal,
            &sashPos,
            g.Layout{},
            g.Layout{
                g.TabBar().TabItems(
                    g.TabItem("A"),
                    g.TabItem("B").Layout(
                        g.InputText(&filter).Hint("Filter..."),
                        buildTable(),
                    ),
                ),
            },
        ),
    )
}

The problem also goes away when using Custom(). I had this issue on v0.6.2 and v0.7.0 as well.