Repository: cogentcore/core (go)

Issue:
Clicking the Children Button of an Element in Inspector Crashes Cogent
### Describe the bug

There's some sort of edit icon present for the Children field of an element in the inspector.  When clicking it Cogent will crash (see slightly redacted crash log)

[crash_2024-11-17_10-13-21.txt](https://github.com/user-attachments/files/17791680/crash_2024-11-17_10-13-21.txt)


### How to reproduce

Run the example minimal reproduction
Open inspector
Click on an element (Submit was used for the included log)
Click on the button in the Children field (1 nodes)

### Example code

```go
package main

import (
	"cogentcore.org/core/core"
	"cogentcore.org/core/events"
	"fmt"
)

func main() {
	b := core.NewBody()
	core.NewText(b).SetText("Minimal Reproduction")
	textField := core.NewTextField(b)
	textField.StartFocus()

	btn := core.NewButton(b).SetText("Submit")

	btn.OnClick(func(e events.Event) {
		core.MessageSnackbar(b, fmt.Sprintf("button click, text content: %v", textField.Text()))
		textField.SetText("")
	})

	//btn.SetKey(keymap.Enter)
	//btn.SetShortcut(keymap.Enter.Chord())
	btn.SetShortcut("Command+S")

	b.RunMainWindow()
}
```


### Relevant output

_No response_

### Platform

Linux

Pull request description:
Redesign reflectx pointer logic to fix panic
This PR makes `reflectx.NonPointerValue`, `Underlying`, and `UnderlyingPointer` take a best-effort approach to nil pointers and interfaces, returning the nil pointer or interface instead of returning an invalid reflect value. This prevents various crashes and increases overall robustness at the cost of causing other issues, which this PR also resolves using the renamed and improved `reflectx.IsNil` function in place of `IsValid` after getting `Underlying` values. This PR also adds a set of `reflectx` pointer tests, which should hopefully significantly reduce the chance of future reflection-related regressions and crashes.

Fixes #1320 

Patch:
diff --git a/base/reflectx/pointers.go b/base/reflectx/pointers.go
index fa43d43a6..7a2611ace 100644
--- a/base/reflectx/pointers.go
+++ b/base/reflectx/pointers.go
@@ -20,9 +20,15 @@ func NonPointerType(typ reflect.Type) reflect.Type {
 }
 
 // NonPointerValue returns a non-pointer version of the given value.
+// If it encounters a nil pointer, it returns the nil pointer instead
+// of an invalid value.
 func NonPointerValue(v reflect.Value) reflect.Value {
 	for v.Kind() == reflect.Pointer {
-		v = v.Elem()
+		new := v.Elem()
+		if !new.IsValid() {
+			return v
+		}
+		v = new
 	}
 	return v
 }
@@ -30,6 +36,9 @@ func NonPointerValue(v reflect.Value) reflect.Value {
 // PointerValue returns a pointer to the given value if it is not already
 // a pointer.
 func PointerValue(v reflect.Value) reflect.Value {
+	if !v.IsValid() {
+		return v
+	}
 	if v.Kind() == reflect.Pointer {
 		return v
 	}
@@ -44,6 +53,9 @@ func PointerValue(v reflect.Value) reflect.Value {
 // OnePointerValue returns a value that is exactly one pointer away
 // from a non-pointer value.
 func OnePointerValue(v reflect.Value) reflect.Value {
+	if !v.IsValid() {
+		return v
+	}
 	if v.Kind() != reflect.Pointer {
 		if v.CanAddr() {
 			return v.Addr()
@@ -61,22 +73,28 @@ func OnePointerValue(v reflect.Value) reflect.Value {
 }
 
 // Underlying returns the actual underlying version of the given value,
-// going through any pointers and interfaces.
+// going through any pointers and interfaces. If it encounters a nil
+// pointer or interface, it returns the nil pointer or interface instead of
+// an invalid value.
 func Underlying(v reflect.Value) reflect.Value {
 	if !v.IsValid() {
 		return v
 	}
 	for v.Type().Kind() == reflect.Interface || v.Type().Kind() == reflect.Pointer {
-		v = v.Elem()
-		if !v.IsValid() {
+		new := v.Elem()
+		if !new.IsValid() {
 			return v
 		}
+		v = new
 	}
 	return v
 }
 
 // UnderlyingPointer returns a pointer to the actual underlying version of the
-// given value, going through any pointers and interfaces.
+// given value, going through any pointers and interfaces. It is equivalent to
+// [OnePointerValue] of [Underlying], so if it encounters a nil pointer or
+// interface, it stops at the nil pointer or interface instead of returning
+// an invalid value.
 func UnderlyingPointer(v reflect.Value) reflect.Value {
 	if !v.IsValid() {
 		return v
diff --git a/base/reflectx/structs.go b/base/reflectx/structs.go
index 7cc05c30a..e647b944b 100644
--- a/base/reflectx/structs.go
+++ b/base/reflectx/structs.go
@@ -103,11 +103,8 @@ func ValueIsDefault(fv reflect.Value, def string) bool {
 // SetFromDefaultTags sets the values of fields in the given struct based on
 // `default:` default value struct field tags.
 func SetFromDefaultTags(v any) error {
-	if AnyIsNil(v) {
-		return nil
-	}
 	ov := reflect.ValueOf(v)
-	if ov.Kind() == reflect.Pointer && ov.IsNil() {
+	if IsNil(ov) {
 		return nil
 	}
 	val := NonPointerValue(ov)
@@ -161,8 +158,8 @@ type ShouldSaver interface {
 func NonDefaultFields(v any) map[string]any {
 	res := map[string]any{}
 
-	rv := NonPointerValue(reflect.ValueOf(v))
-	if !rv.IsValid() {
+	rv := Underlying(reflect.ValueOf(v))
+	if IsNil(rv) {
 		return nil
 	}
 	rt := rv.Type()
diff --git a/base/reflectx/values.go b/base/reflectx/values.go
index a3390fb00..ebadbbbef 100644
--- a/base/reflectx/values.go
+++ b/base/reflectx/values.go
@@ -21,17 +21,16 @@ import (
 	"cogentcore.org/core/enums"
 )
 
-// AnyIsNil checks if an interface value is nil. The interface itself
-// could be nil, or the value pointed to by the interface could be nil.
-// This safely checks both.
-func AnyIsNil(v any) bool {
-	if v == nil {
+// IsNil returns whether the given value is nil or invalid.
+// If it is a non-nillable type, it does not check whether
+// it is nil to avoid panics.
+func IsNil(v reflect.Value) bool {
+	if !v.IsValid() {
 		return true
 	}
-	rv := reflect.ValueOf(v)
-	vk := rv.Kind()
-	if vk == reflect.Pointer || vk == reflect.Interface || vk == reflect.Map || vk == reflect.Slice || vk == reflect.Func || vk == reflect.Chan {
-		return rv.IsNil()
+	switch v.Kind() {
+	case reflect.Pointer, reflect.Interface, reflect.Map, reflect.Slice, reflect.Func, reflect.Chan:
+		return v.IsNil()
 	}
 	return false
 }
@@ -178,24 +177,24 @@ func ToBool(v any) (bool, error) {
 	}
 
 	// then fall back on reflection
-	if AnyIsNil(v) {
+	uv := Underlying(reflect.ValueOf(v))
+	if IsNil(uv) {
 		return false, fmt.Errorf("got nil value of type %T", v)
 	}
-	npv := Underlying(reflect.ValueOf(v))
-	vk := npv.Kind()
+	vk := uv.Kind()
 	switch {
 	case vk >= reflect.Int && vk <= reflect.Int64:
-		return (npv.Int() != 0), nil
+		return (uv.Int() != 0), nil
 	case vk >= reflect.Uint && vk <= reflect.Uint64:
-		return (npv.Uint() != 0), nil
+		return (uv.Uint() != 0), nil
 	case vk == reflect.Bool:
-		return npv.Bool(), nil
+		return uv.Bool(), nil
 	case vk >= reflect.Float32 && vk <= reflect.Float64:
-		return (npv.Float() != 0.0), nil
+		return (uv.Float() != 0.0), nil
 	case vk >= reflect.Complex64 && vk <= reflect.Complex128:
-		return (real(npv.Complex()) != 0.0), nil
+		return (real(uv.Complex()) != 0.0), nil
 	case vk == reflect.String:
-		r, err := strconv.ParseBool(npv.String())
+		r, err := strconv.ParseBool(uv.String())
 		if err != nil {
 			return false, err
 		}
@@ -335,27 +334,27 @@ func ToInt(v any) (int64, error) {
 	}
 
 	// then fall back on reflection
-	if AnyIsNil(v) {
+	uv := Underlying(reflect.ValueOf(v))
+	if IsNil(uv) {
 		return 0, fmt.Errorf("got nil value of type %T", v)
 	}
-	npv := Underlying(reflect.ValueOf(v))
-	vk := npv.Kind()
+	vk := uv.Kind()
 	switch {
 	case vk >= reflect.Int && vk <= reflect.Int64:
-		return npv.Int(), nil
+		return uv.Int(), nil
 	case vk >= reflect.Uint && vk <= reflect.Uint64:
-		return int64(npv.Uint()), nil
+		return int64(uv.Uint()), nil
 	case vk == reflect.Bool:
-		if npv.Bool() {
+		if uv.Bool() {
 			return 1, nil
 		}
 		return 0, nil
 	case vk >= reflect.Float32 && vk <= reflect.Float64:
-		return int64(npv.Float()), nil
+		return int64(uv.Float()), nil
 	case vk >= reflect.Complex64 && vk <= reflect.Complex128:
-		return int64(real(npv.Complex())), nil
+		return int64(real(uv.Complex())), nil
 	case vk == reflect.String:
-		r, err := strconv.ParseInt(npv.String(), 0, 64)
+		r, err := strconv.ParseInt(uv.String(), 0, 64)
 		if err != nil {
 			return 0, err
 		}
@@ -492,27 +491,27 @@ func ToFloat(v any) (float64, error) {
 	}
 
 	// then fall back on reflection
-	if AnyIsNil(v) {
+	uv := Underlying(reflect.ValueOf(v))
+	if IsNil(uv) {
 		return 0, fmt.Errorf("got nil value of type %T", v)
 	}
-	npv := Underlying(reflect.ValueOf(v))
-	vk := npv.Kind()
+	vk := uv.Kind()
 	switch {
 	case vk >= reflect.Int && vk <= reflect.Int64:
-		return float64(npv.Int()), nil
+		return float64(uv.Int()), nil
 	case vk >= reflect.Uint && vk <= reflect.Uint64:
-		return float64(npv.Uint()), nil
+		return float64(uv.Uint()), nil
 	case vk == reflect.Bool:
-		if npv.Bool() {
+		if uv.Bool() {
 			return 1, nil
 		}
 		return 0, nil
 	case vk >= reflect.Float32 && vk <= reflect.Float64:
-		return npv.Float(), nil
+		return uv.Float(), nil
 	case vk >= reflect.Complex64 && vk <= reflect.Complex128:
-		return real(npv.Complex()), nil
+		return real(uv.Complex()), nil
 	case vk == reflect.String:
-		r, err := strconv.ParseFloat(npv.String(), 64)
+		r, err := strconv.ParseFloat(uv.String(), 64)
 		if err != nil {
 			return 0, err
 		}
@@ -649,27 +648,27 @@ func ToFloat32(v any) (float32, error) {
 	}
 
 	// then fall back on reflection
-	if AnyIsNil(v) {
+	uv := Underlying(reflect.ValueOf(v))
+	if IsNil(uv) {
 		return 0, fmt.Errorf("got nil value of type %T", v)
 	}
-	npv := Underlying(reflect.ValueOf(v))
-	vk := npv.Kind()
+	vk := uv.Kind()
 	switch {
 	case vk >= reflect.Int && vk <= reflect.Int64:
-		return float32(npv.Int()), nil
+		return float32(uv.Int()), nil
 	case vk >= reflect.Uint && vk <= reflect.Uint64:
-		return float32(npv.Uint()), nil
+		return float32(uv.Uint()), nil
 	case vk == reflect.Bool:
-		if npv.Bool() {
+		if uv.Bool() {
 			return 1, nil
 		}
 		return 0, nil
 	case vk >= reflect.Float32 && vk <= reflect.Float64:
-		return float32(npv.Float()), nil
+		return float32(uv.Float()), nil
 	case vk >= reflect.Complex64 && vk <= reflect.Complex128:
-		return float32(real(npv.Complex())), nil
+		return float32(real(uv.Complex())), nil
 	case vk == reflect.String:
-		r, err := strconv.ParseFloat(npv.String(), 32)
+		r, err := strconv.ParseFloat(uv.String(), 32)
 		if err != nil {
 			return 0, err
 		}
@@ -689,7 +688,10 @@ func ToFloat32(v any) (float32, error) {
 // pointers, and byte is converted as string(byte), not the decimal representation.
 func ToString(v any) string {
 	nilstr := "nil"
-	if AnyIsNil(v) {
+	// TODO: this reflection is unideal for performance, but we need it to prevent panics,
+	// so this whole "greatest efficiency" type switch is kind of pointless.
+	rv := reflect.ValueOf(v)
+	if IsNil(rv) {
 		return nilstr
 	}
 	switch vt := v.(type) {
@@ -836,26 +838,26 @@ func ToString(v any) string {
 	}
 
 	// then fall back on reflection
-	if AnyIsNil(v) {
+	uv := Underlying(rv)
+	if IsNil(uv) {
 		return nilstr
 	}
-	npv := Underlying(reflect.ValueOf(v))
-	vk := npv.Kind()
+	vk := uv.Kind()
 	switch {
 	case vk >= reflect.Int && vk <= reflect.Int64:
-		return strconv.FormatInt(npv.Int(), 10)
+		return strconv.FormatInt(uv.Int(), 10)
 	case vk >= reflect.Uint && vk <= reflect.Uint64:
-		return strconv.FormatUint(npv.Uint(), 10)
+		return strconv.FormatUint(uv.Uint(), 10)
 	case vk == reflect.Bool:
-		return strconv.FormatBool(npv.Bool())
+		return strconv.FormatBool(uv.Bool())
 	case vk >= reflect.Float32 && vk <= reflect.Float64:
-		return strconv.FormatFloat(npv.Float(), 'G', -1, 64)
+		return strconv.FormatFloat(uv.Float(), 'G', -1, 64)
 	case vk >= reflect.Complex64 && vk <= reflect.Complex128:
-		cv := npv.Complex()
+		cv := uv.Complex()
 		rv := strconv.FormatFloat(real(cv), 'G', -1, 64) + "," + strconv.FormatFloat(imag(cv), 'G', -1, 64)
 		return rv
 	case vk == reflect.String:
-		return npv.String()
+		return uv.String()
 	case vk == reflect.Slice:
 		eltyp := SliceElementType(v)
 		if eltyp.Kind() == reflect.Uint8 { // []byte
@@ -936,35 +938,35 @@ func ToStringPrec(v any, prec int) string {
 // Note that maps are not reset prior to setting, whereas slices are
 // set to be fully equivalent to the source slice.
 func SetRobust(to, from any) error {
-	if AnyIsNil(to) {
+	rto := reflect.ValueOf(to)
+	pto := UnderlyingPointer(rto)
+	if IsNil(pto) {
 		return fmt.Errorf("got nil destination value")
 	}
-	v := reflect.ValueOf(to)
-	pointer := UnderlyingPointer(v)
-	ti := pointer.Interface()
+	pito := pto.Interface()
 
-	typ := pointer.Elem().Type()
-	kind := typ.Kind()
-	if !pointer.Elem().CanSet() {
-		return fmt.Errorf("destination value cannot be set; it must be a variable or field, not a const or tmp or other value that cannot be set (value: %v of type %T)", pointer, pointer)
+	totyp := pto.Elem().Type()
+	tokind := totyp.Kind()
+	if !pto.Elem().CanSet() {
+		return fmt.Errorf("destination value cannot be set; it must be a variable or field, not a const or tmp or other value that cannot be set (value: %v of type %T)", pto, pto)
 	}
 
 	// first we do the generic AssignableTo case
-	if v.Kind() == reflect.Pointer {
+	if rto.Kind() == reflect.Pointer {
 		fv := reflect.ValueOf(from)
 		if fv.IsValid() {
-			if fv.Type().AssignableTo(typ) {
-				pointer.Elem().Set(fv)
+			if fv.Type().AssignableTo(totyp) {
+				pto.Elem().Set(fv)
 				return nil
 			}
 			ufvp := UnderlyingPointer(fv)
-			if ufvp.IsValid() && ufvp.Type().AssignableTo(typ) {
-				pointer.Elem().Set(ufvp)
+			if ufvp.IsValid() && ufvp.Type().AssignableTo(totyp) {
+				pto.Elem().Set(ufvp)
 				return nil
 			}
 			ufv := ufvp.Elem()
-			if ufv.IsValid() && ufv.Type().AssignableTo(typ) {
-				pointer.Elem().Set(ufv)
+			if ufv.IsValid() && ufv.Type().AssignableTo(totyp) {
+				pto.Elem().Set(ufv)
 				return nil
 			}
 		} else {
@@ -972,14 +974,14 @@ func SetRobust(to, from any) error {
 		}
 	}
 
-	if sa, ok := ti.(SetAnyer); ok {
+	if sa, ok := pito.(SetAnyer); ok {
 		err := sa.SetAny(from)
 		if err != nil {
 			return err
 		}
 		return nil
 	}
-	if ss, ok := ti.(SetStringer); ok {
+	if ss, ok := pito.(SetStringer); ok {
 		if s, ok := from.(string); ok {
 			err := ss.SetString(s)
 			if err != nil {
@@ -988,7 +990,7 @@ func SetRobust(to, from any) error {
 			return nil
 		}
 	}
-	if es, ok := ti.(enums.EnumSetter); ok {
+	if es, ok := pito.(enums.EnumSetter); ok {
 		if en, ok := from.(enums.Enum); ok {
 			es.SetInt64(en.Int64())
 			return nil
@@ -1004,7 +1006,7 @@ func SetRobust(to, from any) error {
 		return nil
 	}
 
-	if bv, ok := ti.(bools.BoolSetter); ok {
+	if bv, ok := pito.(bools.BoolSetter); ok {
 		fb, err := ToBool(from)
 		if err != nil {
 			return err
@@ -1012,7 +1014,7 @@ func SetRobust(to, from any) error {
 		bv.SetBool(fb)
 		return nil
 	}
-	if td, ok := ti.(*time.Duration); ok {
+	if td, ok := pito.(*time.Duration); ok {
 		if fs, ok := from.(string); ok {
 			fd, err := time.ParseDuration(fs)
 			if err != nil {
@@ -1024,7 +1026,7 @@ func SetRobust(to, from any) error {
 	}
 
 	if fc, err := colors.FromAny(from); err == nil {
-		switch c := ti.(type) {
+		switch c := pito.(type) {
 		case *color.RGBA:
 			*c = fc
 			return nil
@@ -1043,39 +1045,39 @@ func SetRobust(to, from any) error {
 	ftyp := NonPointerType(reflect.TypeOf(from))
 
 	switch {
-	case kind >= reflect.Int && kind <= reflect.Int64:
+	case tokind >= reflect.Int && tokind <= reflect.Int64:
 		fm, err := ToInt(from)
 		if err != nil {
 			return err
 		}
-		pointer.Elem().Set(reflect.ValueOf(fm).Convert(typ))
+		pto.Elem().Set(reflect.ValueOf(fm).Convert(totyp))
 		return nil
-	case kind >= reflect.Uint && kind <= reflect.Uint64:
+	case tokind >= reflect.Uint && tokind <= reflect.Uint64:
 		fm, err := ToInt(from)
 		if err != nil {
 			return err
 		}
-		pointer.Elem().Set(reflect.ValueOf(fm).Convert(typ))
+		pto.Elem().Set(reflect.ValueOf(fm).Convert(totyp))
 		return nil
-	case kind == reflect.Bool:
+	case tokind == reflect.Bool:
 		fm, err := ToBool(from)
 		if err != nil {
 			return err
 		}
-		pointer.Elem().Set(reflect.ValueOf(fm).Convert(typ))
+		pto.Elem().Set(reflect.ValueOf(fm).Convert(totyp))
 		return nil
-	case kind >= reflect.Float32 && kind <= reflect.Float64:
+	case tokind >= reflect.Float32 && tokind <= reflect.Float64:
 		fm, err := ToFloat(from)
 		if err != nil {
 			return err
 		}
-		pointer.Elem().Set(reflect.ValueOf(fm).Convert(typ))
+		pto.Elem().Set(reflect.ValueOf(fm).Convert(totyp))
 		return nil
-	case kind == reflect.String:
+	case tokind == reflect.String:
 		fm := ToString(from)
-		pointer.Elem().Set(reflect.ValueOf(fm).Convert(typ))
+		pto.Elem().Set(reflect.ValueOf(fm).Convert(totyp))
 		return nil
-	case kind == reflect.Struct:
+	case tokind == reflect.Struct:
 		if ftyp.Kind() == reflect.String {
 			err := json.Unmarshal([]byte(ToString(from)), to) // todo: this is not working -- see what marshal says, etc
 			if err != nil {
@@ -1084,7 +1086,7 @@ func SetRobust(to, from any) error {
 			}
 			return nil
 		}
-	case kind == reflect.Slice:
+	case tokind == reflect.Slice:
 		if ftyp.Kind() == reflect.String {
 			err := json.Unmarshal([]byte(ToString(from)), to)
 			if err != nil {
@@ -1094,7 +1096,7 @@ func SetRobust(to, from any) error {
 			return nil
 		}
 		return CopySliceRobust(to, from)
-	case kind == reflect.Map:
+	case tokind == reflect.Map:
 		if ftyp.Kind() == reflect.String {
 			err := json.Unmarshal([]byte(ToString(from)), to)
 			if err != nil {
@@ -1108,5 +1110,5 @@ func SetRobust(to, from any) error {
 
 	tos := elide.End(fmt.Sprintf("%v", to), 40)
 	fms := elide.End(fmt.Sprintf("%v", from), 40)
-	return fmt.Errorf("unable to set value %s of type %T (using underlying type: %s) from value %s of type %T (using underlying type: %s): not a supported type pair and direct assigning is not possible", tos, to, typ.String(), fms, from, LongTypeName(Underlying(reflect.ValueOf(from)).Type()))
+	return fmt.Errorf("unable to set value %s of type %T (using underlying type: %s) from value %s of type %T (using underlying type: %s): not a supported type pair and direct assigning is not possible", tos, to, totyp.String(), fms, from, LongTypeName(Underlying(reflect.ValueOf(from)).Type()))
 }
diff --git a/cli/field.go b/cli/field.go
index 3c7602963..be83539b9 100644
--- a/cli/field.go
+++ b/cli/field.go
@@ -60,11 +60,8 @@ func addFields(obj any, allFields *fields, cmd string) {
 // the "cmd" struct tag, as the user already knows what command they are
 // running, so they do not need that duplicated specificity for every flag.
 func addFieldsImpl(obj any, path string, cmdPath string, allFields *fields, usedNames map[string]*field, cmd string) {
-	if reflectx.AnyIsNil(obj) {
-		return
-	}
 	ov := reflect.ValueOf(obj)
-	if ov.Kind() == reflect.Pointer && ov.IsNil() {
+	if reflectx.IsNil(ov) {
 		return
 	}
 	val := reflectx.NonPointerValue(ov)
diff --git a/core/dialog.go b/core/dialog.go
index 0683b1ccb..b684a9561 100644
--- a/core/dialog.go
+++ b/core/dialog.go
@@ -6,6 +6,7 @@ package core
 
 import (
 	"log/slog"
+	"reflect"
 
 	"cogentcore.org/core/base/errors"
 	"cogentcore.org/core/base/reflectx"
@@ -198,7 +199,7 @@ func (bd *Body) dialogStyles() {
 // nonNilContext returns a non-nil context widget, falling back on the top
 // scene of the current window.
 func nonNilContext(ctx Widget) Widget {
-	if !reflectx.AnyIsNil(ctx) {
+	if !reflectx.IsNil(reflect.ValueOf(ctx)) {
 		return ctx
 	}
 	return currentRenderWindow.mains.top().Scene
diff --git a/core/form.go b/core/form.go
index 6cdbe7a85..9785dbfd5 100644
--- a/core/form.go
+++ b/core/form.go
@@ -129,7 +129,7 @@ func (fm *Form) Init() {
 	})
 
 	fm.Maker(func(p *tree.Plan) {
-		if reflectx.AnyIsNil(fm.Struct) {
+		if reflectx.IsNil(reflect.ValueOf(fm.Struct)) {
 			return
 		}
 
diff --git a/core/inlinelist.go b/core/inlinelist.go
index 280a0e30a..671cbabfa 100644
--- a/core/inlinelist.go
+++ b/core/inlinelist.go
@@ -76,7 +76,7 @@ func (il *InlineList) Init() {
 // SetSlice sets the source slice that we are viewing.
 // It rebuilds the children to represent this slice.
 func (il *InlineList) SetSlice(sl any) *InlineList {
-	if reflectx.AnyIsNil(sl) {
+	if reflectx.IsNil(reflect.ValueOf(sl)) {
 		il.Slice = nil
 		return il
 	}
diff --git a/core/keyedlist.go b/core/keyedlist.go
index c8a4a1b4c..5a9c221d7 100644
--- a/core/keyedlist.go
+++ b/core/keyedlist.go
@@ -50,10 +50,10 @@ func (kl *KeyedList) Init() {
 	})
 
 	kl.Maker(func(p *tree.Plan) {
-		if reflectx.AnyIsNil(kl.Map) {
+		mapv := reflectx.Underlying(reflect.ValueOf(kl.Map))
+		if reflectx.IsNil(mapv) {
 			return
 		}
-		mapv := reflectx.Underlying(reflect.ValueOf(kl.Map))
 
 		kl.ncols = 2
 		typeAny := false
@@ -187,7 +187,7 @@ func (kl *KeyedList) toggleSort() {
 
 // AddItem adds a new key-value item to the map.
 func (kl *KeyedList) AddItem() {
-	if reflectx.AnyIsNil(kl.Map) {
+	if reflectx.IsNil(reflect.ValueOf(kl.Map)) {
 		return
 	}
 	reflectx.MapAdd(kl.Map)
@@ -196,7 +196,7 @@ func (kl *KeyedList) AddItem() {
 
 // DeleteItem deletes a key-value item from the map.
 func (kl *KeyedList) DeleteItem(key reflect.Value) {
-	if reflectx.AnyIsNil(kl.Map) {
+	if reflectx.IsNil(reflect.ValueOf(kl.Map)) {
 		return
 	}
 	reflectx.MapDelete(kl.Map, reflectx.NonPointerValue(key))
@@ -204,7 +204,7 @@ func (kl *KeyedList) DeleteItem(key reflect.Value) {
 }
 
 func (kl *KeyedList) MakeToolbar(p *tree.Plan) {
-	if reflectx.AnyIsNil(kl.Map) {
+	if reflectx.IsNil(reflect.ValueOf(kl.Map)) {
 		return
 	}
 	tree.Add(p, func(w *Button) {
diff --git a/core/layout.go b/core/layout.go
index e8d938bd4..a479fddc2 100644
--- a/core/layout.go
+++ b/core/layout.go
@@ -1153,7 +1153,8 @@ func (fr *Frame) SizeDown(iter int) bool {
 // total size.
 func (fr *Frame) sizeDownFrame(iter int) bool {
 	if fr.Styles.Display == styles.Custom {
-		return fr.sizeDownCustom(iter)
+		fr.WidgetBase.SizeDown(iter) // behave like a widget
+		return fr.sizeDownChildren(iter)
 	}
 	if !fr.HasChildren() || !fr.layout.shapeCheck(fr, "SizeDown") {
 		return fr.WidgetBase.SizeDown(iter) // behave like a widget
@@ -1485,27 +1486,6 @@ func (fr *Frame) sizeDownAllocActualStacked(iter int) {
 	})
 }
 
-func (fr *Frame) sizeDownCustom(iter int) bool {
-	prel := fr.updateParentRelSizes()
-	fr.growToAlloc()
-	sz := &fr.Geom.Size
-	if DebugSettings.LayoutTrace {
-		fmt.Println(fr, "Custom Managing Alloc:", sz.Alloc.Content)
-	}
-	styles.SetClampMaxVector(&sz.Alloc.Content, sz.Max) // can't be more than max..
-	// this allocates our full size to each child, same as ActualStacked all case
-	asz := sz.Actual.Content
-	fr.ForWidgetChildren(func(i int, cw Widget, cwb *WidgetBase) bool {
-		ksz := &cwb.Geom.Size
-		ksz.Alloc.Total = asz
-		ksz.setContentFromTotal(&ksz.Alloc)
-		return tree.Continue
-	})
-	redo := fr.sizeDownChildren(iter)
-	fr.sizeDownParts(iter) // no std role, just get sizes
-	return prel || redo
-}
-
 // sizeFinalUpdateChildrenSizes can optionally be called for layouts
 // that dynamically create child elements based on final layout size.
 // It ensures that the children are properly sized.
@@ -1567,7 +1547,7 @@ func (wb *WidgetBase) sizeFinalParts() {
 func (fr *Frame) SizeFinal() {
 	if fr.Styles.Display == styles.Custom {
 		fr.WidgetBase.SizeFinal() // behave like a widget
-		fr.WidgetBase.sizeFinalChildren()
+		fr.sizeFinalChildren()
 		return
 	}
 	if !fr.HasChildren() || !fr.layout.shapeCheck(fr, "SizeFinal") {
@@ -1675,7 +1655,6 @@ func (wb *WidgetBase) positionChildren() {
 func (fr *Frame) Position() {
 	if fr.Styles.Display == styles.Custom {
 		fr.positionFromPos()
-		fr.positionChildren()
 		return
 	}
 	if !fr.HasChildren() || !fr.layout.shapeCheck(fr, "Position") {
diff --git a/core/list.go b/core/list.go
index 4b337e01d..0cf485d54 100644
--- a/core/list.go
+++ b/core/list.go
@@ -407,7 +407,7 @@ func (lb *ListBase) SetSliceBase() {
 // Note: it is important to at least set an empty slice of
 // the desired type at the start to enable initial configuration.
 func (lb *ListBase) SetSlice(sl any) *ListBase {
-	if reflectx.AnyIsNil(sl) {
+	if reflectx.IsNil(reflect.ValueOf(sl)) {
 		lb.Slice = nil
 		return lb
 	}
@@ -427,7 +427,7 @@ func (lb *ListBase) SetSlice(sl any) *ListBase {
 	lb.Slice = sl
 	lb.sliceUnderlying = reflectx.Underlying(reflect.ValueOf(lb.Slice))
 	lb.isArray = reflectx.NonPointerType(reflect.TypeOf(sl)).Kind() == reflect.Array
-	lb.elementValue = reflectx.SliceElementValue(sl)
+	lb.elementValue = reflectx.Underlying(reflectx.SliceElementValue(sl))
 	lb.SetSliceBase()
 	return lb
 }
@@ -485,8 +485,8 @@ func (lb *ListBase) BindSelect(val *int) *ListBase {
 
 func (lb *ListBase) UpdateMaxWidths() {
 	lb.maxWidth = 0
-	npv := reflectx.NonPointerValue(lb.elementValue)
-	isString := npv.Type().Kind() == reflect.String && npv.Type() != reflect.TypeFor[icons.Icon]()
+	ev := lb.elementValue
+	isString := ev.Type().Kind() == reflect.String && ev.Type() != reflect.TypeFor[icons.Icon]()
 	if !isString || lb.SliceSize == 0 {
 		return
 	}
@@ -505,10 +505,10 @@ func (lb *ListBase) sliceElementValue(si int) reflect.Value {
 	if si < lb.SliceSize {
 		val = reflectx.Underlying(lb.sliceUnderlying.Index(si)) // deal with pointer lists
 	} else {
-		val = reflectx.Underlying(lb.elementValue)
+		val = lb.elementValue
 	}
 	if !val.IsValid() {
-		val = reflectx.Underlying(lb.elementValue)
+		val = lb.elementValue
 	}
 	return val
 }
@@ -803,7 +803,7 @@ func (lb *ListBase) DeleteAt(i int) {
 }
 
 func (lb *ListBase) MakeToolbar(p *tree.Plan) {
-	if reflectx.AnyIsNil(lb.Slice) {
+	if reflectx.IsNil(reflect.ValueOf(lb.Slice)) {
 		return
 	}
 	if lb.isArray || lb.IsReadOnly() {
diff --git a/core/table.go b/core/table.go
index 8187d602b..e115a37eb 100644
--- a/core/table.go
+++ b/core/table.go
@@ -121,7 +121,7 @@ func (tb *Table) StyleValue(w Widget, s *styles.Style, row, col int) {
 
 // SetSlice sets the source slice that we are viewing.
 func (tb *Table) SetSlice(sl any) *Table {
-	if reflectx.AnyIsNil(sl) {
+	if reflectx.IsNil(reflect.ValueOf(sl)) {
 		tb.Slice = nil
 		return tb
 	}
diff --git a/core/valuer.go b/core/valuer.go
index 9708c8b3c..f23c7f1d8 100644
--- a/core/valuer.go
+++ b/core/valuer.go
@@ -85,7 +85,7 @@ func toValue(value any, tags reflect.StructTag) Value {
 		return NewText()
 	}
 	uv := reflectx.Underlying(rv)
-	if !uv.IsValid() {
+	if reflectx.IsNil(uv) {
 		return NewText()
 	}
 	typ := uv.Type()
diff --git a/core/windowlists.go b/core/windowlists.go
index 83bbf440d..8587ee61e 100644
--- a/core/windowlists.go
+++ b/core/windowlists.go
@@ -49,7 +49,7 @@ func (wl *renderWindowList) FindName(name string) *renderWindow {
 // window and true if found, nil, false otherwise.
 // data of type string works fine -- does equality comparison on string contents.
 func (wl *renderWindowList) findData(data any) (*renderWindow, bool) {
-	if reflectx.AnyIsNil(data) {
+	if reflectx.IsNil(reflect.ValueOf(data)) {
 		return nil, false
 	}
 	typ := reflect.TypeOf(data)
diff --git a/parse/lexer/errors.go b/parse/lexer/errors.go
index 4540640db..f58b185ac 100644
--- a/parse/lexer/errors.go
+++ b/parse/lexer/errors.go
@@ -14,6 +14,7 @@ import (
 	"fmt"
 	"io"
 	"path/filepath"
+	"reflect"
 	"sort"
 
 	"cogentcore.org/core/base/reflectx"
@@ -67,7 +68,7 @@ func (e Error) Report(basepath string, showSrc, showRule bool) string {
 		}
 	}
 	str := fnm + ":" + e.Pos.String() + ": " + e.Msg
-	if showRule && !reflectx.AnyIsNil(e.Rule) {
+	if showRule && !reflectx.IsNil(reflect.ValueOf(e.Rule)) {
 		str += fmt.Sprintf(" (rule: %v)", e.Rule.AsTree().Name)
 	}
 	ssz := len(e.Src)
diff --git a/styles/style_props.go b/styles/style_props.go
index 05f22ce4f..1a4f825d8 100644
--- a/styles/style_props.go
+++ b/styles/style_props.go
@@ -6,6 +6,7 @@ package styles
 
 import (
 	"log/slog"
+	"reflect"
 
 	"cogentcore.org/core/base/errors"
 	"cogentcore.org/core/base/num"
@@ -22,7 +23,7 @@ func styleInhInit(val, parent any) (inh, init bool) {
 	if str, ok := val.(string); ok {
 		switch str {
 		case "inherit":
-			return !reflectx.AnyIsNil(parent), false
+			return !reflectx.IsNil(reflect.ValueOf(parent)), false
 		case "initial":
 			return false, true
 		default:
diff --git a/yaegicore/symbols/cogentcore_org-core-base-reflectx.go b/yaegicore/symbols/cogentcore_org-core-base-reflectx.go
index aef279b5a..c341675c8 100644
--- a/yaegicore/symbols/cogentcore_org-core-base-reflectx.go
+++ b/yaegicore/symbols/cogentcore_org-core-base-reflectx.go
@@ -11,11 +11,11 @@ import (
 func init() {
 	Symbols["cogentcore.org/core/base/reflectx/reflectx"] = map[string]reflect.Value{
 		// function, constant and variable definitions
-		"AnyIsNil":           reflect.ValueOf(reflectx.AnyIsNil),
 		"CloneToType":        reflect.ValueOf(reflectx.CloneToType),
 		"CopyMapRobust":      reflect.ValueOf(reflectx.CopyMapRobust),
 		"CopySliceRobust":    reflect.ValueOf(reflectx.CopySliceRobust),
 		"FormatDefault":      reflect.ValueOf(reflectx.FormatDefault),
+		"IsNil":              reflect.ValueOf(reflectx.IsNil),
 		"KindIsBasic":        reflect.ValueOf(reflectx.KindIsBasic),
 		"KindIsNumber":       reflect.ValueOf(reflectx.KindIsNumber),
 		"LongTypeName":       reflect.ValueOf(reflectx.LongTypeName),


Test patch:
diff --git a/base/reflectx/pointers_test.go b/base/reflectx/pointers_test.go
index f9169b401..0238307a5 100644
--- a/base/reflectx/pointers_test.go
+++ b/base/reflectx/pointers_test.go
@@ -8,8 +8,224 @@ import (
 	"reflect"
 	"testing"
 	"unsafe"
+
+	"github.com/stretchr/testify/assert"
 )
 
+type myInterface interface {
+	myMethod()
+}
+
+func TestNonPointerType(t *testing.T) {
+	assert.Equal(t, reflect.TypeFor[int](), NonPointerType(reflect.TypeFor[int]()))
+	assert.Equal(t, reflect.TypeFor[int](), NonPointerType(reflect.TypeFor[*int]()))
+	assert.Equal(t, reflect.TypeFor[int](), NonPointerType(reflect.TypeFor[**int]()))
+	assert.Equal(t, reflect.TypeFor[int](), NonPointerType(reflect.TypeFor[***int]()))
+
+	assert.Equal(t, reflect.TypeFor[any](), NonPointerType(reflect.TypeFor[any]()))
+	assert.Equal(t, reflect.TypeFor[any](), NonPointerType(reflect.TypeFor[*any]()))
+	assert.Equal(t, reflect.TypeFor[any](), NonPointerType(reflect.TypeFor[**any]()))
+	assert.Equal(t, reflect.TypeFor[any](), NonPointerType(reflect.TypeFor[***any]()))
+
+	assert.Equal(t, nil, NonPointerType(reflect.TypeOf(nil)))
+}
+
+func TestNonPointerValue(t *testing.T) {
+	v := 1
+	rv := reflect.ValueOf(v)
+	assert.True(t, NonPointerValue(reflect.ValueOf(v)).Equal(rv))
+	assert.True(t, NonPointerValue(reflect.ValueOf(&v)).Equal(rv))
+
+	p := &v
+	assert.True(t, NonPointerValue(reflect.ValueOf(p)).Equal(rv))
+	assert.True(t, NonPointerValue(reflect.ValueOf(&p)).Equal(rv))
+
+	a := any(v)
+	assert.True(t, NonPointerValue(reflect.ValueOf(a)).Equal(rv))
+	assert.Equal(t, rv.Type(), NonPointerValue(reflect.ValueOf(a)).Type())
+	assert.True(t, NonPointerValue(reflect.ValueOf(&a)).Equal(rv))
+	// NonPointerValue cannot go through *any to get the true type
+	assert.NotEqual(t, rv.Type(), NonPointerValue(reflect.ValueOf(&a)).Type())
+
+	n := (*int)(nil)
+	rn := reflect.ValueOf(n)
+	assert.True(t, rn.IsValid())
+	assert.True(t, NonPointerValue(rn).IsValid())
+	assert.True(t, NonPointerValue(rn).Equal(rn))
+
+	in := myInterface(nil)
+	rinp := reflect.ValueOf(&in)
+	assert.True(t, rinp.IsValid())
+	assert.True(t, NonPointerValue(rinp).IsValid())
+	assert.True(t, NonPointerValue(rinp).Equal(reflect.ValueOf(in)))
+
+	an := any(nil)
+	ran := reflect.ValueOf(an)
+	assert.False(t, ran.IsValid())
+	assert.False(t, NonPointerValue(ran).IsValid())
+}
+
+func TestPointerValue(t *testing.T) {
+	v := 1
+	rv := reflect.ValueOf(v)
+	assert.False(t, rv.CanAddr())
+	assert.False(t, PointerValue(reflect.ValueOf(v)).Equal(rv))
+	assert.Equal(t, reflect.TypeFor[*int](), PointerValue(reflect.ValueOf(v)).Type())
+
+	p := &v
+	rp := reflect.ValueOf(p)
+	assert.True(t, PointerValue(rp).Equal(rp))
+	assert.Equal(t, reflect.TypeFor[*int](), PointerValue(rp).Type())
+
+	assert.True(t, rp.Elem().CanAddr())
+	assert.True(t, PointerValue(rp.Elem()).Equal(rp))
+	assert.True(t, PointerValue(rp.Elem()).Equal(rp.Elem().Addr()))
+
+	pp := &p
+	rpp := reflect.ValueOf(pp)
+	assert.True(t, PointerValue(rpp).Equal(rpp))
+	assert.Equal(t, reflect.TypeFor[**int](), PointerValue(rpp).Type())
+
+	n := (*int)(nil)
+	rn := reflect.ValueOf(n)
+	assert.True(t, PointerValue(rn).Equal(rn))
+
+	an := any(nil)
+	ran := reflect.ValueOf(an)
+	assert.False(t, ran.IsValid())
+	assert.False(t, PointerValue(ran).IsValid())
+}
+
+func TestOnePointerValue(t *testing.T) {
+	v := 1
+	rv := reflect.ValueOf(v)
+	assert.False(t, rv.CanAddr())
+	assert.False(t, OnePointerValue(reflect.ValueOf(v)).Equal(rv))
+	assert.Equal(t, reflect.TypeFor[*int](), OnePointerValue(reflect.ValueOf(v)).Type())
+
+	p := &v
+	rp := reflect.ValueOf(p)
+	assert.True(t, OnePointerValue(rp).Equal(rp))
+	assert.Equal(t, reflect.TypeFor[*int](), OnePointerValue(rp).Type())
+
+	assert.True(t, rp.Elem().CanAddr())
+	assert.True(t, OnePointerValue(rp.Elem()).Equal(rp))
+	assert.True(t, OnePointerValue(rp.Elem()).Equal(rp.Elem().Addr()))
+
+	pp := &p
+	rpp := reflect.ValueOf(pp)
+	assert.False(t, OnePointerValue(rpp).Equal(rpp))
+	assert.True(t, OnePointerValue(rpp).Equal(rp))
+	assert.Equal(t, reflect.TypeFor[*int](), OnePointerValue(rpp).Type())
+
+	n := (*int)(nil)
+	rn := reflect.ValueOf(n)
+	assert.True(t, rn.IsValid())
+	assert.True(t, OnePointerValue(rn).IsValid())
+	assert.True(t, OnePointerValue(rn).Equal(rn))
+
+	an := any(nil)
+	ran := reflect.ValueOf(an)
+	assert.False(t, ran.IsValid())
+	assert.False(t, OnePointerValue(ran).IsValid())
+}
+
+func TestUnderlying(t *testing.T) {
+	v := 1
+	rv := reflect.ValueOf(v)
+	assert.True(t, Underlying(reflect.ValueOf(v)).Equal(rv))
+	assert.True(t, Underlying(reflect.ValueOf(&v)).Equal(rv))
+
+	p := &v
+	assert.True(t, Underlying(reflect.ValueOf(p)).Equal(rv))
+	assert.True(t, Underlying(reflect.ValueOf(&p)).Equal(rv))
+
+	a := any(v)
+	assert.True(t, Underlying(reflect.ValueOf(a)).Equal(rv))
+	assert.Equal(t, rv.Type(), Underlying(reflect.ValueOf(a)).Type())
+	assert.True(t, Underlying(reflect.ValueOf(&a)).Equal(rv))
+	assert.Equal(t, rv.Type(), Underlying(reflect.ValueOf(&a)).Type())
+
+	n := (*int)(nil)
+	rn := reflect.ValueOf(n)
+	assert.True(t, rn.IsValid())
+	assert.True(t, Underlying(rn).IsValid())
+	assert.True(t, Underlying(rn).Equal(rn))
+
+	in := myInterface(nil)
+	rinp := reflect.ValueOf(&in)
+	assert.True(t, rinp.IsValid())
+	assert.True(t, Underlying(rinp).IsValid())
+	assert.True(t, Underlying(rinp).Equal(rinp.Elem()))
+
+	an := any(nil)
+	ran := reflect.ValueOf(an)
+	assert.False(t, ran.IsValid())
+	assert.False(t, Underlying(ran).IsValid())
+}
+
+func TestUnderlyingPointer(t *testing.T) {
+	v := 1
+	rv := reflect.ValueOf(v)
+	assert.False(t, rv.CanAddr())
+	assert.False(t, UnderlyingPointer(reflect.ValueOf(v)).Equal(rv))
+	assert.Equal(t, reflect.TypeFor[*int](), UnderlyingPointer(reflect.ValueOf(v)).Type())
+
+	p := &v
+	rp := reflect.ValueOf(p)
+	assert.True(t, UnderlyingPointer(rp).Equal(rp))
+	assert.Equal(t, reflect.TypeFor[*int](), UnderlyingPointer(rp).Type())
+
+	assert.True(t, rp.Elem().CanAddr())
+	assert.True(t, UnderlyingPointer(rp.Elem()).Equal(rp))
+	assert.True(t, UnderlyingPointer(rp.Elem()).Equal(rp.Elem().Addr()))
+
+	pp := &p
+	rpp := reflect.ValueOf(pp)
+	assert.False(t, UnderlyingPointer(rpp).Equal(rpp))
+	assert.True(t, UnderlyingPointer(rpp).Equal(rp))
+	assert.Equal(t, reflect.TypeFor[*int](), UnderlyingPointer(rpp).Type())
+
+	a := any(v)
+	ap := &a
+	rap := reflect.ValueOf(ap)
+	// Different pointer, same type
+	assert.False(t, UnderlyingPointer(rap).Equal(rp))
+	assert.Equal(t, rp.Type(), UnderlyingPointer(rap).Type())
+	assert.Equal(t, reflect.TypeFor[*int](), UnderlyingPointer(rap).Type())
+
+	n := (*int)(nil)
+	rn := reflect.ValueOf(n)
+	assert.True(t, rn.IsValid())
+	assert.True(t, UnderlyingPointer(rn).IsValid())
+	assert.True(t, UnderlyingPointer(rn).Equal(rn))
+
+	an := any(nil)
+	ran := reflect.ValueOf(an)
+	assert.False(t, ran.IsValid())
+	assert.False(t, UnderlyingPointer(ran).IsValid())
+}
+
+func TestNonNilNew(t *testing.T) {
+	n0 := NonNilNew(reflect.TypeFor[int]())
+	assert.Equal(t, reflect.TypeFor[*int](), n0.Type())
+	assert.False(t, n0.IsNil())
+	assert.Equal(t, 0, n0.Elem().Interface())
+
+	n1 := NonNilNew(reflect.TypeFor[*int]())
+	assert.Equal(t, reflect.TypeFor[**int](), n1.Type())
+	assert.False(t, n1.IsNil())
+	assert.False(t, n1.Elem().IsNil())
+	assert.Equal(t, 0, n1.Elem().Elem().Interface())
+
+	n2 := NonNilNew(reflect.TypeFor[**int]())
+	assert.Equal(t, reflect.TypeFor[***int](), n2.Type())
+	assert.False(t, n2.IsNil())
+	assert.False(t, n2.Elem().IsNil())
+	assert.False(t, n2.Elem().Elem().IsNil())
+	assert.Equal(t, 0, n2.Elem().Elem().Elem().Interface())
+}
+
 type PointerTestSub struct {
 	Mbr1 string
 	Mbr2 int
diff --git a/base/reflectx/values_test.go b/base/reflectx/values_test.go
index a467410ad..6a26f7bfd 100644
--- a/base/reflectx/values_test.go
+++ b/base/reflectx/values_test.go
@@ -6,6 +6,7 @@ package reflectx
 
 import (
 	"fmt"
+	"reflect"
 	"testing"
 
 	"cogentcore.org/core/base/reflectx/testdata"
@@ -41,32 +42,25 @@ func InitC() {
 	c.Mbr6 = 6
 }
 
-func AFun(aa any) bool {
-	return AnyIsNil(aa)
+func isNil(v any) bool {
+	return IsNil(reflect.ValueOf(v))
 }
 
-func TestAnyIsNil(t *testing.T) {
+func TestIsNil(t *testing.T) {
+	assert.False(t, IsNil(reflect.ValueOf(0)))
+	assert.True(t, IsNil(reflect.ValueOf((*int)(nil))))
+
 	ai := any(a)
 
-	if AnyIsNil(ai) != false {
-		t.Errorf("should be non-nil: %v\n", ai)
-	}
+	assert.False(t, IsNil(reflect.ValueOf(ai)))
 
 	var ap *A
 	api := any(ap)
 
-	if AnyIsNil(api) != true {
-		t.Errorf("should be nil: %v\n", api)
-	}
-
-	if AFun(ap) != true {
-		t.Errorf("should be nil: %v\n", ap)
-	}
-
-	if AFun(&a) != false {
-		t.Errorf("should be non-nil: %v\n", &a)
-	}
-
+	assert.True(t, IsNil(reflect.ValueOf(api)))
+	assert.True(t, isNil(ap))
+	assert.False(t, isNil(&a))
+	assert.False(t, isNil(&ap))
 }
 
 func TestConverts(t *testing.T) {
diff --git a/core/layout_test.go b/core/layout_test.go
index 07c7763cc..44512760b 100644
--- a/core/layout_test.go
+++ b/core/layout_test.go
@@ -258,17 +258,3 @@ func TestCustomLayout(t *testing.T) {
 	})
 	b.AssertRender(t, "layout/custom")
 }
-
-func TestCustomLayoutButton(t *testing.T) {
-	b := NewBody()
-	b.Styler(func(s *styles.Style) {
-		s.Display = styles.Custom
-		s.Min.Set(units.Dp(200), units.Dp(100))
-	})
-	bt := NewButton(b).SetText("Hello")
-	bt.Styler(func(s *styles.Style) {
-		s.Min.X.Dp(100)
-		s.Pos.Set(units.Dp(25))
-	})
-	b.AssertRender(t, "layout/custom-button")
-}