diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bfccf3c546..4140a845ba 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -29,7 +29,7 @@ jobs: # we can't test gpu, xyz, and system on the CI since there is no Vulkan support - name: Test - run: go test -v $(go list ./... | grep -v gpu | grep -v xyz | grep -v system) -coverprofile cover.out + run: go test -v $(go list ./... | grep -v gpu | grep -v xyz | grep -v system) -coverprofile cover.out -timeout 30s - name: Update coverage report uses: ncruces/go-coverage-report@v0 diff --git a/core/enumgen.go b/core/enumgen.go index 32710ad503..5d79b13013 100644 --- a/core/enumgen.go +++ b/core/enumgen.go @@ -178,16 +178,16 @@ func (i *MeterTypes) UnmarshalText(text []byte) error { return enums.UnmarshalText(i, text, "MeterTypes") } -var _sceneFlagsValues = []sceneFlags{0, 1, 2, 3, 4, 5} +var _sceneFlagsValues = []sceneFlags{0, 1, 2, 3, 4, 5, 6} // sceneFlagsN is the highest valid value for type sceneFlags, plus one. -const sceneFlagsN sceneFlags = 6 +const sceneFlagsN sceneFlags = 7 -var _sceneFlagsValueMap = map[string]sceneFlags{`HasShown`: 0, `Updating`: 1, `NeedsRender`: 2, `NeedsLayout`: 3, `ImageUpdated`: 4, `ContentSizing`: 5} +var _sceneFlagsValueMap = map[string]sceneFlags{`HasShown`: 0, `Updating`: 1, `NeedsRender`: 2, `NeedsLayout`: 3, `HasDeferred`: 4, `ImageUpdated`: 5, `ContentSizing`: 6} -var _sceneFlagsDescMap = map[sceneFlags]string{0: `sceneHasShown is whether this scene has been shown. This is used to ensure that [events.Show] is only sent once.`, 1: `sceneUpdating means the Scene is in the process of sceneUpdating. It is set for any kind of tree-level update. Skip any further update passes until it goes off.`, 2: `sceneNeedsRender is whether anything in the Scene needs to be re-rendered (but not necessarily the whole scene itself).`, 3: `sceneNeedsLayout is whether the Scene needs a new layout pass.`, 4: `sceneImageUpdated indicates that the Scene's image has been updated e.g., due to a render or a resize. This is reset by the global [RenderWindow] rendering pass, so it knows whether it needs to copy the image up to the GPU or not.`, 5: `sceneContentSizing means that this scene is currently doing a contentSize computation to compute the size of the scene (for sizing window for example). Affects layout size computation.`} +var _sceneFlagsDescMap = map[sceneFlags]string{0: `sceneHasShown is whether this scene has been shown. This is used to ensure that [events.Show] is only sent once.`, 1: `sceneUpdating means the Scene is in the process of sceneUpdating. It is set for any kind of tree-level update. Skip any further update passes until it goes off.`, 2: `sceneNeedsRender is whether anything in the Scene needs to be re-rendered (but not necessarily the whole scene itself).`, 3: `sceneNeedsLayout is whether the Scene needs a new layout pass.`, 4: `sceneHasDeferred is whether the Scene has elements with Deferred functions.`, 5: `sceneImageUpdated indicates that the Scene's image has been updated e.g., due to a render or a resize. This is reset by the global [RenderWindow] rendering pass, so it knows whether it needs to copy the image up to the GPU or not.`, 6: `sceneContentSizing means that this scene is currently doing a contentSize computation to compute the size of the scene (for sizing window for example). Affects layout size computation.`} -var _sceneFlagsMap = map[sceneFlags]string{0: `HasShown`, 1: `Updating`, 2: `NeedsRender`, 3: `NeedsLayout`, 4: `ImageUpdated`, 5: `ContentSizing`} +var _sceneFlagsMap = map[sceneFlags]string{0: `HasShown`, 1: `Updating`, 2: `NeedsRender`, 3: `NeedsLayout`, 4: `HasDeferred`, 5: `ImageUpdated`, 6: `ContentSizing`} // String returns the string representation of this sceneFlags value. func (i sceneFlags) String() string { return enums.BitFlagString(i, _sceneFlagsValues) } diff --git a/core/events.go b/core/events.go index ad6011d051..19528e9e17 100644 --- a/core/events.go +++ b/core/events.go @@ -180,12 +180,12 @@ func (em *Events) handleFocusEvent(e events.Event) { if DebugSettings.FocusTrace { fmt.Println(em.scene, "StartFocus:", em.startFocus) } - em.setFocusEvent(em.startFocus) + em.setFocus(em.startFocus) case em.prevFocus != nil: if DebugSettings.FocusTrace { fmt.Println(em.scene, "PrevFocus:", em.prevFocus) } - em.setFocusEvent(em.prevFocus) + em.setFocus(em.prevFocus) em.prevFocus = nil } } @@ -881,14 +881,14 @@ func (em *Events) focusClear() bool { } em.prevFocus = em.focus } - return em.setFocusEvent(nil) + return em.setFocus(nil) } -// setFocus sets focus to given item, and returns true if focus changed. +// setFocusQuiet sets focus to given item, and returns true if focus changed. // If item is nil, then nothing has focus. -// This does NOT send the events.Focus event to the widget. -// See [SetFocusEvent] for version that does send event. -func (em *Events) setFocus(w Widget) bool { +// This does NOT send the [events.Focus] event to the widget. +// See [Events.setFocus] for version that does send an event. +func (em *Events) setFocusQuiet(w Widget) bool { if DebugSettings.FocusTrace { fmt.Println(em.scene, "SetFocus:", w) } @@ -905,11 +905,11 @@ func (em *Events) setFocus(w Widget) bool { return got } -// setFocusEvent sets focus to given item, and returns true if focus changed. +// setFocus sets focus to given item, and returns true if focus changed. // If item is nil, then nothing has focus. // This sends the [events.Focus] event to the widget. -// See [SetFocus] for a version that does not. -func (em *Events) setFocusEvent(w Widget) bool { +// See [Events.setFocusQuiet] for a version that does not. +func (em *Events) setFocus(w Widget) bool { if DebugSettings.FocusTrace { fmt.Println(em.scene, "SetFocusEvent:", w) } @@ -975,7 +975,7 @@ func (em *Events) FocusNextFrom(from Widget) bool { wb := w.AsWidget() return wb.IsVisible() && !wb.StateIs(states.Disabled) && wb.AbilityIs(abilities.Focusable) }) - em.setFocusEvent(next) + em.setFocus(next) return next != nil } @@ -991,7 +991,7 @@ func (em *Events) focusOnOrNext(foc Widget) bool { return false } if wb.AbilityIs(abilities.Focusable) { - em.setFocusEvent(foc) + em.setFocus(foc) return true } return em.FocusNextFrom(foc) @@ -1009,7 +1009,7 @@ func (em *Events) focusOnOrPrev(foc Widget) bool { return false } if wb.AbilityIs(abilities.Focusable) { - em.setFocusEvent(foc) + em.setFocus(foc) return true } return em.focusPrevFrom(foc) @@ -1031,7 +1031,7 @@ func (em *Events) focusPrevFrom(from Widget) bool { wb := w.AsWidget() return wb.IsVisible() && !wb.StateIs(states.Disabled) && wb.AbilityIs(abilities.Focusable) }) - em.setFocusEvent(prev) + em.setFocus(prev) return prev != nil } @@ -1072,7 +1072,7 @@ func (em *Events) activateStartFocus() bool { em.focusFirst() } else { // fmt.Println("start focus on:", sf) - em.setFocusEvent(sf) + em.setFocus(sf) } return true } diff --git a/core/filepicker.go b/core/filepicker.go index f8ba257e5f..f3c1406b0b 100644 --- a/core/filepicker.go +++ b/core/filepicker.go @@ -107,7 +107,7 @@ func (fp *FilePicker) Init() { case keymap.Search: e.SetHandled() sf := fp.selectField - sf.SetFocusEvent() + sf.SetFocus() } }) diff --git a/core/frame.go b/core/frame.go index c3e35db84b..f8a45372c9 100644 --- a/core/frame.go +++ b/core/frame.go @@ -353,7 +353,7 @@ func (fr *Frame) focusOnName(e events.Event) bool { if focel != nil { em := fr.Events() if em != nil { - em.setFocusEvent(focel.(Widget)) // this will also scroll by default! + em.setFocus(focel.(Widget)) // this will also scroll by default! } fr.focusNameLast = focel return true diff --git a/core/list.go b/core/list.go index 0cf485d549..707d9f7f5e 100644 --- a/core/list.go +++ b/core/list.go @@ -530,7 +530,7 @@ func (lb *ListBase) MakeGrid(p *tree.Plan, maker func(p *tree.Plan)) { s.Min.Y.Em(6) }) oc := func(e events.Event) { - lb.SetFocusEvent() + lb.SetFocus() row, _, isValid := w.indexFromPixel(e.Pos()) if isValid { lb.updateSelectRow(row, e.SelectMode()) @@ -868,7 +868,7 @@ func (lb *ListBase) RowGrabFocus(row int) *WidgetBase { return w } lb.InFocusGrab = true - w.SetFocusEvent() + w.SetFocus() lb.InFocusGrab = false return w } diff --git a/core/mainstage.go b/core/mainstage.go index 4a6c5ac3bf..2775f09cc9 100644 --- a/core/mainstage.go +++ b/core/mainstage.go @@ -300,7 +300,7 @@ func (st *Stage) runDialog() *Stage { // if our main stages are nil, we wait until our context is shown and then try again if ctx.Scene.Stage == nil || ctx.Scene.Stage.Mains == nil { - ctx.OnShow(func(e events.Event) { + ctx.Defer(func() { st.runDialog() }) return st diff --git a/core/pages.go b/core/pages.go index b638e48855..5c1434d3c8 100644 --- a/core/pages.go +++ b/core/pages.go @@ -47,6 +47,7 @@ func (pg *Pages) Init() { } pg.page = pg.Page fun(pg) + pg.DeferShown() }) } diff --git a/core/popupstage.go b/core/popupstage.go index 524c25a2dd..a9ffafd996 100644 --- a/core/popupstage.go +++ b/core/popupstage.go @@ -51,7 +51,7 @@ func (st *Stage) runPopup() *Stage { // if our context stage is nil, we wait until // our context is shown and then try again if ctx.Scene.Stage == nil { - ctx.OnShow(func(e events.Event) { + ctx.Defer(func() { st.runPopup() }) return st diff --git a/core/render.go b/core/render.go index ea222dc106..5f64be8745 100644 --- a/core/render.go +++ b/core/render.go @@ -17,6 +17,7 @@ import ( "cogentcore.org/core/base/profile" "cogentcore.org/core/colors" "cogentcore.org/core/colors/cam/hct" + "cogentcore.org/core/events" "cogentcore.org/core/math32" "cogentcore.org/core/styles" "cogentcore.org/core/tree" @@ -213,12 +214,8 @@ func (sc *Scene) doUpdate() bool { } if sc.showIter == sceneShowIters { // end of first pass - sc.showIter++ - if !sc.hasFlag(sceneContentSizing) { - sc.Events.activateStartFocus() - } + sc.showIter++ // just go 1 past the iters cutoff } - return true } @@ -399,8 +396,51 @@ func (wb *WidgetBase) renderChildren() { }) } -//////////////////////////////////////////////////////////////////////////////// -// Standard Box Model rendering +//////// Defer + +// Defer adds a function to [WidgetBase.Deferred] that will be called after the next +// [Scene] update/render, including on the initial Scene render. After the function +// is called, it is removed and not called again. In the function, sending events +// etc will work as expected. +func (wb *WidgetBase) Defer(fun func()) { + wb.Deferred = append(wb.Deferred, fun) + if wb.Scene != nil { + wb.Scene.setFlag(true, sceneHasDeferred) + } +} + +// runDeferred runs deferred functions on all widgets in the scene. +func (sc *Scene) runDeferred() { + sc.WidgetWalkDown(func(cw Widget, cwb *WidgetBase) bool { + for _, f := range cwb.Deferred { + f() + } + cwb.Deferred = nil + return tree.Continue + }) +} + +// DeferShown adds a [WidgetBase.Defer] function to call [WidgetBase.Shown] +// and activate [WidgetBase.StartFocus]. For example, this is called in [Tabs] +// and [Pages] when a tab/page is newly shown, so that elements can perform +// [WidgetBase.OnShow] updating as needed. +func (wb *WidgetBase) DeferShown() { + wb.Defer(func() { + wb.Shown() + wb.Scene.Events.activateStartFocus() + }) +} + +// Shown sends [events.Show] to all widgets from this one down. Also see +// [WidgetBase.DeferShown]. +func (wb *WidgetBase) Shown() { + wb.WidgetWalkDown(func(cw Widget, cwb *WidgetBase) bool { + cwb.Send(events.Show) + return tree.Continue + }) +} + +//////// Standard Box Model rendering // RenderBoxGeom renders a box with the given geometry. func (wb *WidgetBase) RenderBoxGeom(pos math32.Vector2, sz math32.Vector2, bs styles.Border) { @@ -414,8 +454,7 @@ func (wb *WidgetBase) RenderStandardBox() { wb.Scene.PaintContext.DrawStandardBox(&wb.Styles, pos, sz, wb.parentActualBackground()) } -////////////////////////////////////////////////////////////////// -// Widget position functions +//////// Widget position functions // PointToRelPos translates a point in Scene pixel coords // into relative position within node, based on the Content BBox @@ -446,7 +485,7 @@ func (wb *WidgetBase) winPos(x, y float32) image.Point { return pt } -// Profiling and Benchmarking, controlled by settings app bar: +//////// Profiling and Benchmarking, controlled by settings app bar // ProfileToggle turns profiling on or off, which does both // targeted profiling and global CPU and memory profiling. diff --git a/core/renderwindow.go b/core/renderwindow.go index 21d6a77d16..73db384f03 100644 --- a/core/renderwindow.go +++ b/core/renderwindow.go @@ -436,7 +436,7 @@ func (w *renderWindow) handleWindowEvents(e events.Event) { rc.unlock() // one case where we need to break lock w.renderWindow() rc.lock() - w.mains.sendShowEvents() + w.mains.runDeferred() // note: must be outside of locks in renderWindow case events.WindowResize: e.SetHandled() diff --git a/core/scene.go b/core/scene.go index 24ccb3469c..0f9c8ad383 100644 --- a/core/scene.go +++ b/core/scene.go @@ -118,6 +118,9 @@ const ( // sceneNeedsLayout is whether the Scene needs a new layout pass. sceneNeedsLayout + // sceneHasDeferred is whether the Scene has elements with Deferred functions. + sceneHasDeferred + // sceneImageUpdated indicates that the Scene's image has been updated // e.g., due to a render or a resize. This is reset by the // global [RenderWindow] rendering pass, so it knows whether it needs to diff --git a/core/stages.go b/core/stages.go index a3344a1a90..792bcfbefb 100644 --- a/core/stages.go +++ b/core/stages.go @@ -9,9 +9,7 @@ import ( "sync" "cogentcore.org/core/base/ordmap" - "cogentcore.org/core/events" "cogentcore.org/core/math32" - "cogentcore.org/core/tree" ) // stages manages a stack of [Stage]s. @@ -246,25 +244,29 @@ func (sm *stages) windowStage() *Stage { return nil } -func (sm *stages) sendShowEvents() { +func (sm *stages) runDeferred() { for _, kv := range sm.stack.Order { st := kv.Value if st.Scene == nil { continue } sc := st.Scene + if sc.hasFlag(sceneContentSizing) { + continue + } + if sc.hasFlag(sceneHasDeferred) { + sc.setFlag(false, sceneHasDeferred) + sc.runDeferred() + } + if sc.showIter == sceneShowIters+1 { sc.showIter++ if !sc.hasFlag(sceneHasShown) { + if !sc.hasFlag(sceneContentSizing) { + sc.Events.activateStartFocus() + } sc.setFlag(true, sceneHasShown) - // profile.Profiling = true - // pr := profile.Start("send show") - sc.WidgetWalkDown(func(cw Widget, cwb *WidgetBase) bool { - cwb.Send(events.Show) - return tree.Continue - }) - // pr.End() - // profile.Report(time.Millisecond) + sc.Shown() } } } diff --git a/core/table.go b/core/table.go index e115a37eb6..5ed80d3543 100644 --- a/core/table.go +++ b/core/table.go @@ -485,7 +485,7 @@ func (tb *Table) RowGrabFocus(row int) *WidgetBase { for fli := 0; fli < tb.numVisibleFields; fli++ { w := lg.Child(ridx + idxOff + fli).(Widget).AsWidget() if w.CanFocus() { - w.SetFocusEvent() + w.SetFocus() return w } } diff --git a/core/tabs.go b/core/tabs.go index 4970a8fefe..a29530e40a 100644 --- a/core/tabs.go +++ b/core/tabs.go @@ -280,6 +280,7 @@ func (ts *Tabs) SelectTabIndex(idx int) *Frame { tab.SetSelected(true) fr.StackTop = idx fr.Update() + frame.DeferShown() ts.mu.Unlock() return frame } diff --git a/core/text.go b/core/text.go index f3eeda4802..ff52fc5be0 100644 --- a/core/text.go +++ b/core/text.go @@ -211,7 +211,7 @@ func (tx *Text) Init() { }) tx.OnDoubleClick(func(e events.Event) { tx.SetSelected(true) - tx.SetFocus() + tx.SetFocusQuiet() }) tx.OnFocusLost(func(e events.Event) { tx.SetSelected(false) diff --git a/core/textfield.go b/core/textfield.go index 61f91a3576..c5f4c77486 100644 --- a/core/textfield.go +++ b/core/textfield.go @@ -290,7 +290,7 @@ func (tf *TextField) Init() { }) tf.On(events.MouseDown, func(e events.Event) { if !tf.StateIs(states.Focused) { - tf.SetFocusEvent() // always grab, even if read only.. + tf.SetFocus() // always grab, even if read only.. } if tf.IsReadOnly() { return @@ -309,14 +309,14 @@ func (tf *TextField) Init() { if tf.IsReadOnly() { return } - tf.SetFocusEvent() + tf.SetFocus() }) tf.On(events.DoubleClick, func(e events.Event) { if tf.IsReadOnly() { return } if !tf.IsReadOnly() && !tf.StateIs(states.Focused) { - tf.SetFocusEvent() + tf.SetFocus() } e.SetHandled() tf.selectWord() @@ -326,7 +326,7 @@ func (tf *TextField) Init() { return } if !tf.IsReadOnly() && !tf.StateIs(states.Focused) { - tf.SetFocusEvent() + tf.SetFocus() } e.SetHandled() tf.selectAll() @@ -547,7 +547,7 @@ func (tf *TextField) clear() { tf.startPos = 0 tf.endPos = 0 tf.selectReset() - tf.SetFocusEvent() // this is essential for ensuring that the clear applies after focus is lost.. + tf.SetFocus() // this is essential for ensuring that the clear applies after focus is lost.. tf.NeedsRender() } diff --git a/core/tree.go b/core/tree.go index 175183a9c6..5b06a3f728 100644 --- a/core/tree.go +++ b/core/tree.go @@ -766,20 +766,20 @@ func (tr *Tree) selectUpdate(mode events.SelectModes) bool { if len(sl) > 1 { tr.UnselectAll() tr.Select() - tr.SetFocus() + tr.SetFocusQuiet() sel = true } } else { tr.UnselectAll() tr.Select() - tr.SetFocus() + tr.SetFocusQuiet() sel = true } case events.ExtendContinuous: sl := tr.GetSelectedNodes() if len(sl) == 0 { tr.Select() - tr.SetFocus() + tr.SetFocusQuiet() sel = true } else { minIndex := -1 @@ -814,7 +814,7 @@ func (tr *Tree) selectUpdate(mode events.SelectModes) bool { tr.UnselectEvent() } else { tr.Select() - tr.SetFocus() + tr.SetFocusQuiet() sel = true } case events.SelectQuiet: @@ -903,7 +903,7 @@ func (tr *Tree) moveDown(selMode events.SelectModes) *Tree { func (tr *Tree) moveDownEvent(selMode events.SelectModes) *Tree { nn := tr.moveDown(selMode) if nn != nil && nn != tr { - nn.SetFocus() + nn.SetFocusQuiet() nn.ScrollToThis() tr.sendSelectEvent() } @@ -963,7 +963,7 @@ func (tr *Tree) moveUp(selMode events.SelectModes) *Tree { func (tr *Tree) moveUpEvent(selMode events.SelectModes) *Tree { nn := tr.moveUp(selMode) if nn != nil && nn != tr { - nn.SetFocus() + nn.SetFocusQuiet() nn.ScrollToThis() tr.sendSelectEvent() } @@ -996,7 +996,7 @@ func (tr *Tree) movePageUpEvent(selMode events.SelectModes) *Tree { if selMode == events.SelectOne { fnn.selectUpdate(selMode) } - fnn.SetFocus() + fnn.SetFocusQuiet() fnn.ScrollToThis() tr.sendSelectEvent() } @@ -1027,7 +1027,7 @@ func (tr *Tree) movePageDownEvent(selMode events.SelectModes) *Tree { if selMode == events.SelectOne { fnn.selectUpdate(selMode) } - fnn.SetFocus() + fnn.SetFocusQuiet() fnn.ScrollToThis() tr.sendSelectEvent() } @@ -1055,7 +1055,7 @@ func (tr *Tree) moveToLastChild(selMode events.SelectModes) *Tree { // and emits select event for newly selected item func (tr *Tree) moveHomeEvent(selMode events.SelectModes) *Tree { tr.root.selectUpdate(selMode) - tr.root.SetFocus() + tr.root.SetFocusQuiet() tr.root.ScrollToThis() tr.root.sendSelectEvent() return tr.root @@ -1083,7 +1083,7 @@ func (tr *Tree) moveEndEvent(selMode events.SelectModes) *Tree { if selMode == events.SelectOne { fnn.selectUpdate(selMode) } - fnn.SetFocus() + fnn.SetFocusQuiet() fnn.ScrollToThis() tr.sendSelectEvent() } diff --git a/core/typegen.go b/core/typegen.go index b396d57f43..7a3776cdc6 100644 --- a/core/typegen.go +++ b/core/typegen.go @@ -1264,7 +1264,7 @@ var _ = types.AddType(&types.Type{Name: "cogentcore.org/core/core.FontButton", I // a dialog for selecting the font family. func NewFontButton(parent ...tree.Node) *FontButton { return tree.New[FontButton](parent...) } -var _ = types.AddType(&types.Type{Name: "cogentcore.org/core/core.WidgetBase", IDName: "widget-base", Doc: "WidgetBase implements the [Widget] interface and provides the core functionality\nof a widget. You must use WidgetBase as an embedded struct in all higher-level\nwidget types. It renders the standard box model, but does not layout or render\nany children; see [Frame] for that.", Methods: []types.Method{{Name: "Update", Doc: "Update updates the widget and all of its children by running [WidgetBase.UpdateWidget]\nand [WidgetBase.Style] on each one, and triggering a new layout pass with\n[WidgetBase.NeedsLayout]. It is the main way that end users should trigger widget\nupdates, and it is guaranteed to fully update a widget to the current state.\nFor example, it should be called after making any changes to the core properties\nof a widget, such as the text of [Text], the icon of a [Button], or the slice\nof a [Table].\n\nUpdate differs from [WidgetBase.UpdateWidget] in that it updates the widget and all\nof its children down the tree, whereas [WidgetBase.UpdateWidget] only updates the widget\nitself. Also, Update also calls [WidgetBase.Style] and [WidgetBase.NeedsLayout],\nwhereas [WidgetBase.UpdateWidget] does not. End-user code should typically call Update,\nnot [WidgetBase.UpdateWidget].\n\nIf you are calling this in a separate goroutine outside of the main\nconfiguration, rendering, and event handling structure, you need to\ncall [WidgetBase.AsyncLock] and [WidgetBase.AsyncUnlock] before and\nafter this, respectively.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "NodeBase"}}, Fields: []types.Field{{Name: "Tooltip", Doc: "Tooltip is the text for the tooltip for this widget,\nwhich can use HTML formatting."}, {Name: "Parts", Doc: "Parts are a separate tree of sub-widgets that can be used to store\northogonal parts of a widget when necessary to separate them from children.\nFor example, [Tree]s use parts to separate their internal parts from\nthe other child tree nodes. Composite widgets like buttons should\nNOT use parts to store their components; parts should only be used when\nabsolutely necessary. Use [WidgetBase.newParts] to make the parts."}, {Name: "Geom", Doc: "Geom has the full layout geometry for size and position of this widget."}, {Name: "OverrideStyle", Doc: "OverrideStyle, if true, indicates override the computed styles of the widget\nand allow directly editing [WidgetBase.Styles]. It is typically only set in\nthe inspector."}, {Name: "Styles", Doc: "Styles are styling settings for this widget. They are set by\n[WidgetBase.Stylers] in [WidgetBase.Style]."}, {Name: "Stylers", Doc: "Stylers is a tiered set of functions that are called in sequential\nascending order (so the last added styler is called last and\nthus can override all other stylers) to style the element.\nThese should be set using the [WidgetBase.Styler], [WidgetBase.FirstStyler],\nand [WidgetBase.FinalStyler] functions."}, {Name: "Listeners", Doc: "Listeners is a tiered set of event listener functions for processing events on this widget.\nThey are called in sequential descending order (so the last added listener\nis called first). They should be added using the [WidgetBase.On], [WidgetBase.OnFirst],\nand [WidgetBase.OnFinal] functions, or any of the various On{EventType} helper functions."}, {Name: "ContextMenus", Doc: "ContextMenus is a slice of menu functions to call to construct\nthe widget's context menu on an [events.ContextMenu]. The\nfunctions are called in reverse order such that the elements\nadded in the last function are the first in the menu.\nContext menus should be added through [WidgetBase.AddContextMenu].\nSeparators will be added between each context menu function.\n[Scene.ContextMenus] apply to all widgets in the scene."}, {Name: "Scene", Doc: "Scene is the overall Scene to which we belong. It is automatically\nby widgets whenever they are added to another widget parent."}, {Name: "ValueUpdate", Doc: "ValueUpdate is a function set by [Bind] that is called in\n[WidgetBase.UpdateWidget] to update the widget's value from the bound value.\nIt should not be accessed by end users."}, {Name: "ValueOnChange", Doc: "ValueOnChange is a function set by [Bind] that is called when\nthe widget receives an [events.Change] event to update the bound value\nfrom the widget's value. It should not be accessed by end users."}, {Name: "ValueTitle", Doc: "ValueTitle is the title to display for a dialog for this [Value]."}, {Name: "flags", Doc: "/ flags are atomic bit flags for [WidgetBase] state."}}}) +var _ = types.AddType(&types.Type{Name: "cogentcore.org/core/core.WidgetBase", IDName: "widget-base", Doc: "WidgetBase implements the [Widget] interface and provides the core functionality\nof a widget. You must use WidgetBase as an embedded struct in all higher-level\nwidget types. It renders the standard box model, but does not layout or render\nany children; see [Frame] for that.", Methods: []types.Method{{Name: "Update", Doc: "Update updates the widget and all of its children by running [WidgetBase.UpdateWidget]\nand [WidgetBase.Style] on each one, and triggering a new layout pass with\n[WidgetBase.NeedsLayout]. It is the main way that end users should trigger widget\nupdates, and it is guaranteed to fully update a widget to the current state.\nFor example, it should be called after making any changes to the core properties\nof a widget, such as the text of [Text], the icon of a [Button], or the slice\nof a [Table].\n\nUpdate differs from [WidgetBase.UpdateWidget] in that it updates the widget and all\nof its children down the tree, whereas [WidgetBase.UpdateWidget] only updates the widget\nitself. Also, Update also calls [WidgetBase.Style] and [WidgetBase.NeedsLayout],\nwhereas [WidgetBase.UpdateWidget] does not. End-user code should typically call Update,\nnot [WidgetBase.UpdateWidget].\n\nIf you are calling this in a separate goroutine outside of the main\nconfiguration, rendering, and event handling structure, you need to\ncall [WidgetBase.AsyncLock] and [WidgetBase.AsyncUnlock] before and\nafter this, respectively.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "NodeBase"}}, Fields: []types.Field{{Name: "Tooltip", Doc: "Tooltip is the text for the tooltip for this widget,\nwhich can use HTML formatting."}, {Name: "Parts", Doc: "Parts are a separate tree of sub-widgets that can be used to store\northogonal parts of a widget when necessary to separate them from children.\nFor example, [Tree]s use parts to separate their internal parts from\nthe other child tree nodes. Composite widgets like buttons should\nNOT use parts to store their components; parts should only be used when\nabsolutely necessary. Use [WidgetBase.newParts] to make the parts."}, {Name: "Geom", Doc: "Geom has the full layout geometry for size and position of this widget."}, {Name: "OverrideStyle", Doc: "OverrideStyle, if true, indicates override the computed styles of the widget\nand allow directly editing [WidgetBase.Styles]. It is typically only set in\nthe inspector."}, {Name: "Styles", Doc: "Styles are styling settings for this widget. They are set by\n[WidgetBase.Stylers] in [WidgetBase.Style]."}, {Name: "Stylers", Doc: "Stylers is a tiered set of functions that are called in sequential\nascending order (so the last added styler is called last and\nthus can override all other stylers) to style the element.\nThese should be set using the [WidgetBase.Styler], [WidgetBase.FirstStyler],\nand [WidgetBase.FinalStyler] functions."}, {Name: "Listeners", Doc: "Listeners is a tiered set of event listener functions for processing events on this widget.\nThey are called in sequential descending order (so the last added listener\nis called first). They should be added using the [WidgetBase.On], [WidgetBase.OnFirst],\nand [WidgetBase.OnFinal] functions, or any of the various On{EventType} helper functions."}, {Name: "ContextMenus", Doc: "ContextMenus is a slice of menu functions to call to construct\nthe widget's context menu on an [events.ContextMenu]. The\nfunctions are called in reverse order such that the elements\nadded in the last function are the first in the menu.\nContext menus should be added through [WidgetBase.AddContextMenu].\nSeparators will be added between each context menu function.\n[Scene.ContextMenus] apply to all widgets in the scene."}, {Name: "Deferred", Doc: "Deferred is a slice of functions to call after a full [Scene] update.\nAt this point event sending will work as expected, for example.\nIt is used to send an OnShow event when a [Tabs] or [Pages] item is newly shown,\nand to apply [Widget.StartFocus]."}, {Name: "Scene", Doc: "Scene is the overall Scene to which we belong. It is automatically\nby widgets whenever they are added to another widget parent."}, {Name: "ValueUpdate", Doc: "ValueUpdate is a function set by [Bind] that is called in\n[WidgetBase.UpdateWidget] to update the widget's value from the bound value.\nIt should not be accessed by end users."}, {Name: "ValueOnChange", Doc: "ValueOnChange is a function set by [Bind] that is called when\nthe widget receives an [events.Change] event to update the bound value\nfrom the widget's value. It should not be accessed by end users."}, {Name: "ValueTitle", Doc: "ValueTitle is the title to display for a dialog for this [Value]."}, {Name: "flags", Doc: "/ flags are atomic bit flags for [WidgetBase] state."}}}) // NewWidgetBase returns a new [WidgetBase] with the given optional parent: // WidgetBase implements the [Widget] interface and provides the core functionality diff --git a/core/widget.go b/core/widget.go index 686594634c..f6f85cc109 100644 --- a/core/widget.go +++ b/core/widget.go @@ -191,6 +191,11 @@ type WidgetBase struct { // [Scene.ContextMenus] apply to all widgets in the scene. ContextMenus []func(m *Scene) `copier:"-" json:"-" xml:"-" set:"-" edit:"-"` + // Deferred is a slice of functions to call after the next [Scene] update/render. + // In each function event sending etc will work as expected. Use + // [WidgetBase.Defer] to add a function. + Deferred []func() `copier:"-" json:"-" xml:"-" set:"-" edit:"-"` + // Scene is the overall Scene to which we belong. It is automatically // by widgets whenever they are added to another widget parent. Scene *Scene `copier:"-" json:"-" xml:"-" set:"-"` @@ -459,7 +464,8 @@ func (wb *WidgetBase) forVisibleChildren(fun func(i int, cw Widget, cwb *WidgetB } } -// WidgetWalkDown is a version of [tree.NodeBase.WalkDown] that operates on [Widget] types. +// WidgetWalkDown is a version of [tree.NodeBase.WalkDown] that operates on [Widget] types, +// calling the given function on the Widget and all of its children in a depth-first manner. // Return [tree.Continue] to continue and [tree.Break] to terminate. func (wb *WidgetBase) WidgetWalkDown(fun func(cw Widget, cwb *WidgetBase) bool) { wb.WalkDown(func(n tree.Node) bool { diff --git a/core/widgetevents.go b/core/widgetevents.go index 6206903d46..210a9f2a26 100644 --- a/core/widgetevents.go +++ b/core/widgetevents.go @@ -293,7 +293,7 @@ func (wb *WidgetBase) handleWidgetClick() { wb.SetState(!wb.StateIs(states.Checked), states.Checked) } if wb.AbilityIs(abilities.Focusable) { - wb.SetFocus() + wb.SetFocusQuiet() } else { wb.focusClear() } @@ -467,15 +467,15 @@ func (wb *WidgetBase) HandleClickOnEnterSpace() { }) } -/////////////////////////////////////////////////////////////////// -// Focus +//////// Focus -// SetFocus sets the keyboard input focus on this item or the first item within it -// that can be focused (if none, then just sets focus to this object). -// This does not send an [events.Focus] event, which typically results in -// the widget being styled as focused. See [WidgetBase.SetFocusEvent] for -// a version that does. -func (wb *WidgetBase) SetFocus() { +// SetFocusQuiet sets the keyboard input focus on this item or the first item +// within it that can be focused (if none, then just sets focus to this widget). +// This does NOT send an [events.Focus] event, so the widget will NOT appear focused; +// it will however receive keyboard input, at which point it will get visible focus. +// See [WidgetBase.SetFocus] for a version that sends an event. Also see +// [WidgetBase.StartFocus]. +func (wb *WidgetBase) SetFocusQuiet() { foc := wb.This.(Widget) if !wb.AbilityIs(abilities.Focusable) { foc = wb.focusableInThis() @@ -485,17 +485,16 @@ func (wb *WidgetBase) SetFocus() { } em := wb.Events() if em != nil { - // fmt.Println("grab focus:", foc) - em.setFocus(foc) // doesn't send event + em.setFocusQuiet(foc) // doesn't send event } } -// SetFocusEvent sets the keyboard input focus on this item or the first item within it -// that can be focused (if none, then just sets focus to this object). +// SetFocus sets the keyboard input focus on this item or the first item within it +// that can be focused (if none, then just sets focus to this widget). // This sends an [events.Focus] event, which typically results in -// the widget being styled as focused. See [WidgetBase.SetFocus] for -// a version that does not. -func (wb *WidgetBase) SetFocusEvent() { +// the widget being styled as focused. See [WidgetBase.SetFocusQuiet] for +// a version that does not. Also see [WidgetBase.StartFocus]. +func (wb *WidgetBase) SetFocus() { foc := wb.This.(Widget) if !wb.AbilityIs(abilities.Focusable) { foc = wb.focusableInThis() @@ -505,8 +504,7 @@ func (wb *WidgetBase) SetFocusEvent() { } em := wb.Events() if em != nil { - // fmt.Println("grab focus:", foc) - em.setFocusEvent(foc) // doesn't send event + em.setFocus(foc) } } @@ -547,7 +545,10 @@ func (wb *WidgetBase) focusClear() { } } -// StartFocus specifies that this widget should get focus when the [Scene] is shown. +// StartFocus specifies that this widget should get focus when the [Scene] is shown, +// or when a major content managing widget (e.g., [Tabs], [Pages]) shows a +// tab/page/element that contains this widget. This is implemented via an +// [events.Show] event. func (wb *WidgetBase) StartFocus() { em := wb.Events() if em != nil { diff --git a/docs/content/1-tutorials/2-sign-in.md b/docs/content/1-tutorials/2-sign-in.md index f760e529e5..242aad12d6 100644 --- a/docs/content/1-tutorials/2-sign-in.md +++ b/docs/content/1-tutorials/2-sign-in.md @@ -18,4 +18,4 @@ pg.AddPage("home", func(pg *core.Pages) { pg.Open("sign-in") }) }) -``` \ No newline at end of file +``` diff --git a/enums/enumgen/testdata/enumgen.go b/enums/enumgen/testdata/enumgen.go index c2481dfa1d..e02efad438 100644 --- a/enums/enumgen/testdata/enumgen.go +++ b/enums/enumgen/testdata/enumgen.go @@ -1,4 +1,4 @@ -// Code generated by "enumgen.test -test.testlogfile=/var/folders/x1/r8shprmj7j71zbw3qvgl9dqc0000gq/T/go-build2217811126/b647/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true"; DO NOT EDIT. +// Code generated by "enumgen.test -test.testlogfile=/var/folders/x1/r8shprmj7j71zbw3qvgl9dqc0000gq/T/go-build1829688390/b649/testlog.txt -test.paniconexit0 -test.timeout=20s"; DO NOT EDIT. package testdata diff --git a/events/enumgen.go b/events/enumgen.go index 3abc3f2aaf..720dc6bae3 100644 --- a/events/enumgen.go +++ b/events/enumgen.go @@ -138,7 +138,7 @@ const TypesN Types = 46 var _TypesValueMap = map[string]Types{`UnknownType`: 0, `MouseDown`: 1, `MouseUp`: 2, `MouseMove`: 3, `MouseDrag`: 4, `Click`: 5, `DoubleClick`: 6, `TripleClick`: 7, `ContextMenu`: 8, `LongPressStart`: 9, `LongPressEnd`: 10, `MouseEnter`: 11, `MouseLeave`: 12, `LongHoverStart`: 13, `LongHoverEnd`: 14, `DragStart`: 15, `DragMove`: 16, `DragEnter`: 17, `DragLeave`: 18, `Drop`: 19, `DropDeleteSource`: 20, `SlideStart`: 21, `SlideMove`: 22, `SlideStop`: 23, `Scroll`: 24, `KeyDown`: 25, `KeyUp`: 26, `KeyChord`: 27, `TouchStart`: 28, `TouchEnd`: 29, `TouchMove`: 30, `Magnify`: 31, `Rotate`: 32, `Select`: 33, `Focus`: 34, `FocusLost`: 35, `Change`: 36, `Input`: 37, `Show`: 38, `Close`: 39, `Window`: 40, `WindowResize`: 41, `WindowPaint`: 42, `OS`: 43, `OSOpenFiles`: 44, `Custom`: 45} -var _TypesDescMap = map[Types]string{0: `zero value is an unknown type`, 1: `MouseDown happens when a mouse button is pressed down. See MouseButton() for which. See Click for a synthetic event representing a MouseDown followed by MouseUp on the same element with Left (primary) mouse button. Often that is the most useful.`, 2: `MouseUp happens when a mouse button is released. See MouseButton() for which.`, 3: `MouseMove is always sent when the mouse is moving but no button is down, even if there might be other higher-level events too. These can be numerous and thus it is typically more efficient to listen to other events derived from this. Not unique, and Prev position is updated during compression.`, 4: `MouseDrag is always sent when the mouse is moving and there is a button down, even if there might be other higher-level events too. The start pos indicates where (and when) button first was pressed. Not unique and Prev position is updated during compression.`, 5: `Click represents a MouseDown followed by MouseUp in sequence on the same element, with the Left (primary) button. This is the typical event for most basic user interaction.`, 6: `DoubleClick represents two Click events in a row in rapid succession.`, 7: `TripleClick represents three Click events in a row in rapid succession.`, 8: `ContextMenu represents a MouseDown/Up event with the Right mouse button (which is also activated by Control key + Left Click).`, 9: `LongPressStart is when the mouse has been relatively stable after MouseDown on an element for a minimum duration (500 msec default).`, 10: `LongPressEnd is sent after LongPressStart when the mouse has gone up, moved sufficiently, left the current element, or another input event has happened.`, 11: `MouseEnter is when the mouse enters the bounding box of a new element. It is used for setting the Hover state, and can trigger cursor changes. See DragEnter for alternative case during Drag events.`, 12: `MouseLeave is when the mouse leaves the bounding box of an element, that previously had a MouseEnter event. Given that elements can have overlapping bounding boxes (e.g., child elements within a container), it is not the case that a MouseEnter on a child triggers a MouseLeave on surrounding containers. See DragLeave for alternative case during Drag events.`, 13: `LongHoverStart is when the mouse has been relatively stable after MouseEnter on an element for a minimum duration (500 msec default). This triggers the LongHover state typically used for Tooltips.`, 14: `LongHoverEnd is after LongHoverStart when the mouse has moved sufficiently, left the current element, or another input event has happened, thereby terminating the LongHover state.`, 15: `DragStart is at the start of a drag-n-drop event sequence, when a Draggable element is Active and a sufficient distance of MouseDrag events has occurred to engage the DragStart event.`, 16: `DragMove is for a MouseDrag event during the drag-n-drop sequence. Usually don't need to listen to this one. MouseDrag is also sent.`, 17: `DragEnter is like MouseEnter but after a DragStart during a drag-n-drop sequence. MouseEnter is not sent in this case.`, 18: `DragLeave is like MouseLeave but after a DragStart during a drag-n-drop sequence. MouseLeave is not sent in this case.`, 19: `Drop is sent when an item being Dragged is dropped on top of a target element. The event struct should be DragDrop.`, 20: `DropDeleteSource is sent to the source Drag element if the Drag-n-Drop event is a Move type, which requires deleting the source element. The event struct should be DragDrop.`, 21: `SlideStart is for a Slideable element when Active and a sufficient distance of MouseDrag events has occurred to engage the SlideStart event. Sets the Sliding state.`, 22: `SlideMove is for a Slideable element after SlideStart is being dragged via MouseDrag events.`, 23: `SlideStop is when the mouse button is released on a Slideable element being dragged via MouseDrag events. This typically also accompanied by a Changed event for the new slider value.`, 24: `Scroll is for scroll wheel or other scrolling events (gestures). These are not unique and Delta is updated during compression.`, 25: `KeyDown is when a key is pressed down. This provides fine-grained data about each key as it happens. KeyChord is recommended for a more complete Key event.`, 26: `KeyUp is when a key is released. This provides fine-grained data about each key as it happens. KeyChord is recommended for a more complete Key event.`, 27: `KeyChord is only generated when a non-modifier key is released, and it also contains a string representation of the full chord, suitable for translation into keyboard commands, emacs-style etc. It can be somewhat delayed relative to the KeyUp.`, 28: `TouchStart is when a touch event starts, for the low-level touch event processing. TouchStart also activates MouseDown, Scroll, Magnify, or Rotate events depending on gesture recognition.`, 29: `TouchEnd is when a touch event ends, for the low-level touch event processing. TouchEnd also activates MouseUp events depending on gesture recognition.`, 30: `TouchMove is when a touch event moves, for the low-level touch event processing. TouchMove also activates MouseMove, Scroll, Magnify, or Rotate events depending on gesture recognition.`, 31: `Magnify is a touch-based magnify event (e.g., pinch)`, 32: `Rotate is a touch-based rotate event.`, 33: `Select is sent for any direction of selection change on (or within if relevant) a Selectable element. Typically need to query the element(s) to determine current selection state.`, 34: `Focus is sent when a Focusable element receives keyboard focus (ie: by tabbing).`, 35: `FocusLost is sent when a Focusable element loses keyboard focus.`, 36: `Change is when a value represented by the element has been changed by the user and committed (for example, someone has typed text in a textfield and then pressed enter). This is *not* triggered when the value has not been committed; see [Input] for that. This is for Editable, Checkable, and Slidable items.`, 37: `Input is when a value represented by the element has changed, but has not necessarily been committed (for example, this triggers each time someone presses a key in a text field). This *is* triggered when the value has not been committed; see [Change] for a version that only occurs when the value is committed. This is for Editable, Checkable, and Slidable items.`, 38: `Show is sent to widgets when their Scene is first shown to the user in its final form. Listening to this event enables widgets to perform initial one-time activities on startup, in the context of a fully rendered display. This is guaranteed to only happen once per widget per Scene.`, 39: `Close is sent to widgets when their Scene is being closed. This is an opportunity to save unsaved edits, for example. This is guaranteed to only happen once per widget per Scene.`, 40: `Window reports on changes in the window position, visibility (iconify), focus changes, screen update, and closing. These are only sent once per event (Unique).`, 41: `WindowResize happens when the window has been resized, which can happen continuously during a user resizing episode. These are not Unique events, and are compressed to minimize lag.`, 42: `WindowPaint is sent continuously at FPS frequency (60 frames per second by default) to drive updating check on the window. It is not unique, will be compressed to keep pace with updating.`, 43: `OS is an operating system generated event (app level typically)`, 44: `OSOpenFiles is an event telling app to open given files`, 45: `Custom is a user-defined event with a data any field`} +var _TypesDescMap = map[Types]string{0: `zero value is an unknown type`, 1: `MouseDown happens when a mouse button is pressed down. See MouseButton() for which. See Click for a synthetic event representing a MouseDown followed by MouseUp on the same element with Left (primary) mouse button. Often that is the most useful.`, 2: `MouseUp happens when a mouse button is released. See MouseButton() for which.`, 3: `MouseMove is always sent when the mouse is moving but no button is down, even if there might be other higher-level events too. These can be numerous and thus it is typically more efficient to listen to other events derived from this. Not unique, and Prev position is updated during compression.`, 4: `MouseDrag is always sent when the mouse is moving and there is a button down, even if there might be other higher-level events too. The start pos indicates where (and when) button first was pressed. Not unique and Prev position is updated during compression.`, 5: `Click represents a MouseDown followed by MouseUp in sequence on the same element, with the Left (primary) button. This is the typical event for most basic user interaction.`, 6: `DoubleClick represents two Click events in a row in rapid succession.`, 7: `TripleClick represents three Click events in a row in rapid succession.`, 8: `ContextMenu represents a MouseDown/Up event with the Right mouse button (which is also activated by Control key + Left Click).`, 9: `LongPressStart is when the mouse has been relatively stable after MouseDown on an element for a minimum duration (500 msec default).`, 10: `LongPressEnd is sent after LongPressStart when the mouse has gone up, moved sufficiently, left the current element, or another input event has happened.`, 11: `MouseEnter is when the mouse enters the bounding box of a new element. It is used for setting the Hover state, and can trigger cursor changes. See DragEnter for alternative case during Drag events.`, 12: `MouseLeave is when the mouse leaves the bounding box of an element, that previously had a MouseEnter event. Given that elements can have overlapping bounding boxes (e.g., child elements within a container), it is not the case that a MouseEnter on a child triggers a MouseLeave on surrounding containers. See DragLeave for alternative case during Drag events.`, 13: `LongHoverStart is when the mouse has been relatively stable after MouseEnter on an element for a minimum duration (500 msec default). This triggers the LongHover state typically used for Tooltips.`, 14: `LongHoverEnd is after LongHoverStart when the mouse has moved sufficiently, left the current element, or another input event has happened, thereby terminating the LongHover state.`, 15: `DragStart is at the start of a drag-n-drop event sequence, when a Draggable element is Active and a sufficient distance of MouseDrag events has occurred to engage the DragStart event.`, 16: `DragMove is for a MouseDrag event during the drag-n-drop sequence. Usually don't need to listen to this one. MouseDrag is also sent.`, 17: `DragEnter is like MouseEnter but after a DragStart during a drag-n-drop sequence. MouseEnter is not sent in this case.`, 18: `DragLeave is like MouseLeave but after a DragStart during a drag-n-drop sequence. MouseLeave is not sent in this case.`, 19: `Drop is sent when an item being Dragged is dropped on top of a target element. The event struct should be DragDrop.`, 20: `DropDeleteSource is sent to the source Drag element if the Drag-n-Drop event is a Move type, which requires deleting the source element. The event struct should be DragDrop.`, 21: `SlideStart is for a Slideable element when Active and a sufficient distance of MouseDrag events has occurred to engage the SlideStart event. Sets the Sliding state.`, 22: `SlideMove is for a Slideable element after SlideStart is being dragged via MouseDrag events.`, 23: `SlideStop is when the mouse button is released on a Slideable element being dragged via MouseDrag events. This typically also accompanied by a Changed event for the new slider value.`, 24: `Scroll is for scroll wheel or other scrolling events (gestures). These are not unique and Delta is updated during compression.`, 25: `KeyDown is when a key is pressed down. This provides fine-grained data about each key as it happens. KeyChord is recommended for a more complete Key event.`, 26: `KeyUp is when a key is released. This provides fine-grained data about each key as it happens. KeyChord is recommended for a more complete Key event.`, 27: `KeyChord is only generated when a non-modifier key is released, and it also contains a string representation of the full chord, suitable for translation into keyboard commands, emacs-style etc. It can be somewhat delayed relative to the KeyUp.`, 28: `TouchStart is when a touch event starts, for the low-level touch event processing. TouchStart also activates MouseDown, Scroll, Magnify, or Rotate events depending on gesture recognition.`, 29: `TouchEnd is when a touch event ends, for the low-level touch event processing. TouchEnd also activates MouseUp events depending on gesture recognition.`, 30: `TouchMove is when a touch event moves, for the low-level touch event processing. TouchMove also activates MouseMove, Scroll, Magnify, or Rotate events depending on gesture recognition.`, 31: `Magnify is a touch-based magnify event (e.g., pinch)`, 32: `Rotate is a touch-based rotate event.`, 33: `Select is sent for any direction of selection change on (or within if relevant) a Selectable element. Typically need to query the element(s) to determine current selection state.`, 34: `Focus is sent when a Focusable element receives keyboard focus (ie: by tabbing).`, 35: `FocusLost is sent when a Focusable element loses keyboard focus.`, 36: `Change is when a value represented by the element has been changed by the user and committed (for example, someone has typed text in a textfield and then pressed enter). This is *not* triggered when the value has not been committed; see [Input] for that. This is for Editable, Checkable, and Slidable items.`, 37: `Input is when a value represented by the element has changed, but has not necessarily been committed (for example, this triggers each time someone presses a key in a text field). This *is* triggered when the value has not been committed; see [Change] for a version that only occurs when the value is committed. This is for Editable, Checkable, and Slidable items.`, 38: `Show is sent to widgets when their Scene is first shown to the user in its final form, and whenever a major content managing widget (e.g., [core.Tabs], [core.Pages]) shows a new tab/page/element (via [core.WidgetBase.Shown] or DeferShown). This can be used for updates that depend on other elements, or relatively expensive updates that should be only done when actually needed "at show time".`, 39: `Close is sent to widgets when their Scene is being closed. This is an opportunity to save unsaved edits, for example. This is guaranteed to only happen once per widget per Scene.`, 40: `Window reports on changes in the window position, visibility (iconify), focus changes, screen update, and closing. These are only sent once per event (Unique).`, 41: `WindowResize happens when the window has been resized, which can happen continuously during a user resizing episode. These are not Unique events, and are compressed to minimize lag.`, 42: `WindowPaint is sent continuously at FPS frequency (60 frames per second by default) to drive updating check on the window. It is not unique, will be compressed to keep pace with updating.`, 43: `OS is an operating system generated event (app level typically)`, 44: `OSOpenFiles is an event telling app to open given files`, 45: `Custom is a user-defined event with a data any field`} var _TypesMap = map[Types]string{0: `UnknownType`, 1: `MouseDown`, 2: `MouseUp`, 3: `MouseMove`, 4: `MouseDrag`, 5: `Click`, 6: `DoubleClick`, 7: `TripleClick`, 8: `ContextMenu`, 9: `LongPressStart`, 10: `LongPressEnd`, 11: `MouseEnter`, 12: `MouseLeave`, 13: `LongHoverStart`, 14: `LongHoverEnd`, 15: `DragStart`, 16: `DragMove`, 17: `DragEnter`, 18: `DragLeave`, 19: `Drop`, 20: `DropDeleteSource`, 21: `SlideStart`, 22: `SlideMove`, 23: `SlideStop`, 24: `Scroll`, 25: `KeyDown`, 26: `KeyUp`, 27: `KeyChord`, 28: `TouchStart`, 29: `TouchEnd`, 30: `TouchMove`, 31: `Magnify`, 32: `Rotate`, 33: `Select`, 34: `Focus`, 35: `FocusLost`, 36: `Change`, 37: `Input`, 38: `Show`, 39: `Close`, 40: `Window`, 41: `WindowResize`, 42: `WindowPaint`, 43: `OS`, 44: `OSOpenFiles`, 45: `Custom`} diff --git a/events/types.go b/events/types.go index 5f719f9ab9..42ecea940c 100644 --- a/events/types.go +++ b/events/types.go @@ -214,10 +214,11 @@ const ( Input // Show is sent to widgets when their Scene is first shown to the user - // in its final form. Listening to this event enables widgets to perform - // initial one-time activities on startup, in the context of a fully - // rendered display. This is guaranteed to only happen once per widget - // per Scene. + // in its final form, and whenever a major content managing widget + // (e.g., [core.Tabs], [core.Pages]) shows a new tab/page/element (via + // [core.WidgetBase.Shown] or DeferShown). This can be used for updates + // that depend on other elements, or relatively expensive updates that + // should be only done when actually needed "at show time". Show // Close is sent to widgets when their Scene is being closed. This is an diff --git a/tensor/tensorcore/table.go b/tensor/tensorcore/table.go index 8036d9fee5..1bfe75e996 100644 --- a/tensor/tensorcore/table.go +++ b/tensor/tensorcore/table.go @@ -574,7 +574,7 @@ func (tb *Table) RowGrabFocus(row int) *core.WidgetBase { for fli := 0; fli < tb.NCols; fli++ { w := lg.Child(ridx + idxOff + fli).(core.Widget).AsWidget() if w.CanFocus() { - w.SetFocusEvent() + w.SetFocus() return w } } diff --git a/tensor/tensorcore/tensoreditor.go b/tensor/tensorcore/tensoreditor.go index 9f6e93f40e..65a833cc91 100644 --- a/tensor/tensorcore/tensoreditor.go +++ b/tensor/tensorcore/tensoreditor.go @@ -328,7 +328,7 @@ func (tb *TensorEditor) RowGrabFocus(row int) *core.WidgetBase { for fli := 0; fli < tb.NCols; fli++ { w := lg.Child(ridx + idxOff + fli).(core.Widget).AsWidget() if w.CanFocus() { - w.SetFocusEvent() + w.SetFocus() return w } } diff --git a/texteditor/events.go b/texteditor/events.go index 46f70d5b54..90240efbbd 100644 --- a/texteditor/events.go +++ b/texteditor/events.go @@ -541,7 +541,7 @@ func (ed *Editor) OpenLinkAt(pos lexer.Pos) (*paint.TextLink, bool) { func (ed *Editor) handleMouse() { ed.On(events.MouseDown, func(e events.Event) { // note: usual is Click.. if !ed.StateIs(states.Focused) { - ed.SetFocusEvent() + ed.SetFocus() } pt := ed.PointToRelPos(e.Pos()) newPos := ed.PixelToCursor(pt) @@ -571,7 +571,7 @@ func (ed *Editor) handleMouse() { }) ed.OnDoubleClick(func(e events.Event) { if !ed.StateIs(states.Focused) { - ed.SetFocusEvent() + ed.SetFocus() ed.Send(events.Focus, e) // sets focused flag } e.SetHandled() @@ -582,7 +582,7 @@ func (ed *Editor) handleMouse() { }) ed.On(events.TripleClick, func(e events.Event) { if !ed.StateIs(states.Focused) { - ed.SetFocusEvent() + ed.SetFocus() ed.Send(events.Focus, e) // sets focused flag } e.SetHandled() diff --git a/types/typegen/testdata/typegen.go b/types/typegen/testdata/typegen.go index d42cc65c16..3149353580 100644 --- a/types/typegen/testdata/typegen.go +++ b/types/typegen/testdata/typegen.go @@ -1,4 +1,4 @@ -// Code generated by "typegen.test -test.testlogfile=/var/folders/x1/r8shprmj7j71zbw3qvgl9dqc0000gq/T/go-build2217811126/b974/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true"; DO NOT EDIT. +// Code generated by "typegen.test -test.testlogfile=/var/folders/x1/r8shprmj7j71zbw3qvgl9dqc0000gq/T/go-build1829688390/b982/testlog.txt -test.paniconexit0 -test.timeout=20s"; DO NOT EDIT. package testdata